Merge "Fix hole in recycler view" into sc-dev am: 3d6d2e38b0

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/15270060

Change-Id: I7bf9cd8b3ed98c086b0496ce84f5f41d312d68dc
diff --git a/Android.bp b/Android.bp
index 1b6ffe4..45d022f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -112,6 +112,7 @@
         "androidx.preference_preference",
         "androidx.slice_slice-view",
         "androidx.cardview_cardview",
+        "com.google.android.material_material",
         "iconloader_base",
     ],
     manifest: "AndroidManifest-common.xml",
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index d725a16..4eecf29 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -144,7 +144,7 @@
         <activity
             android:name="com.android.launcher3.settings.SettingsActivity"
             android:label="@string/settings_button_text"
-            android:theme="@style/HomeSettingsTheme"
+            android:theme="@style/HomeSettings.Theme"
             android:exported="true"
             android:autoRemoveFromRecents="true">
             <intent-filter>
diff --git a/go/quickstep/res/values-ar/strings.xml b/go/quickstep/res/values-ar/strings.xml
index bbe35f5..3f6e369 100644
--- a/go/quickstep/res/values-ar/strings.xml
+++ b/go/quickstep/res/values-ar/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"استماع"</string>
     <string name="action_translate" msgid="8028378961867277746">"‏ترجمة Google"</string>
     <string name="action_search" msgid="6269564710943755464">"‏عدسة Google"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"حسنًا"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"إلغاء"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"الإعدادات"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"ترجمة النص الظاهر على الشاشة أو الاستماع إليه"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"‏قد تتم مشاركة معلومات مثل النص الظاهر على الشاشة وعناوين الويب ولقطات الشاشة مع Google.\n\nلتغيير المعلومات التي تتم مشاركتها، انتقِل إلى "<b>"الإعدادات &gt; التطبيقات &gt; التطبيقات التلقائية &gt; تطبيق المساعد الرقمي"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"اختيار مساعد لاستخدام هذه الميزة"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"للاستماع للنص الظاهر على الشاشة أو ترجمته، اختَر تطبيق مساعد رقمي في الإعدادات."</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"تغيير المساعد لاستخدام هذه الميزة"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"للاستماع للنص الظاهر على الشاشة أو ترجمته، عليك تغيير تطبيق المساعد الرقمي في الإعدادات."</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"انقر هنا للاستماع للنص الظاهر على هذه الشاشة."</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"انقر هنا لترجمة النص الظاهر على هذه الشاشة."</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-az/strings.xml b/go/quickstep/res/values-az/strings.xml
index f097ad3..1914c3b 100644
--- a/go/quickstep/res/values-az/strings.xml
+++ b/go/quickstep/res/values-az/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Dinləyin"</string>
     <string name="action_translate" msgid="8028378961867277746">"Tərcümə"</string>
     <string name="action_search" msgid="6269564710943755464">"Linza"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"ANLADIM"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"LƏĞV EDİN"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"AYARLAR"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Ekrandakı mətni tərcümə edin və ya dinləyin"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Ekrandakı mətn, veb ünvanlar və ekran görüntüləri kimi məlumatlar Google ilə paylaşıla bilər.\n\nHansı məlumatların paylaşılmasını dəyişmək üçün "<b>"Ayarlar &gt; Tətbiqlər &gt; Defolt tətbiqlər &gt; Rəqəmsal assistent tətbiqi"</b>" bölməsinə keçin."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Bu funksiyadan istifadə etmək üçün assistenti seçin"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Ekrandakı mətni dinləmək və ya tərcümə etmək üçün Ayarlarda rəqəmsal assistent tətbiqini seçin"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Bu funksiyadan istifadə etmək üçün assistenti dəyişin"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Ekrandakı mətni dinləmək və ya tərcümə etmək üçün Ayarlarda rəqəmsal assistent tətbiqini dəyişin"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Bu ekrandakı mətni dinləmək üçün buraya toxunun"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Bu ekrandakı mətni tərcümə etmək üçün buraya toxunun"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-b+sr+Latn/strings.xml b/go/quickstep/res/values-b+sr+Latn/strings.xml
index 8663c81..fc574c8 100644
--- a/go/quickstep/res/values-b+sr+Latn/strings.xml
+++ b/go/quickstep/res/values-b+sr+Latn/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Pusti"</string>
     <string name="action_translate" msgid="8028378961867277746">"Prevedi"</string>
     <string name="action_search" msgid="6269564710943755464">"Objektiv"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"VAŽI"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"OTKAŽI"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"PODEŠAVANJA"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Prevodite ili slušajte tekst na ekranu"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Informacije poput teksta na ekranu, veb-adresa i snimaka ekrana mogu da se dele sa Google-om.\n\nDa biste promenili informacije koje delite, idite u "<b>"Podešavanja &gt; Aplikacije &gt; Podrazumevane aplikacije &gt; Aplikacija digitalnog pomoćnika"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Odaberite pomoćnika da biste koristili ovu funkciju"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Da biste čuli tekst sa ekrana ili ga preveli, odaberite aplikaciju digitalnog pomoćnika u Podešavanjima"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Promenite pomoćnika da biste koristili ovu funkciju"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Da biste čuli tekst sa ekrana ili ga preveli, promenite aplikaciju digitalnog pomoćnika u Podešavanjima"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Dodirnite ovde da biste čuli tekst sa ovog ekrana"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Dodirnite ovde da biste preveli tekst sa ovog ekrana"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-be/strings.xml b/go/quickstep/res/values-be/strings.xml
index d4600cc..e7bfec6 100644
--- a/go/quickstep/res/values-be/strings.xml
+++ b/go/quickstep/res/values-be/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Праслухаць"</string>
     <string name="action_translate" msgid="8028378961867277746">"Перакласці"</string>
     <string name="action_search" msgid="6269564710943755464">"Аб\'ектыў"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"ЗРАЗУМЕЛА"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"СКАСАВАЦЬ"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"НАЛАДЫ"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Перакласці ці праслухаць тэкст на экране"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Такая інфармацыя з вашага экрана, як тэксты, вэб-адрасы і здымкі экрана, можа абагульвацца з Google.\n\nКаб змяніць тып інфармацыі для абагульвання, адкрыйце "<b>"Налады &gt; Праграмы &gt; Стандартныя праграмы &gt; Лічбавы памочнік"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Для карыстання гэтай функцыяй выберыце памочніка"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Каб праслухаць або перакласці тэкст на экране, выберыце ў Наладах лічбавага памочніка"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Для карыстання гэтай функцыяй змяніце памочніка"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Каб праслухаць ці перакласці тэкст на экране, змяніце ў Наладах лічбавага памочніка"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Націсніце тут, каб праслухаць тэкст на экране"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Націсніце тут, каб перакласці тэкст на экране"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-bg/strings.xml b/go/quickstep/res/values-bg/strings.xml
index 3741646..6b2a5f2 100644
--- a/go/quickstep/res/values-bg/strings.xml
+++ b/go/quickstep/res/values-bg/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Слушане"</string>
     <string name="action_translate" msgid="8028378961867277746">"Превод"</string>
     <string name="action_search" msgid="6269564710943755464">"Обектив"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"РАЗБРАХ"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"ОТКАЗ"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"НАСТРОЙКИ"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Превеждане или четене на текста на екрана"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Определена информация може да бъде споделена с Google, като например текстът на екрана ви, уеб адресите и екранните снимки.\n\nЗа да промените информацията, която споделяте, отворете "<b>"„Настройки“ &gt; „Приложения“ &gt; „Приложения по подразбиране“ &gt; „Приложение за дигитален асистент“"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Изберете приложение за асистент, за да използвате тази функция"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"За да слушате или превеждате текст на екрана си, от настройките изберете приложение за дигитален асистент"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Изберете приложение за асистент, за да използвате тази функция"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"За да слушате или превеждате текст на екрана си, от настройките променете приложението си за дигитален асистент"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Докоснете тук, за да слушате текста на този екран"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Докоснете тук, за да преведете текста на този екран"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-ca/strings.xml b/go/quickstep/res/values-ca/strings.xml
index 212df28..cb633d7 100644
--- a/go/quickstep/res/values-ca/strings.xml
+++ b/go/quickstep/res/values-ca/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Escolta"</string>
     <string name="action_translate" msgid="8028378961867277746">"Tradueix"</string>
     <string name="action_search" msgid="6269564710943755464">"Lens"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"ENTESOS"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"CANCEL·LA"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"CONFIGURACIÓ"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Tradueix o escolta el text en pantalla"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"És possible que determinada informació es comparteixi amb Google, com ara el text en pantalla, les adreces web i les captures de pantalla.\n\nPer canviar quina informació comparteixes, ves a "<b>"Configuració &gt; Aplicacions &gt; Aplicacions predeterminades &gt; Aplicació de l\'assistent digital"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Tria un assistent per utilitzar aquesta funció"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Per escoltar o traduir text en pantalla, tria una aplicació d\'assistent digital a Configuració"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Canvia l\'assistent per utilitzar aquesta funció"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Per escoltar o traduir text en pantalla, canvia l\'aplicació d\'assistent digital a Configuració"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Toca aquí per escoltar text en pantalla"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Toca aquí per traduir text en pantalla"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-cs/strings.xml b/go/quickstep/res/values-cs/strings.xml
index 886b5c2..9f69060 100644
--- a/go/quickstep/res/values-cs/strings.xml
+++ b/go/quickstep/res/values-cs/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Poslechnout"</string>
     <string name="action_translate" msgid="8028378961867277746">"Přeložit"</string>
     <string name="action_search" msgid="6269564710943755464">"Lens"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"ROZUMÍM"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"ZRUŠIT"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"NASTAVENÍ"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Překládejte nebo poslouchejte text na obrazovce"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"S Googlem mohou být sdílena data, jako je text na obrazovce, webové adresy a snímky obrazovky.\n\nSdílená data můžete upřesnit v "<b>"Nastavení &gt; Aplikace &gt; Výchozí aplikace &gt; Aplikace digitálního asistenta"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Pokud chcete použít tuto funkci, vyberte asistenta"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Pokud si chcete poslechnout nebo přeložit text na obrazovce, v Nastavení vyberte aplikaci digitálního asistenta"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Pokud chcete použít tuto funkci, změňte asistenta"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Pokud si chcete poslechnout nebo přeložit text na obrazovce, v Nastavení změňte aplikaci digitálního asistenta"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Klepnutím sem si poslechnete text na této obrazovce"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Klepnutím sem přeložíte text na této obrazovce"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-da/strings.xml b/go/quickstep/res/values-da/strings.xml
index 2bc179c..973c320 100644
--- a/go/quickstep/res/values-da/strings.xml
+++ b/go/quickstep/res/values-da/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Lyt"</string>
     <string name="action_translate" msgid="8028378961867277746">"Oversæt"</string>
     <string name="action_search" msgid="6269564710943755464">"Lens"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"OK"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"ANNULLER"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"INDSTILLINGER"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Oversæt eller hør tekst på skærmen"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Oplysninger såsom tekst på din skærm, webadresser og screenshots deles muligvis med Google.\n\nHvis du vil ændre, hvilke oplysninger du deler, kan du gå til "<b>"Indstillinger &gt; Apps &gt; Standardapps &gt; App for digital assistent"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Vælg en assistent for at bruge denne funktion"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Hvis du vil høre eller oversætte tekst på din skærm, skal du vælge en digital assistent i Indstillinger"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Skift assistent for at bruge denne funktion"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Hvis du vil høre eller oversætte tekst på din skærm, skal du ændre din digitale assistent i Indstillinger"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Tryk her for at høre teksten på denne skærm"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Tryk her for at oversætte teksten på denne skærm"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-es-rUS/strings.xml b/go/quickstep/res/values-es-rUS/strings.xml
index cde1cd7..e9762dd 100644
--- a/go/quickstep/res/values-es-rUS/strings.xml
+++ b/go/quickstep/res/values-es-rUS/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Escuchar"</string>
     <string name="action_translate" msgid="8028378961867277746">"Traducir"</string>
     <string name="action_search" msgid="6269564710943755464">"Lens"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"ENTENDIDO"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"CANCELAR"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"CONFIGURACIÓN"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Traduce o escucha el texto que aparece en la pantalla"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Es posible que se comparta información con Google, como el texto de la pantalla, direcciones web y capturas de pantalla.\n\nPara cambiar la información que se comparte, ve a "<b>"Configuración &gt; Apps &gt; Apps predeterminadas &gt; App de asistente digital"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Elige un asistente para usar esta función"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Para escuchar o traducir texto en la pantalla, elige una app de asistente digital en Configuración"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Cambia el asistente para usar esta función"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Para escuchar o traducir texto en la pantalla, cambia la app de asistente digital en Configuración"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Presiona aquí para escuchar texto en esta pantalla"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Presiona aquí para traducir texto en esta pantalla"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-es/strings.xml b/go/quickstep/res/values-es/strings.xml
index 0049443..2ca750e 100644
--- a/go/quickstep/res/values-es/strings.xml
+++ b/go/quickstep/res/values-es/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Escuchar"</string>
     <string name="action_translate" msgid="8028378961867277746">"Traducir"</string>
     <string name="action_search" msgid="6269564710943755464">"Lens"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"ENTENDIDO"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"CANCELAR"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"AJUSTES"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Traduce o escucha texto que haya en pantalla"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Se puede compartir con Google cierta información, como el texto que aparece en pantalla, direcciones web o capturas de pantalla.\n\nPara cambiar la información que compartes, ve a "<b>"Ajustes &gt; Aplicaciones &gt; Aplicaciones predeterminadas &gt; Asistente digital"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Elige un asistente para usar esta función"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Para escuchar o traducir texto que haya en tu pantalla, elige una aplicación de asistente digital en Ajustes"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Cambia tu asistente para usar esta función"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Para escuchar o traducir texto que haya en tu pantalla, cambia tu aplicación de asistente digital en Ajustes"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Toca aquí para escuchar el texto que hay en esta pantalla"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Toca aquí para traducir el texto que hay en esta pantalla"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-et/strings.xml b/go/quickstep/res/values-et/strings.xml
index 9a7c118..5bab725 100644
--- a/go/quickstep/res/values-et/strings.xml
+++ b/go/quickstep/res/values-et/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Kuula"</string>
     <string name="action_translate" msgid="8028378961867277746">"Tõlge"</string>
     <string name="action_search" msgid="6269564710943755464">"Lens"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"SELGE"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"TÜHISTA"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"SEADED"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Ekraanil oleva teksti tõlkimine või kuulamine"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Teavet, nagu teie ekraanil olev tekst, veebiaadressid ja ekraanipildid, võidakse jagada Google\'iga.\n\nKui soovite muuta, millist teavet jagate, avage "<b>"Seaded &gt; Rakendused &gt; Vaikerakendused &gt; Digitaalse assistendi rakendus"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Valige selle funktsiooni kasutamiseks assistent"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Ekraanil kuvatud teksti kuulamiseks või tõlkimiseks valige seadetes digitaalse assistendi rakendus"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Vahetage selle funktsiooni kasutamiseks assistenti"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Ekraanil kuvatud teksti kuulamiseks või tõlkimiseks vahetage seadetes digitaalse assistendi rakendust"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Puudutage siin, et ekraanil kuvatud teksti kuulda"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Puudutage siin, et ekraanil kuvatud tekst tõlkida"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-eu/strings.xml b/go/quickstep/res/values-eu/strings.xml
index 79b1acb..3067382 100644
--- a/go/quickstep/res/values-eu/strings.xml
+++ b/go/quickstep/res/values-eu/strings.xml
@@ -11,7 +11,7 @@
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Itzuli edo entzun pantailako testua"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Agian pantailako testua, web-helbideak, pantaila-argazkiak eta antzeko informazioa partekatuko duzu Google-rekin.\n\nPartekatzen duzun informazioa aldatzeko, joan hona: "<b>"Ezarpenak &gt; Aplikazioak &gt; Aplikazio lehenetsiak &gt; Laguntzaile digitalaren aplikazioa"</b>"."</string>
     <string name="assistant_not_selected_title" msgid="5017072974603345228">"Hautatu laguntzaile bat eginbidea erabiltzeko"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Pantailako testua entzun edo itzultzeko, aukeratu laguntzaile digitalaren aplikazio bat Ezarpenak atalean"</string>
+    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Pantailako testua entzun edo itzultzeko, hautatu laguntzaile digitalaren aplikazio bat Ezarpenak atalean"</string>
     <string name="assistant_not_supported_title" msgid="1675788067597484142">"Aldatu laguntzailea eginbidea erabiltzeko"</string>
     <string name="assistant_not_supported_text" msgid="1708031078549268884">"Pantailako testua entzun edo itzultzeko, aldatu laguntzaile digitalaren aplikazioa Ezarpenak atalean"</string>
     <string name="tooltip_listen" msgid="7634466447860989102">"Sakatu hau pantailako testua entzuteko"</string>
diff --git a/go/quickstep/res/values-fa/strings.xml b/go/quickstep/res/values-fa/strings.xml
index c86fddb..2552717 100644
--- a/go/quickstep/res/values-fa/strings.xml
+++ b/go/quickstep/res/values-fa/strings.xml
@@ -5,12 +5,12 @@
     <string name="action_listen" msgid="2370304050784689486">"گوش دادن"</string>
     <string name="action_translate" msgid="8028378961867277746">"ترجمه"</string>
     <string name="action_search" msgid="6269564710943755464">"لنز"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"متوجه‌ام"</string>
+    <string name="dialog_acknowledge" msgid="2804025517675853172">"متوجه شدم"</string>
     <string name="dialog_cancel" msgid="6464336969134856366">"لغو"</string>
     <string name="dialog_settings" msgid="6564397136021186148">"تنظیمات"</string>
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"ترجمه نوشتار روی صفحه‌نمایش یا گوش دادن به آن"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"‏ممکن است اطلاعاتی مثل نوشتار روی صفحه‌نمایش، نشانی‌های وب، و نماگرفت‌ها با Google هم‌رسانی شود.\n\nبرای تغییر اطلاعاتی که هم‌رسانی می‌کنید، به "<b>"تنظیمات &lt; برنامه‌ها &lt; برنامه‌های پیش‌فرض &lt; برنامه دستیار دیجیتال"</b>" بروید."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"برای استفاده از این ویژگی، دستیاری انتخاب کنید"</string>
+    <string name="assistant_not_selected_title" msgid="5017072974603345228">"برای استفاده از این ویژگی، یکی از دستیارها را انتخاب کنید"</string>
     <string name="assistant_not_selected_text" msgid="3244613673884359276">"برای گوش کردن به نوشتار در صفحه‌نمایش‌تان یا ترجمه کردن آن، یکی از برنامه‌های دستیار دیجیتالی را در «تنظیمات» انتخاب کنید"</string>
     <string name="assistant_not_supported_title" msgid="1675788067597484142">"برای استفاده از این ویژگی، دستیارتان را تغییر دهید"</string>
     <string name="assistant_not_supported_text" msgid="1708031078549268884">"برای گوش کردن به نوشتار در صفحه‌نمایش‌تان یا ترجمه کردن آن، برنامه دستیار دیجیتالی‌تان را در «تنظیمات» تغییر دهید"</string>
diff --git a/go/quickstep/res/values-fi/strings.xml b/go/quickstep/res/values-fi/strings.xml
index c13a92c..51a4474 100644
--- a/go/quickstep/res/values-fi/strings.xml
+++ b/go/quickstep/res/values-fi/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Kuuntele"</string>
     <string name="action_translate" msgid="8028378961867277746">"Käännä"</string>
     <string name="action_search" msgid="6269564710943755464">"Lens"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"OK"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"PERU"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"ASETUKSET"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Käännä tai kuuntele näytöllä oleva teksti"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Googlelle saatetaan jakaa näytöllä olevaa tekstiä, verkko-osoitteita, kuvakaappauksia ja muita tietoja.\n\nVoit valita jaettavat tiedot valitsemalla "<b>"Asetukset &gt; Sovellukset &gt; Oletussovellukset &gt; Digiavustajasovellus"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Valitse avustaja tämän ominaisuuden käyttöön"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Jos haluat kuunnella tai kääntää näytöllä näkyvää tekstiä, valitse digiavustajasovellus asetuksista"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Vaihda avustaja tämän ominaisuuden käyttöä varten"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Jos haluat kuunnella tai kääntää näytöllä näkyvää tekstiä, vaihda digiavustajasovellus asetuksista"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Kuuntele näytöllä näkyvä teksti napauttamalla tästä"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Käännä näytöllä näkyvä teksti napauttamalla tästä"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-fr-rCA/strings.xml b/go/quickstep/res/values-fr-rCA/strings.xml
index d04abd8..5ffded5 100644
--- a/go/quickstep/res/values-fr-rCA/strings.xml
+++ b/go/quickstep/res/values-fr-rCA/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Écouter"</string>
     <string name="action_translate" msgid="8028378961867277746">"Traduire"</string>
     <string name="action_search" msgid="6269564710943755464">"Lentille"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"OK"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"ANNULER"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"PARAMÈTRES"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Traduire ou écouter le texte à l\'écran"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Des renseignements comme du texte sur votre écran, des adresses Web et des captures d\'écran peuvent être partagés avec Google.\n\nPour modifier les renseignements que vous partagez, accédez à "<b>"Paramètres &gt; Applications &gt; Applications par défaut &gt; Application d\'assistant numérique"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Choisir un assistant pour utiliser cette fonctionnalité"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Pour écouter ou traduire le texte affiché sur votre écran, choisissez l\'application d\'un assistant numérique dans les paramètres"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Modifier votre assistant pour utiliser cette fonctionnalité"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Pour écouter ou traduire le texte affiché sur votre écran, modifiez l\'application de votre assistant numérique dans les paramètres"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Touchez ce bouton pour écouter le texte affiché sur cet écran"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Touchez ce bouton pour traduire le texte affiché sur cet écran"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-hu/strings.xml b/go/quickstep/res/values-hu/strings.xml
index f2cd089..3606f9c 100644
--- a/go/quickstep/res/values-hu/strings.xml
+++ b/go/quickstep/res/values-hu/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Lejátszás"</string>
     <string name="action_translate" msgid="8028378961867277746">"Fordítás"</string>
     <string name="action_search" msgid="6269564710943755464">"Lens"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"ÉRTEM"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"MÉGSE"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"BEÁLLÍTÁSOK"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Képernyőn megjelenő szöveg fordítása és hallgatása"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Előfordulhat, hogy a rendszer megoszt bizonyos adatokat (például képernyőn megjelenő szöveget, internetcímeket és képernyőképeket) a Google-lal.\n\nHa módosítani szeretné, hogy milyen adatokat oszt meg, lépjen a "<b>"Beállítások és alkalmazások; Alapértelmezett alkalmazások; Digitális asszisztens app"</b>" menüpontra."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Válasszon asszisztenst a funkció használatához"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"A képernyőn megjelenő szöveg meghallgatásához vagy lefordításához válasszon digitálisasszisztens-alkalmazást a Beállítások menüben"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Módosítsa az asszisztenst a funkció használatához"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"A képernyőn megjelenő szöveg meghallgatásához vagy lefordításához módosítsa a digitálisasszisztens-alkalmazást a Beállítások menüben"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Koppintson ide a jelenleg képernyőn lévő szöveg meghallgatásához"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Koppintson ide a jelenleg képernyőn lévő szöveg lefordításához"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-hy/strings.xml b/go/quickstep/res/values-hy/strings.xml
index b238a92..6cee921 100644
--- a/go/quickstep/res/values-hy/strings.xml
+++ b/go/quickstep/res/values-hy/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Լսել"</string>
     <string name="action_translate" msgid="8028378961867277746">"Թարգմանել"</string>
     <string name="action_search" msgid="6269564710943755464">"Տեսապակի"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"ԵՂԱՎ"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"ՉԵՂԱՐԿԵԼ"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"ԿԱՐԳԱՎՈՐՈՒՄՆԵՐ"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Էկրանի տեքստի թարգմանություն կամ ունկնդրում"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Որոշակի տեղեկություններ (օր․՝ էկրանի տեքստը, վեբ հասցեները և սքրինշոթները) կարող են ուղարկվել Google-ին։\n\nՏեղեկությունները, որոնցով դուք կիսվում եք, կարող եք փոխել՝ անցնելով "<b>"Կարգավորումներ &gt; Հավելվածներ &gt; Կանխադրված հավելվածներ &gt; Թվային օգնականի հավելված"</b>"։"</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Այս գործառույթն օգտագործելու համար ընտրեք օգնական"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Էկրանի տեքստը լսելու կամ թարգմանելու համար կարգավորումներում ընտրեք թվային օգնականի հավելված։"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Այս գործառույթն օգտագործելու համար փոխեք ձեր օգնականը"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Էկրանի տեքստը լսելու կամ թարգմանելու համար կարգավորումներում փոխեք ձեր թվային օգնականի հավելվածը։"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Հպեք այստեղ՝ այս էկրանի տեքստը լսելու համար"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Հպեք այստեղ` այս էկրանի տեքստը թարգմանելու համար"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-is/strings.xml b/go/quickstep/res/values-is/strings.xml
index c882c99..ce27dbb 100644
--- a/go/quickstep/res/values-is/strings.xml
+++ b/go/quickstep/res/values-is/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Hlusta"</string>
     <string name="action_translate" msgid="8028378961867277746">"Þýða"</string>
     <string name="action_search" msgid="6269564710943755464">"Linsa"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"ÉG SKIL"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"HÆTTA VIÐ"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"STILLINGAR"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Þýða eða hlusta á texta á skjánum"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Mögulegt er að upplýsingum á borð við texta á skjánum, vefslóðum og skjámyndum verði deilt með Google.\n\nTil að breyta því hvaða upplýsingum þú deilir skaltu opna "<b>"Stillingar &gt; Forrit &gt; Sjálfgefin forrit &gt; Forrit stafræns hjálpara"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Veldu hjálpara til að nota þennan eiginleika"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Veldu stafrænan hjálpara í stillingum til að hlusta á eða þýða texta á skjánum"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Breyttu hjálparanum til að nota þennan eiginleika"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Breyttu forriti stafræna hjálparans í stillingum til að hlusta á eða þýða texta"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Ýttu hér til að hlusta á texta á þessum skjá"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Ýttu hér til að þýða texta á þessum skjá"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-iw/strings.xml b/go/quickstep/res/values-iw/strings.xml
index fd3747e..8166a37 100644
--- a/go/quickstep/res/values-iw/strings.xml
+++ b/go/quickstep/res/values-iw/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"האזנה"</string>
     <string name="action_translate" msgid="8028378961867277746">"תרגום"</string>
     <string name="action_search" msgid="6269564710943755464">"Lens"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"הבנתי"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"ביטול"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"הגדרות"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"תרגום טקסט שמוצג במסך או האזנה לו"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"‏ייתכן שישותף עם Google מידע כגון טקסט שמוצג במסך, כתובות אינטרנט וצילומי מסך.\n\nכדי לקבוע איזה מידע ישותף, יש לעבור אל "<b>"הגדרות &gt; אפליקציות &amp;gt אפליקציות ברירת מחדל &gt; אפליקציית עוזר דיגיטלי"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"יש לבחור עוזר דיגיטלי כדי להשתמש בתכונה הזו"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"כדי להאזין לטקסט שבמסך או לתרגם אותו, צריך לבחור אפליקציית עוזר דיגיטלי ב\'הגדרות\'"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"צריך לשנות את העוזר הדיגיטלי כדי להשתמש בתכונה הזו"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"כדי להאזין לטקסט שבמסך או לתרגם אותו, צריך לשנות את אפליקציית העוזר הדיגיטלי ב\'הגדרות\'"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"צריך להקיש כאן כדי להאזין לטקסט שבמסך הזה"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"צריך להקיש כאן כדי לתרגם את הטקסט שבמסך הזה"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-ja/strings.xml b/go/quickstep/res/values-ja/strings.xml
index cfbfeca..bc428b1 100644
--- a/go/quickstep/res/values-ja/strings.xml
+++ b/go/quickstep/res/values-ja/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"聴く"</string>
     <string name="action_translate" msgid="8028378961867277746">"翻訳"</string>
     <string name="action_search" msgid="6269564710943755464">"レンズ"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"OK"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"キャンセル"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"設定"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"画面上のテキストを翻訳または聞く"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"画面上のテキスト、ウェブアドレス、スクリーンショットなどの情報が Google と共有される場合があります。\n\n共有される情報を変更するには、"<b>"[設定] &gt; [アプリ] &gt; [デフォルトのアプリ] &gt; [デジタル アシスタント アプリ]"</b>" に移動してください。"</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"この機能を使用するにはアシスタントを選択してください"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"画面上のテキストを翻訳するかまたは聞くには、[設定] でデジタル アシスタント アプリを選択してください"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"この機能を使用するにはアシスタントを変更してください"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"画面上のテキストを翻訳するかまたは聞くには、[設定] でデジタル アシスタント アプリを変更してください"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"この画面上のテキストを聞くには、ここをタップしてください"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"この画面上のテキストを翻訳するには、ここをタップしてください"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-ka/strings.xml b/go/quickstep/res/values-ka/strings.xml
index 3280a7e..adf3e6b 100644
--- a/go/quickstep/res/values-ka/strings.xml
+++ b/go/quickstep/res/values-ka/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"მოსმენა"</string>
     <string name="action_translate" msgid="8028378961867277746">"თარგმნა"</string>
     <string name="action_search" msgid="6269564710943755464">"Lens"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"გასაგებია"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"გაუქმება"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"პარამეტრები"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"თარგმნეთ ან მოისმინეთ ეკრანზე ნაჩვენები ტექსტი"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"ისეთი ინფორმაცია, როგორიც არის ტექსტი თქვენს ეკრანზე, ვებ-მისამართები და ეკრანის ანაბეჭდები შეიძლება გაზიარდეს Google-თან.\n\nთუ გსურთ, შეცვალოთ, რა ინფორმაციას აზიარებთ, გადადით: "<b>"პარამეტრები &gt; აპები &gt; ნაგულისხმევი აპები &gt; ციფრული ასისტენტის აპი"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"ამ ფუნქციით სარგებლობისთვის აირჩიეთ ასისტენტი"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"თქვენს ეკრანზე ნაჩვენები ტექსტის მოსასმენად ან სათარგმნად, აირჩიეთ ციფრული ასისტენტის აპი პარამეტრებიდან"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"ამ ფუნქციით სარგებლობისთვის შეცვალეთ ასისტენტი"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"თქვენს ეკრანზე ნაჩვენები ტექსტის მოსასმენად ან სათარგმნად, შეცვალეთ ციფრული ასისტენტის აპი პარამეტრებიდან"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"შეეხეთ აქ ამ ეკრანზე ნაჩვენები ტექსტის მოსასმენად"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"შეეხეთ აქ ამ ეკრანზე ნაჩვენები ტექსტის სათარგმნად"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-kk/strings.xml b/go/quickstep/res/values-kk/strings.xml
index b68ab3e..8a3aedb 100644
--- a/go/quickstep/res/values-kk/strings.xml
+++ b/go/quickstep/res/values-kk/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Тыңдау"</string>
     <string name="action_translate" msgid="8028378961867277746">"Аудару"</string>
     <string name="action_search" msgid="6269564710943755464">"Объектив"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"ТҮСІНІКТІ"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"БАС ТАРТУ"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"ПАРАМЕТРЛЕР"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Экрандағы мәтінді аудару немесе тыңдау"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Экрандағы мәтін, веб-мекенжайлар мен скриншоттар сияқты ақпарат Google-мен бөлісілуі мүмкін.\n\nБөлісілетін ақпаратты өзгерту үшін "<b>"Параметрлер &gt; Қолданбалар &gt; Әдепкі қолданбалар &gt; Цифрлық көмекші қолданбасы"</b>" тармағына өтіңіз."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Осы функцияны пайдалану үшін көмекшіні таңдау"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Экрандағы мәтінді тыңдау немесе аудару үшін параметрлерден цифрлық көмекшіні таңдаңыз."</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Осы функцияны пайдалану үшін көмекшіні өзгерту"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Экрандағы мәтінді тыңдау немесе аудару үшін параметрлерден цифрлық көмекшіні өзгертіңіз."</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Экрандағы мәтінді тыңдау үшін осы жерде түртіңіз."</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Экрандағы мәтінді аудару үшін осы жерде түртіңіз."</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-ko/strings.xml b/go/quickstep/res/values-ko/strings.xml
index 7b298e9..58e2278 100644
--- a/go/quickstep/res/values-ko/strings.xml
+++ b/go/quickstep/res/values-ko/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"듣기"</string>
     <string name="action_translate" msgid="8028378961867277746">"번역"</string>
     <string name="action_search" msgid="6269564710943755464">"렌즈"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"확인"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"취소"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"설정"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"화면의 텍스트 번역 또는 듣기"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"화면의 텍스트, 웹 주소, 스크린샷과 같은 정보가 Google과 공유될 수 있습니다.\n\n공유되는 정보를 변경하려면 "<b>"설정 &gt; 앱 &gt; 기본 앱 &gt; 디지털 어시스턴트 앱"</b>"으로 이동하세요."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"이 기능을 사용하려면 어시스턴트를 선택하세요."</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"화면에서 텍스트를 듣거나 번역하려면 설정에서 디지털 어시스턴트 앱을 선택하세요."</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"어시스턴트가 이 기능을 사용하도록 변경하세요."</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"화면에서 텍스트를 듣거나 번역하려면 설정에서 디지털 어시스턴트 앱을 변경하세요."</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"화면에서 텍스트를 들으려면 여기를 탭하세요."</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"화면에서 텍스트를 번역하려면 여기를 탭하세요."</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-ky/strings.xml b/go/quickstep/res/values-ky/strings.xml
index 66fb264..0f56c9b 100644
--- a/go/quickstep/res/values-ky/strings.xml
+++ b/go/quickstep/res/values-ky/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Угуу"</string>
     <string name="action_translate" msgid="8028378961867277746">"Которуу"</string>
     <string name="action_search" msgid="6269564710943755464">"Lens"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"ТҮШҮНДҮМ"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"ЖОККО ЧЫГАРУУ"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"ЖӨНДӨӨЛӨР"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Экрандагы текстти которуу же угуу"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Экрандагы текст, веб-даректер жана скриншоттор сыяктуу маалымат Google менен бөлүшүлүшү мүмкүн.\n\nБөлүшүлгөн маалыматты өзгөртүү үчүн"<b>"Жөндөөлөр &gt; Колдонмолор &gt; Демейки колдонмолор &gt; Санариптик жардамчы колдонмосуна өтүңүз"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Бул функцияны колдонуу үчүн жардамчыны тандаңыз"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Экраныңыздагы текстти угуу же которуу үчүн Жөндөөлөрдөн санариптик жардамчы колдонмосун тандаңыз"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Бул функцияны колдонуу үчүн жардамчыңызды өзгөртүңүз"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Экраныңыздагы текстти угуу же которуу үчүн Жөндөөлөрдөн санариптик жардамчы колдонмосун өзгөртүңүз"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Бул экрандагы текстти угуу үчүн бул жерди басыңыз"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Бул экрандагы текстти которуу үчүн бул жерди басыңыз"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-lt/strings.xml b/go/quickstep/res/values-lt/strings.xml
index e47d2bb..e1b4d97 100644
--- a/go/quickstep/res/values-lt/strings.xml
+++ b/go/quickstep/res/values-lt/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Klausyti"</string>
     <string name="action_translate" msgid="8028378961867277746">"Išversti"</string>
     <string name="action_search" msgid="6269564710943755464">"Lens"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"SUPRATAU"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"ATŠAUKTI"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"NUSTATYMAI"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Ekrane esančio teksto vertimas arba klausymas"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Tam tikra informacija, pvz., ekrane esantis tekstas, žiniatinklio adresai ir ekrano kopijos, gali būti bendrinama su „Google“.\n\nJei norite pakeisti, kokia informacija gali būti bendrinama, eikite į "<b>"„Nustatymai“ &gt; „Programos“ &gt; „Numatytosios programos“ &gt; „Skaitmeninio pagelbiklio programa“"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Norint naudoti šią funkciją, reikia pasirinkti pagelbiklį"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Jei norite klausyti teksto ekrane ar jį išversti, pasirinkite skaitmeninio pagelbiklio programą „Nustatymų“ skiltyje"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Norint naudoti šią funkciją, reikia pakeisti pagelbiklį"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Jei norite klausyti teksto ekrane ar jį išversti, pakeiskite skaitmeninio pagelbiklio programą „Nustatymų“ skiltyje"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Palieskite čia, jei norite klausyti teksto šiame ekrane"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Palieskite čia, jei norite išversti tekstą šiame ekrane"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-lv/strings.xml b/go/quickstep/res/values-lv/strings.xml
index 831486a..a697ccb 100644
--- a/go/quickstep/res/values-lv/strings.xml
+++ b/go/quickstep/res/values-lv/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Klausīties"</string>
     <string name="action_translate" msgid="8028378961867277746">"Tulkot"</string>
     <string name="action_search" msgid="6269564710943755464">"Lens"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"LABI"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"ATCELT"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"IESTATĪJUMI"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Ekrānā redzamā teksta atskaņošana vai tulkošana"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Ar uzņēmumu Google var tikt kopīgota noteikta informācija, piemēram, ekrānā redzamais teksts, tīmekļa adreses un ekrānuzņēmumi.\n\nLai mainītu kopīgotās informācijas veidu, atveriet sadaļu "<b>"Iestatījumi &gt; Lietotnes &gt; Noklusējuma lietotnes &gt; Digitālā asistenta lietotne"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Lai izmantotu šo funkciju, izvēlieties asistentu"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Lai klausītos vai tulkotu ekrānā parādīto tekstu, iestatījumos izvēlieties digitālā asistenta lietotni"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Lai izmantotu šo funkciju, mainiet asistentu"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Lai klausītos vai tulkotu ekrānā parādīto tekstu, iestatījumos mainiet digitālā asistenta lietotni"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Lai klausītos ekrānā parādīto tekstu, pieskarieties šeit"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Lai tulkotu ekrānā parādīto tekstu, pieskarieties šeit"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-mk/strings.xml b/go/quickstep/res/values-mk/strings.xml
index 93d582c..5560e11 100644
--- a/go/quickstep/res/values-mk/strings.xml
+++ b/go/quickstep/res/values-mk/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Слушај"</string>
     <string name="action_translate" msgid="8028378961867277746">"Преведи"</string>
     <string name="action_search" msgid="6269564710943755464">"Lens"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"СФАТИВ"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"ОТКАЖИ"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"ПОСТАВКИ"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Преведете или слушајте текст на екранот"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Информациите како текст на екранот, интернет-адреси и слики од екранот може да се споделуваат со Google.\n\nЗа да промените кои информации ќе се споделуваат, одете во "<b>"Поставки &gt; Апликации &gt; Стандардни апликации &gt; Апликација за дигитален помошник"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Изберете помошник за да ја користите функцијава"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"За да го слушнете или преведете текстот од екранот, изберете ја апликацијата за дигитален помошник во „Поставки“"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Променете го помошникот за да ја користите функцијава"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"За да го слушнете или преведете текстот од екранот, променете ја апликацијата за дигитален помошник во „Поставки“"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Допрете тука за да го слушнете текстот од екранов"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Допрете тука за да го преведете текстот од екранов"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-my/strings.xml b/go/quickstep/res/values-my/strings.xml
index e318314..6e0e78a 100644
--- a/go/quickstep/res/values-my/strings.xml
+++ b/go/quickstep/res/values-my/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"နားထောင်ရန်"</string>
     <string name="action_translate" msgid="8028378961867277746">"ဘာသာပြန်ရန်"</string>
     <string name="action_search" msgid="6269564710943755464">"Lens"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"ရပြီ"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"မလုပ်တော့"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"ဆက်တင်များ"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"ဖန်သားပြင်ပေါ်ရှိ စာသားကို ဘာသာပြန်ပါ (သို့) နားထောင်ပါ"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"သင့်ဖန်သားပြင်ပေါ်ရှိ စာသား၊ ဝဘ်လိပ်စာနှင့် ဖန်သားပြင်ဓာတ်ပုံများကဲ့သို့ အချက်အလက်များကို Google နှင့် မျှဝေနိုင်သည်။\n\nသင်မျှဝေသည့် အချက်အလက်များကို ပြောင်းရန် "<b>"ဆက်တင်များ &gt; အက်ပ်များ &gt; မူရင်းအက်ပ်များ &gt; ဒစ်ဂျစ်တယ် Assistant အက်ပ်"</b>" သို့ သွားပါ။"</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"ဤဝန်ဆောင်မှုကို အသုံးပြုရန် assistant ရွေးပါ"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"ဖန်သားပြင်ပေါ်ရှိ စာသားကို နားထောင်ရန် (သို့) ဘာသာပြန်ဆိုရန် ‘ဆက်တင်များ’ တွင် ဒစ်ဂျစ်တယ် assistant အက်ပ် ရွေးပါ"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"ဤဝန်ဆောင်မှုကို သုံးရန် assistant ကို ပြောင်းပါ"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"ဖန်သားပြင်ပေါ်ရှိ စာသားကို နားထောင်ရန် (သို့) ဘာသာပြန်ဆိုရန် ‘ဆက်တင်များ’ တွင် ဒစ်ဂျစ်တယ် assistant အက်ပ်ကို ပြောင်းပါ"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"ဤဖန်သားပြင်ပေါ်ရှိ စာသားကို နားထောင်ရန် ဤနေရာကို တို့ပါ"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"ဤဖန်သားပြင်ပေါ်ရှိ စာသားကို ဘာသာပြန်ဆိုရန် ဤနေရာကို တို့ပါ"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-nb/strings.xml b/go/quickstep/res/values-nb/strings.xml
index ec3ef59..f21b7a5 100644
--- a/go/quickstep/res/values-nb/strings.xml
+++ b/go/quickstep/res/values-nb/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Lytt"</string>
     <string name="action_translate" msgid="8028378961867277746">"Oversett"</string>
     <string name="action_search" msgid="6269564710943755464">"Lens"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"GREIT"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"AVBRYT"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"INNSTILLINGER"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Oversett eller lytt til tekst på skjermen"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Informasjon som tekst på skjermen, nettadresser og skjermdumper kan deles med Google.\n\nFor å endre hvilken informasjon du deler, gå til "<b>"Innstillinger &gt; Apper &gt; Standardapper &gt; Digital assistent-app"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Velg en assistent for å bruke denne funksjonen"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"For å høre eller oversette tekst på skjermen, velg en digital assistent-app i innstillingene"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Endre assistenten for å bruke denne funksjonen"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"For å høre eller oversette tekst på skjermen, endre digital assistent-appen i innstillingene"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Trykk her for å høre teksten på denne skjermen"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Trykk her for å oversette teksten på denne skjermen"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-nl/strings.xml b/go/quickstep/res/values-nl/strings.xml
index 82a8519..3cf93be 100644
--- a/go/quickstep/res/values-nl/strings.xml
+++ b/go/quickstep/res/values-nl/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Luisteren"</string>
     <string name="action_translate" msgid="8028378961867277746">"Vertalen"</string>
     <string name="action_search" msgid="6269564710943755464">"Lens"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"OK"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"ANNULEREN"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"INSTELLINGEN"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Tekst op het scherm vertalen of beluisteren"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Informatie zoals tekst op je scherm, webadressen en screenshots kan met Google worden gedeeld.\n\nAls je wilt aanpassen welke informatie je deelt, ga je naar "<b>"Instellingen &gt; Apps &gt; Standaard-apps &gt; Digitale-assistent-app"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Kies een assistent om deze functie te gebruiken"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Als je tekst op je scherm wilt beluisteren of vertalen, kies je een digitale-assistent-app in Instellingen"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Wijzig de assistent om deze functie te gebruiken"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Als je tekst op je scherm wilt beluisteren of vertalen, wijzig je de digitale-assistent-app in Instellingen"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Tik hier om tekst op dit scherm te beluisteren"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Tik hier om tekst op dit scherm te vertalen"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-pl/strings.xml b/go/quickstep/res/values-pl/strings.xml
index a03e05e..43a49b6 100644
--- a/go/quickstep/res/values-pl/strings.xml
+++ b/go/quickstep/res/values-pl/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Posłuchaj"</string>
     <string name="action_translate" msgid="8028378961867277746">"Przetłumacz"</string>
     <string name="action_search" msgid="6269564710943755464">"Obiektyw"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"OK"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"ANULUJ"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"USTAWIENIA"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Przetłumacz lub odsłuchaj tekst na ekranie"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Informacje takie jak tekst na ekranie, adresy internetowe i zrzuty ekranu mogą być udostępniane Google.\n\nAby zmienić zakres udostępnianych informacji, kliknij "<b>"Ustawienia &gt; Aplikacje &gt; Aplikacje domyślne &gt; Asystent cyfrowy"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Aby użyć tej funkcji, wybierz asystenta"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Aby odsłuchać lub przetłumaczyć tekst widoczny na ekranie, wybierz w Ustawieniach aplikację asystenta cyfrowego"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Aby użyć tej funkcji, zmień asystenta"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Aby odsłuchać lub przetłumaczyć tekst widoczny na ekranie, zmień w Ustawieniach aplikację asystenta cyfrowego"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Kliknij tutaj, aby odsłuchać tekst widoczny na ekranie"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Kliknij tutaj, aby przetłumaczyć tekst widoczny na ekranie"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-pt/strings.xml b/go/quickstep/res/values-pt/strings.xml
index 0a0da37..32560d0 100644
--- a/go/quickstep/res/values-pt/strings.xml
+++ b/go/quickstep/res/values-pt/strings.xml
@@ -4,7 +4,7 @@
     <string name="app_share_drop_target_label" msgid="5804774105974539508">"Compartilhar app"</string>
     <string name="action_listen" msgid="2370304050784689486">"Ouvir"</string>
     <string name="action_translate" msgid="8028378961867277746">"Traduzir"</string>
-    <string name="action_search" msgid="6269564710943755464">"Lens"</string>
+    <string name="action_search" msgid="6269564710943755464">"Google Lens"</string>
     <string name="dialog_acknowledge" msgid="2804025517675853172">"OK"</string>
     <string name="dialog_cancel" msgid="6464336969134856366">"CANCELAR"</string>
     <string name="dialog_settings" msgid="6564397136021186148">"CONFIGURAÇÕES"</string>
diff --git a/go/quickstep/res/values-ro/strings.xml b/go/quickstep/res/values-ro/strings.xml
index 37cf48d..cfd11f8 100644
--- a/go/quickstep/res/values-ro/strings.xml
+++ b/go/quickstep/res/values-ro/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Ascultați"</string>
     <string name="action_translate" msgid="8028378961867277746">"Traduceți"</string>
     <string name="action_search" msgid="6269564710943755464">"Lens"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"OK"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"ANULAȚI"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"SETĂRI"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Traduceți sau ascultați textul de pe ecran"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Informații precum textul de pe ecran, adresele web și capturile de ecran pot fi trimise la Google.\n\nCa să schimbați informațiile trimise, accesați "<b>"Setări &gt; Aplicații &gt; Aplicații prestabilite &gt; Aplicația asistent digital"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Alegeți un asistent pentru a folosi această funcție"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Pentru a asculta sau a traduce text de pe ecran, alegeți o aplicație asistent digital în Setări"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Schimbați asistentul pentru a folosi această funcție"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Pentru a asculta sau a traduce text de pe ecran, schimbați aplicația asistent digital în Setări"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Atingeți aici pentru a asculta text de pe ecran"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Atingeți aici pentru a traduce text de pe ecran"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-ru/strings.xml b/go/quickstep/res/values-ru/strings.xml
index d31c42a..ad9c7bd 100644
--- a/go/quickstep/res/values-ru/strings.xml
+++ b/go/quickstep/res/values-ru/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Слушать"</string>
     <string name="action_translate" msgid="8028378961867277746">"Перевести"</string>
     <string name="action_search" msgid="6269564710943755464">"Объектив"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"ОК"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"ОТМЕНА"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"ОТКРЫТЬ НАСТРОЙКИ"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Перевод или прослушивание текста на экране"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Определенные сведения (например, текст на экране, веб-адреса и скриншоты) могут быть отправлены в Google.\n\nИзменить информацию, которой вы делитесь, можно в меню "<b>"Настройки &gt; Приложения &gt; Приложения по умолчанию &gt; Цифровой помощник"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Выберите помощника, чтобы пользоваться этой функцией"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Чтобы слушать или переводить текст на экране, укажите цифрового помощника в настройках."</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Смените помощника, чтобы пользоваться этой функцией"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Чтобы слушать или переводить текст на экране, выберите другого цифрового помощника в настройках."</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Нажмите, чтобы прослушать текст на этой странице"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Нажмите, чтобы перевести текст на этой странице"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-sl/strings.xml b/go/quickstep/res/values-sl/strings.xml
index 31fcffc..ba17f7d 100644
--- a/go/quickstep/res/values-sl/strings.xml
+++ b/go/quickstep/res/values-sl/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Poslušanje"</string>
     <string name="action_translate" msgid="8028378961867277746">"Prevedi"</string>
     <string name="action_search" msgid="6269564710943755464">"Lens"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"RAZUMEM"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"PREKLIČI"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"NASTAVITVE"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Prevod ali poslušanje besedila na zaslonu"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Podatki, kot so besedilo na zaslonu, spletni naslovi in posnetki zaslonov, bodo morda deljeni z Googlom.\n\nČe želite spremeniti, katere podatke delite, odprite "<b>"Nastavitve &gt; Aplikacije &gt; Privzete aplikacije &gt; Digitalni pomočnik"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Za uporabo te funkcije izberite pomočnika"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Za poslušanje ali prevod besedila na zaslonu v nastavitvah izberite digitalnega pomočnika."</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Za uporabo te funkcije izberite drugega pomočnika"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Za poslušanje ali prevod besedila na zaslonu v nastavitvah izberite drugega digitalnega pomočnika."</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Dotaknite se tukaj za poslušanje besedila na zaslonu."</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Dotaknite se tukaj za prevod besedila na zaslonu."</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-sr/strings.xml b/go/quickstep/res/values-sr/strings.xml
index 9c9a332..9349d5f 100644
--- a/go/quickstep/res/values-sr/strings.xml
+++ b/go/quickstep/res/values-sr/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Пусти"</string>
     <string name="action_translate" msgid="8028378961867277746">"Преведи"</string>
     <string name="action_search" msgid="6269564710943755464">"Објектив"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"ВАЖИ"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"ОТКАЖИ"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"ПОДЕШАВАЊА"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Преводите или слушајте текст на екрану"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Информације попут текста на екрану, веб-адреса и снимака екрана могу да се деле са Google-ом.\n\nДа бисте променили информације које делите, идите у "<b>"Подешавања &gt; Апликације &gt; Подразумеване апликације &gt; Апликација дигиталног помоћника"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Одаберите помоћника да бисте користили ову функцију"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Да бисте чули текст са екрана или га превели, одаберите апликацију дигиталног помоћника у Подешавањима"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Промените помоћника да бисте користили ову функцију"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Да бисте чули текст са екрана или га превели, промените апликацију дигиталног помоћника у Подешавањима"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Додирните овде да бисте чули текст са овог екрана"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Додирните овде да бисте превели текст са овог екрана"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-sv/strings.xml b/go/quickstep/res/values-sv/strings.xml
index cb70a58..b881454 100644
--- a/go/quickstep/res/values-sv/strings.xml
+++ b/go/quickstep/res/values-sv/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Lyssna"</string>
     <string name="action_translate" msgid="8028378961867277746">"Översätt"</string>
     <string name="action_search" msgid="6269564710943755464">"Lens"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"OK"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"AVBRYT"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"INSTÄLLNINGAR"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Översätt eller lyssna på texten på skärmen"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Information som text på skärmen, webbadresser och skärmbilder kan delas med Google.\n\nFör att ändra vilken information du delar öppnar du "<b>"Inställningar &gt; Appar &gt; Standardappar &gt; Digital assistentapp"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Välj en assistent om du vill använda den här funktionen"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Välj en digital assistentapp i Inställningar om du vill lyssna på eller översätta text på skärmen"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Byt assistent om du vill använda den här funktionen"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Byt digital assistentapp i Inställningar om du vill lyssna på eller översätta text på skärmen"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Tryck här för att lyssna på texten på skärmen"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Tryck här för att översätta texten på skärmen"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-sw/strings.xml b/go/quickstep/res/values-sw/strings.xml
index a78980a..3ff9d9c 100644
--- a/go/quickstep/res/values-sw/strings.xml
+++ b/go/quickstep/res/values-sw/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Sikiliza"</string>
     <string name="action_translate" msgid="8028378961867277746">"Tafsiri"</string>
     <string name="action_search" msgid="6269564710943755464">"Lenzi"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"NIMEELEWA"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"GHAIRI"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"MIPANGILIO"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Tafsiri au usikilize maandishi kwenye skrini"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Maelezo kama vile maandishi kwenye skrini yako, anwani za wavuti na picha za skrini yanaweza kushirikiwa na Google.\n\nIli ubadilishe maelezo unayoshiriki, nenda kwenye "<b>"Mipangilio &gt; Programu &gt; Programu chaguomsingi &gt; Programu ya mratibu dijitali"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Chagua programu ya mratibu ili utumie kipengele hiki"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Ili usikilize au utafsiri maandishi kwenye skrini yako, chagua programu ya mratibu dijitali katika Mipangilio"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Badilisha programu yako ya mratibu ili utumie kipengele hiki"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Ili usikilize au utafsiri maandishi kwenye skrini yako, badilisha programu yako ya mratibu dijitali katika Mipangilio"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Gusa hapa ili usikilize maandishi kwenye skrini hii"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Gusa hapa ili utafsiri maandishi kwenye skrini hii"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-ta/strings.xml b/go/quickstep/res/values-ta/strings.xml
index 896c7c6..cdb91c6 100644
--- a/go/quickstep/res/values-ta/strings.xml
+++ b/go/quickstep/res/values-ta/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"கேளுங்கள்"</string>
     <string name="action_translate" msgid="8028378961867277746">"மொழிபெயர்"</string>
     <string name="action_search" msgid="6269564710943755464">"லென்ஸ்"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"சரி"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"ரத்துசெய்"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"அமைப்புகள்"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"திரையிலுள்ள உரையை மொழிபெயருங்கள் அல்லது கேளுங்கள்"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"உங்கள் திரையிலுள்ள உரை, வலை முகவரிகள், ஸ்கிரீன்ஷாட்டுகள் போன்ற தகவல்கள் Googleளுடன் பகிரப்படலாம்.\n\nஎந்தத் தகவல்கள் பகிரப்படலாம் என்பதை மாற்ற, "<b>"அமைப்புகள் &gt; ஆப்ஸ் &gt; இயல்பு ஆப்ஸ் &gt; டிஜிட்டல் அசிஸ்டண்ட் ஆப்ஸ்"</b>" என்பதற்குச் செல்லுங்கள்."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"இந்த அம்சத்தைப் பயன்படுத்த அசிஸ்டண்ட்டைத் தேர்வுசெய்யவும்"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"திரையில் தோன்றும் வார்த்தைகளைக் கேட்கவோ மொழிபெயர்க்கவோ அமைப்புகளில் டிஜிட்டல் அசிஸ்டண்ட் ஆப்ஸைத் தேர்வுசெய்யவும்"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"இந்த அம்சத்தைப் பயன்படுத்த அசிஸ்டண்ட்டை மாற்றவும்"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"திரையில் தோன்றும் வார்த்தைகளைக் கேட்கவோ மொழிபெயர்க்கவோ அமைப்புகளில் டிஜிட்டல் அசிஸ்டண்ட் ஆப்ஸை மாற்றவும்"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"இந்தத் திரையில் தோன்றும் வார்த்தைகளைக் கேட்க இங்கே தட்டவும்"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"இந்தத் திரையில் தோன்றும் வார்த்தைகளை மொழிபெயர்க்க இங்கே தட்டவும்"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-th/strings.xml b/go/quickstep/res/values-th/strings.xml
index f19cb2a..3267525 100644
--- a/go/quickstep/res/values-th/strings.xml
+++ b/go/quickstep/res/values-th/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"ฟัง"</string>
     <string name="action_translate" msgid="8028378961867277746">"แปลภาษา"</string>
     <string name="action_search" msgid="6269564710943755464">"Lens"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"รับทราบ"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"ยกเลิก"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"การตั้งค่า"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"แปลหรือฟังข้อความบนหน้าจอ"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"อาจมีการแชร์ข้อมูลอย่างเช่น ข้อความบนหน้าจอ ที่อยู่เว็บ และภาพหน้าจอกับ Google\n\nหากต้องการเปลี่ยนประเภทข้อมูลที่คุณแชร์ ให้ไปที่"<b>"การตั้งค่า &gt; แอป &gt; แอปเริ่มต้น &gt; แอปผู้ช่วยดิจิทัล"</b></string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"เลือกผู้ช่วยเพื่อใช้ฟีเจอร์นี้"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"เลือกแอปผู้ช่วยดิจิทัลในการตั้งค่าเพื่อฟังหรือแปลข้อความบนหน้าจอ"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"เปลี่ยนผู้ช่วยเพื่อใช้ฟีเจอร์นี้"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"เปลี่ยนแอปผู้ช่วยดิจิทัลในการตั้งค่าเพื่อฟังหรือแปลข้อความบนหน้าจอ"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"แตะที่นี่เพื่อฟังข้อความบนหน้าจอนี้"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"แตะที่นี่เพื่อแปลข้อความบนหน้าจอนี้"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-tl/strings.xml b/go/quickstep/res/values-tl/strings.xml
index 1810c4c..73bfb7f 100644
--- a/go/quickstep/res/values-tl/strings.xml
+++ b/go/quickstep/res/values-tl/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Makinig"</string>
     <string name="action_translate" msgid="8028378961867277746">"Isalin"</string>
     <string name="action_search" msgid="6269564710943755464">"Lens"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"OK"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"KANSELAHIN"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"MGA SETTING"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"I-translate o pakinggan ang text sa screen"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Posibleng ibahagi sa Google ang impormasyong gaya ng text sa iyong screen, mga web address, at screenshot.\n\nPara baguhin kung anong impormasyon ang ibinabahagi mo, pumunta sa "<b>"Mga Setting &gt; Mga App &gt; Mga default na app &gt; Digital assistant app"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Pumili ng assistant para magamit ang feature na ito"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Para pakinggan o isalin ang text sa iyong screen, pumili ng app ng digital na assistant sa Mga Setting"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Palitan ang iyong assistant para magamit ang feature na ito"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Para pakinggan o isalin ang text sa iyong screen, palitan ang iyong app ng digital na assistant sa Mga Setting"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Mag-tap dito para pakinggan ang text sa screen na ito"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Mag-tap dito para isalin ang text sa screen na ito"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-tr/strings.xml b/go/quickstep/res/values-tr/strings.xml
index 0854dec..0e16d93 100644
--- a/go/quickstep/res/values-tr/strings.xml
+++ b/go/quickstep/res/values-tr/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Dinle"</string>
     <string name="action_translate" msgid="8028378961867277746">"Çevir"</string>
     <string name="action_search" msgid="6269564710943755464">"Lens"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"ANLADIM"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"İPTAL"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"AYARLAR"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Ekrandaki metni çevirin veya dinleyin"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Ekranınızdaki metin, web adresleri ve ekran görüntüleri gibi bilgiler Google ile paylaşılabilir.\n\nPaylaştığınız bilgileri değiştirmek için "<b>"Ayarlar &gt; Uygulamalar &gt; Varsayılan uygulamalar &gt; Dijital asistan uygulamasına gidin"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Bu özelliği kullanmak için bir asistan seçin"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Ekranınızdaki metni dinlemek veya çevirmek için Ayarlar\'dan bir dijital asistan uygulaması seçin"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Bu özelliği kullanmak için asistanınızı değiştirin"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Ekranınızdaki metni dinlemek veya çevirmek için Ayarlar\'dan dijital asistan uygulamanızı değiştirin"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Bu ekrandaki metni dinlemek için buraya dokunun"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Bu ekrandaki metni çevirmek için buraya dokunun"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-uk/strings.xml b/go/quickstep/res/values-uk/strings.xml
index 475ff20..66b4be8 100644
--- a/go/quickstep/res/values-uk/strings.xml
+++ b/go/quickstep/res/values-uk/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Слухати"</string>
     <string name="action_translate" msgid="8028378961867277746">"Перекласти"</string>
     <string name="action_search" msgid="6269564710943755464">"Об’єктив"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"OK"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"СКАСУВАТИ"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"НАЛАШТУВАННЯ"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Перекласти або прослухати текст на екрані"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Певна інформація (наприклад, текст на екрані, веб-адреси, знімки екрана) може надсилатися на сервери Google.\n\nЩоб змінити типи даних, які надсилаються, відкрийте "<b>"Налаштування &gt; Додатки &gt; Додатки за умовчанням &gt; Цифровий помічник"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Щоб користуватися цією функцією, виберіть помічника"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Щоб прослухати чи перекласти текст на екрані, виберіть цифрового помічника в налаштуваннях"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Змініть помічника, щоб користуватися цією функцією"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Щоб прослухати чи перекласти текст на екрані, змініть цифрового помічника в налаштуваннях"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Натисніть тут, щоб прослухати текст на цьому екрані"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Натисніть тут, щоб перекласти текст на цьому екрані"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-vi/strings.xml b/go/quickstep/res/values-vi/strings.xml
index cebb934..d66e01d 100644
--- a/go/quickstep/res/values-vi/strings.xml
+++ b/go/quickstep/res/values-vi/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"Nghe"</string>
     <string name="action_translate" msgid="8028378961867277746">"Dịch"</string>
     <string name="action_search" msgid="6269564710943755464">"Ống kính"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"OK"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"HỦY"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"CÀI ĐẶT"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Dịch hoặc nghe văn bản trên màn hình"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Những thông tin như văn bản trên màn hình, địa chỉ web và ảnh chụp màn hình có thể được chia sẻ với Google.\n\nĐể thay đổi những thông tin mà bạn chia sẻ, hãy chuyển đến phần "<b>"Cài đặt &gt; Ứng dụng &gt; Ứng dụng mặc định &gt; Ứng dụng trợ lý kỹ thuật số"</b>"."</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"Chọn một ứng dụng trợ lý để dùng tính năng này"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"Để nghe hoặc dịch văn bản trên màn hình, hãy chọn một ứng dụng trợ lý kỹ thuật số trong phần Cài đặt"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"Thay đổi ứng dụng trợ lý để dùng tính năng này"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"Để nghe hoặc dịch văn bản trên màn hình, hãy thay đổi ứng dụng trợ lý kỹ thuật số trong phần Cài đặt"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"Nhấn vào đây để nghe văn bản trên màn hình này"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"Nhấn vào đây để dịch văn bản trên màn hình này"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-zh-rCN/strings.xml b/go/quickstep/res/values-zh-rCN/strings.xml
index 9a71dd7..b31de8f 100644
--- a/go/quickstep/res/values-zh-rCN/strings.xml
+++ b/go/quickstep/res/values-zh-rCN/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"聆听"</string>
     <string name="action_translate" msgid="8028378961867277746">"翻译"</string>
     <string name="action_search" msgid="6269564710943755464">"智能镜头"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"知道了"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"取消"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"设置"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"翻译或聆听屏幕上的文字"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"系统可能会将屏幕上的文字、网址和屏幕截图等信息分享给 Google。\n\n如需更改要分享哪些信息,请依次转到"<b>"设置 &gt; 应用 &gt; 默认应用 &gt; 数字助理应用"</b>"。"</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"需选择一款助理应用,才能使用此功能"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"如需收听或翻译屏幕上的文字,请在“设置”部分选择一款数字助理应用"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"需更改助理应用,才能使用此功能"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"如需收听或翻译屏幕上的文字,请在“设置”部分更改数字助理应用"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"点按此处即可收听屏幕上的文字"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"点按此处即可翻译屏幕上的文字"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/go/quickstep/res/values-zh-rTW/strings.xml b/go/quickstep/res/values-zh-rTW/strings.xml
index 86d78c6..a79c268 100644
--- a/go/quickstep/res/values-zh-rTW/strings.xml
+++ b/go/quickstep/res/values-zh-rTW/strings.xml
@@ -5,15 +5,24 @@
     <string name="action_listen" msgid="2370304050784689486">"聆聽"</string>
     <string name="action_translate" msgid="8028378961867277746">"翻譯"</string>
     <string name="action_search" msgid="6269564710943755464">"智慧鏡頭"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"我知道了"</string>
-    <string name="dialog_cancel" msgid="6464336969134856366">"取消"</string>
-    <string name="dialog_settings" msgid="6564397136021186148">"設定"</string>
+    <!-- no translation found for dialog_acknowledge (2804025517675853172) -->
+    <skip />
+    <!-- no translation found for dialog_cancel (6464336969134856366) -->
+    <skip />
+    <!-- no translation found for dialog_settings (6564397136021186148) -->
+    <skip />
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"翻譯或朗讀畫面上的文字"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"系統可能會將畫面上的文字、網址和螢幕截圖等資訊分享給 Google。\n\n如要變更分享的資訊類型,請前往 [設定] &gt; [應用程式] &gt; [預設應用程式] &gt; [數位助理應用程式]"<b></b>"。"</string>
-    <string name="assistant_not_selected_title" msgid="5017072974603345228">"必須選擇數位助理應用程式才能使用這項功能"</string>
-    <string name="assistant_not_selected_text" msgid="3244613673884359276">"如要聽取或翻譯畫面上的文字,請前往「設定」選擇數位助理應用程式"</string>
-    <string name="assistant_not_supported_title" msgid="1675788067597484142">"必須變更數位助理應用程式才能使用這項功能"</string>
-    <string name="assistant_not_supported_text" msgid="1708031078549268884">"如要聽取或翻譯畫面上的文字,請前往「設定」變更數位助理應用程式"</string>
-    <string name="tooltip_listen" msgid="7634466447860989102">"輕觸這裡即可聽取這個畫面上的文字"</string>
-    <string name="tooltip_translate" msgid="4184845868901542567">"輕觸這裡即可翻譯這個畫面上的文字"</string>
+    <!-- no translation found for assistant_not_selected_title (5017072974603345228) -->
+    <skip />
+    <!-- no translation found for assistant_not_selected_text (3244613673884359276) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_title (1675788067597484142) -->
+    <skip />
+    <!-- no translation found for assistant_not_supported_text (1708031078549268884) -->
+    <skip />
+    <!-- no translation found for tooltip_listen (7634466447860989102) -->
+    <skip />
+    <!-- no translation found for tooltip_translate (4184845868901542567) -->
+    <skip />
 </resources>
diff --git a/lint-baseline-launcher3.xml b/lint-baseline-launcher3.xml
index 469ad94..e77c889 100644
--- a/lint-baseline-launcher3.xml
+++ b/lint-baseline-launcher3.xml
@@ -573,4 +573,15 @@
             column="42"/>
     </issue>
 
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 26): `android.appwidget.AppWidgetHostView#setColorResources`"
+        errorLine1="                setColorResources(mWallpaperColorResources);"
+        errorLine2="                ~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/Launcher3/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java"
+            line="528"
+            column="17"/>
+    </issue>
+
 </issues>
diff --git a/quickstep/res/drawable/ic_sysbar_accessibility_button.xml b/quickstep/res/drawable/ic_sysbar_accessibility_button.xml
new file mode 100644
index 0000000..e0d5406
--- /dev/null
+++ b/quickstep/res/drawable/ic_sysbar_accessibility_button.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="21dp"
+    android:height="21dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:pathData="M20.5,6c-2.61,0.7 -5.67,1 -8.5,1S6.11,6.7 3.5,6L3,8c1.86,0.5 4,0.83 6,1v13h2v-6h2v6h2V9c2,-0.17 4.14,-0.5 6,-1L20.5,6zM12,6c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2s-2,0.9 -2,2S10.9,6 12,6z"
+        android:fillColor="@android:color/white"
+        />
+</vector>
\ No newline at end of file
diff --git a/quickstep/res/drawable/ic_sysbar_rotate_button_ccw_start_0.xml b/quickstep/res/drawable/ic_sysbar_rotate_button_ccw_start_0.xml
new file mode 100644
index 0000000..ff5cb9e
--- /dev/null
+++ b/quickstep/res/drawable/ic_sysbar_rotate_button_ccw_start_0.xml
@@ -0,0 +1,187 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2020 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.
+-->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt">
+    <aapt:attr name="android:drawable">
+        <vector android:name="root"
+                android:width="28dp"
+                android:height="28dp"
+                android:viewportWidth="28.0"
+                android:viewportHeight="28.0">
+            <!-- Use scaleX to flip icon so arrows always point in the direction of motion -->
+            <group android:name="icon" android:pivotX="14" android:pivotY="14"
+                   android:scaleX="1">
+                <!-- Tint color to be set directly -->
+                <path android:fillColor="#FFFFFFFF"
+                      android:pathData="M12.02,10.83L9.25,8.06l2.77,-2.77l1.12,1.12l-0.85,0.86h5.16c0.72,0 1.31,0.56 1.31,1.26v9.16l-1.58,-1.58V8.85h-4.89l0.86,0.86L12.02,10.83zM15.98,17.17l-1.12,1.12l0.85,0.86h-4.88v-7.26L9.25,10.3v9.17c0,0.7 0.59,1.26 1.31,1.26h5.16v0.01l-0.85,0.85l1.12,1.12l2.77,-2.77L15.98,17.17z"/>
+            </group>
+        </vector>
+    </aapt:attr>
+
+    <!-- Repeat all animations 5 times but don't fade out at the end -->
+    <target android:name="root">
+        <aapt:attr name="android:animation">
+            <set android:ordering="sequentially">
+                <!-- Linear fade in-->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:valueFrom="0"
+                                android:valueTo="1"
+                                android:interpolator="@android:anim/linear_interpolator" />
+                <!-- Linear fade out -->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="1700"
+                                android:valueFrom="1"
+                                android:valueTo="0"
+                                android:interpolator="@android:anim/linear_interpolator"/>
+                <!-- Linear fade in-->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="100"
+                                android:valueFrom="0"
+                                android:valueTo="1"
+                                android:interpolator="@android:anim/linear_interpolator" />
+                <!-- Linear fade out -->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="1700"
+                                android:valueFrom="1"
+                                android:valueTo="0"
+                                android:interpolator="@android:anim/linear_interpolator"/>
+                <!-- Linear fade in-->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="100"
+                                android:valueFrom="0"
+                                android:valueTo="1"
+                                android:interpolator="@android:anim/linear_interpolator" />
+                <!-- Linear fade out -->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="1700"
+                                android:valueFrom="1"
+                                android:valueTo="0"
+                                android:interpolator="@android:anim/linear_interpolator"/>
+                <!-- Linear fade in-->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="100"
+                                android:valueFrom="0"
+                                android:valueTo="1"
+                                android:interpolator="@android:anim/linear_interpolator" />
+                <!-- Linear fade out -->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="1700"
+                                android:valueFrom="1"
+                                android:valueTo="0"
+                                android:interpolator="@android:anim/linear_interpolator"/>
+                <!-- Linear fade in-->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="100"
+                                android:valueFrom="0"
+                                android:valueTo="1"
+                                android:interpolator="@android:anim/linear_interpolator" />
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="icon">
+        <aapt:attr name="android:animation">
+            <set android:ordering="sequentially">
+                <!-- Icon rotation with start timing offset after fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:startOffset="100"
+                                android:duration="600"
+                                android:valueFrom="0"
+                                android:valueTo="-90">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+
+                <!-- Reset rotation position for fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:startOffset="1300"
+                                android:duration="100"
+                                android:valueFrom="0"
+                                android:valueTo="0"/>
+
+                <!-- Icon rotation with start timing offset after fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:duration="600"
+                                android:valueFrom="0"
+                                android:valueTo="-90">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+
+                <!-- Reset rotation position for fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:startOffset="1300"
+                                android:duration="100"
+                                android:valueFrom="0"
+                                android:valueTo="0"/>
+
+                <!-- Icon rotation with start timing offset after fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:duration="600"
+                                android:valueFrom="0"
+                                android:valueTo="-90">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+
+                <!-- Reset rotation position for fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:startOffset="1300"
+                                android:duration="100"
+                                android:valueFrom="0"
+                                android:valueTo="0"/>
+
+                <!-- Icon rotation with start timing offset after fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:duration="600"
+                                android:valueFrom="0"
+                                android:valueTo="-90">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+
+                <!-- Reset rotation position for fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:startOffset="1300"
+                                android:duration="100"
+                                android:valueFrom="0"
+                                android:valueTo="0"/>
+
+                <!-- Icon rotation with start timing offset after fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:duration="600"
+                                android:valueFrom="0"
+                                android:valueTo="-90">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
\ No newline at end of file
diff --git a/quickstep/res/drawable/ic_sysbar_rotate_button_ccw_start_90.xml b/quickstep/res/drawable/ic_sysbar_rotate_button_ccw_start_90.xml
new file mode 100644
index 0000000..90fedb1
--- /dev/null
+++ b/quickstep/res/drawable/ic_sysbar_rotate_button_ccw_start_90.xml
@@ -0,0 +1,187 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2020 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.
+-->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt">
+    <aapt:attr name="android:drawable">
+        <vector android:name="root"
+                android:width="28dp"
+                android:height="28dp"
+                android:viewportWidth="28.0"
+                android:viewportHeight="28.0">
+            <!-- Use scaleX to flip icon so arrows always point in the direction of motion -->
+            <group android:name="icon" android:pivotX="14" android:pivotY="14"
+                   android:scaleX="1">
+                <!-- Tint color to be set directly -->
+                <path android:fillColor="#FFFFFFFF"
+                      android:pathData="M12.02,10.83L9.25,8.06l2.77,-2.77l1.12,1.12l-0.85,0.86h5.16c0.72,0 1.31,0.56 1.31,1.26v9.16l-1.58,-1.58V8.85h-4.89l0.86,0.86L12.02,10.83zM15.98,17.17l-1.12,1.12l0.85,0.86h-4.88v-7.26L9.25,10.3v9.17c0,0.7 0.59,1.26 1.31,1.26h5.16v0.01l-0.85,0.85l1.12,1.12l2.77,-2.77L15.98,17.17z"/>
+            </group>
+        </vector>
+    </aapt:attr>
+
+    <!-- Repeat all animations 5 times but don't fade out at the end -->
+    <target android:name="root">
+        <aapt:attr name="android:animation">
+            <set android:ordering="sequentially">
+                <!-- Linear fade in-->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:valueFrom="0"
+                                android:valueTo="1"
+                                android:interpolator="@android:anim/linear_interpolator" />
+                <!-- Linear fade out -->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="1700"
+                                android:valueFrom="1"
+                                android:valueTo="0"
+                                android:interpolator="@android:anim/linear_interpolator"/>
+                <!-- Linear fade in-->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="100"
+                                android:valueFrom="0"
+                                android:valueTo="1"
+                                android:interpolator="@android:anim/linear_interpolator" />
+                <!-- Linear fade out -->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="1700"
+                                android:valueFrom="1"
+                                android:valueTo="0"
+                                android:interpolator="@android:anim/linear_interpolator"/>
+                <!-- Linear fade in-->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="100"
+                                android:valueFrom="0"
+                                android:valueTo="1"
+                                android:interpolator="@android:anim/linear_interpolator" />
+                <!-- Linear fade out -->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="1700"
+                                android:valueFrom="1"
+                                android:valueTo="0"
+                                android:interpolator="@android:anim/linear_interpolator"/>
+                <!-- Linear fade in-->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="100"
+                                android:valueFrom="0"
+                                android:valueTo="1"
+                                android:interpolator="@android:anim/linear_interpolator" />
+                <!-- Linear fade out -->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="1700"
+                                android:valueFrom="1"
+                                android:valueTo="0"
+                                android:interpolator="@android:anim/linear_interpolator"/>
+                <!-- Linear fade in-->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="100"
+                                android:valueFrom="0"
+                                android:valueTo="1"
+                                android:interpolator="@android:anim/linear_interpolator" />
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="icon">
+        <aapt:attr name="android:animation">
+            <set android:ordering="sequentially">
+                <!-- Icon rotation with start timing offset after fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:startOffset="100"
+                                android:duration="600"
+                                android:valueFrom="90"
+                                android:valueTo="0">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+
+                <!-- Reset rotation position for fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:startOffset="1300"
+                                android:duration="100"
+                                android:valueFrom="90"
+                                android:valueTo="90"/>
+
+                <!-- Icon rotation with start timing offset after fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:duration="600"
+                                android:valueFrom="90"
+                                android:valueTo="0">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+
+                <!-- Reset rotation position for fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:startOffset="1300"
+                                android:duration="100"
+                                android:valueFrom="90"
+                                android:valueTo="90"/>
+
+                <!-- Icon rotation with start timing offset after fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:duration="600"
+                                android:valueFrom="90"
+                                android:valueTo="0">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+
+                <!-- Reset rotation position for fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:startOffset="1300"
+                                android:duration="100"
+                                android:valueFrom="90"
+                                android:valueTo="90"/>
+
+                <!-- Icon rotation with start timing offset after fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:duration="600"
+                                android:valueFrom="90"
+                                android:valueTo="0">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+
+                <!-- Reset rotation position for fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:startOffset="1300"
+                                android:duration="100"
+                                android:valueFrom="90"
+                                android:valueTo="90"/>
+
+                <!-- Icon rotation with start timing offset after fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:duration="600"
+                                android:valueFrom="90"
+                                android:valueTo="0">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
\ No newline at end of file
diff --git a/quickstep/res/drawable/ic_sysbar_rotate_button_cw_start_0.xml b/quickstep/res/drawable/ic_sysbar_rotate_button_cw_start_0.xml
new file mode 100644
index 0000000..a89e7a3
--- /dev/null
+++ b/quickstep/res/drawable/ic_sysbar_rotate_button_cw_start_0.xml
@@ -0,0 +1,187 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2020 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.
+-->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt">
+    <aapt:attr name="android:drawable">
+        <vector android:name="root"
+                android:width="28dp"
+                android:height="28dp"
+                android:viewportWidth="28.0"
+                android:viewportHeight="28.0">
+            <!-- Use scaleX to flip icon so arrows always point in the direction of motion -->
+            <group android:name="icon" android:pivotX="14" android:pivotY="14"
+                   android:scaleX="-1">
+                <!-- Tint color to be set directly -->
+                <path android:fillColor="#FFFFFFFF"
+                      android:pathData="M12.02,10.83L9.25,8.06l2.77,-2.77l1.12,1.12l-0.85,0.86h5.16c0.72,0 1.31,0.56 1.31,1.26v9.16l-1.58,-1.58V8.85h-4.89l0.86,0.86L12.02,10.83zM15.98,17.17l-1.12,1.12l0.85,0.86h-4.88v-7.26L9.25,10.3v9.17c0,0.7 0.59,1.26 1.31,1.26h5.16v0.01l-0.85,0.85l1.12,1.12l2.77,-2.77L15.98,17.17z"/>
+            </group>
+        </vector>
+    </aapt:attr>
+
+    <!-- Repeat all animations 5 times but don't fade out at the end -->
+    <target android:name="root">
+        <aapt:attr name="android:animation">
+            <set android:ordering="sequentially">
+                <!-- Linear fade in-->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:valueFrom="0"
+                                android:valueTo="1"
+                                android:interpolator="@android:anim/linear_interpolator" />
+                <!-- Linear fade out -->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="1700"
+                                android:valueFrom="1"
+                                android:valueTo="0"
+                                android:interpolator="@android:anim/linear_interpolator"/>
+                <!-- Linear fade in-->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="100"
+                                android:valueFrom="0"
+                                android:valueTo="1"
+                                android:interpolator="@android:anim/linear_interpolator" />
+                <!-- Linear fade out -->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="1700"
+                                android:valueFrom="1"
+                                android:valueTo="0"
+                                android:interpolator="@android:anim/linear_interpolator"/>
+                <!-- Linear fade in-->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="100"
+                                android:valueFrom="0"
+                                android:valueTo="1"
+                                android:interpolator="@android:anim/linear_interpolator" />
+                <!-- Linear fade out -->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="1700"
+                                android:valueFrom="1"
+                                android:valueTo="0"
+                                android:interpolator="@android:anim/linear_interpolator"/>
+                <!-- Linear fade in-->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="100"
+                                android:valueFrom="0"
+                                android:valueTo="1"
+                                android:interpolator="@android:anim/linear_interpolator" />
+                <!-- Linear fade out -->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="1700"
+                                android:valueFrom="1"
+                                android:valueTo="0"
+                                android:interpolator="@android:anim/linear_interpolator"/>
+                <!-- Linear fade in-->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="100"
+                                android:valueFrom="0"
+                                android:valueTo="1"
+                                android:interpolator="@android:anim/linear_interpolator" />
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="icon">
+        <aapt:attr name="android:animation">
+            <set android:ordering="sequentially">
+                <!-- Icon rotation with start timing offset after fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:startOffset="100"
+                                android:duration="600"
+                                android:valueFrom="0"
+                                android:valueTo="90">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+
+                <!-- Reset rotation position for fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:startOffset="1300"
+                                android:duration="100"
+                                android:valueFrom="0"
+                                android:valueTo="0"/>
+
+                <!-- Icon rotation with start timing offset after fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:duration="600"
+                                android:valueFrom="0"
+                                android:valueTo="90">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+
+                <!-- Reset rotation position for fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:startOffset="1300"
+                                android:duration="100"
+                                android:valueFrom="0"
+                                android:valueTo="0"/>
+
+                <!-- Icon rotation with start timing offset after fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:duration="600"
+                                android:valueFrom="0"
+                                android:valueTo="90">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+
+                <!-- Reset rotation position for fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:startOffset="1300"
+                                android:duration="100"
+                                android:valueFrom="0"
+                                android:valueTo="0"/>
+
+                <!-- Icon rotation with start timing offset after fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:duration="600"
+                                android:valueFrom="0"
+                                android:valueTo="90">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+
+                <!-- Reset rotation position for fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:startOffset="1300"
+                                android:duration="100"
+                                android:valueFrom="0"
+                                android:valueTo="0"/>
+
+                <!-- Icon rotation with start timing offset after fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:duration="600"
+                                android:valueFrom="0"
+                                android:valueTo="90">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
\ No newline at end of file
diff --git a/quickstep/res/drawable/ic_sysbar_rotate_button_cw_start_90.xml b/quickstep/res/drawable/ic_sysbar_rotate_button_cw_start_90.xml
new file mode 100644
index 0000000..0dc67b0
--- /dev/null
+++ b/quickstep/res/drawable/ic_sysbar_rotate_button_cw_start_90.xml
@@ -0,0 +1,187 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2020 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.
+-->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt">
+    <aapt:attr name="android:drawable">
+        <vector android:name="root"
+                android:width="28dp"
+                android:height="28dp"
+                android:viewportWidth="28.0"
+                android:viewportHeight="28.0">
+            <!-- Use scaleX to flip icon so arrows always point in the direction of motion -->
+            <group android:name="icon" android:pivotX="14" android:pivotY="14"
+                   android:scaleX="-1">
+                <!-- Tint color to be set directly -->
+                <path android:fillColor="#FFFFFFFF"
+                      android:pathData="M12.02,10.83L9.25,8.06l2.77,-2.77l1.12,1.12l-0.85,0.86h5.16c0.72,0 1.31,0.56 1.31,1.26v9.16l-1.58,-1.58V8.85h-4.89l0.86,0.86L12.02,10.83zM15.98,17.17l-1.12,1.12l0.85,0.86h-4.88v-7.26L9.25,10.3v9.17c0,0.7 0.59,1.26 1.31,1.26h5.16v0.01l-0.85,0.85l1.12,1.12l2.77,-2.77L15.98,17.17z"/>
+            </group>
+        </vector>
+    </aapt:attr>
+
+    <!-- Repeat all animations 5 times but don't fade out at the end -->
+    <target android:name="root">
+        <aapt:attr name="android:animation">
+            <set android:ordering="sequentially">
+                <!-- Linear fade in-->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:valueFrom="0"
+                                android:valueTo="1"
+                                android:interpolator="@android:anim/linear_interpolator" />
+                <!-- Linear fade out -->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="1700"
+                                android:valueFrom="1"
+                                android:valueTo="0"
+                                android:interpolator="@android:anim/linear_interpolator"/>
+                <!-- Linear fade in-->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="100"
+                                android:valueFrom="0"
+                                android:valueTo="1"
+                                android:interpolator="@android:anim/linear_interpolator" />
+                <!-- Linear fade out -->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="1700"
+                                android:valueFrom="1"
+                                android:valueTo="0"
+                                android:interpolator="@android:anim/linear_interpolator"/>
+                <!-- Linear fade in-->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="100"
+                                android:valueFrom="0"
+                                android:valueTo="1"
+                                android:interpolator="@android:anim/linear_interpolator" />
+                <!-- Linear fade out -->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="1700"
+                                android:valueFrom="1"
+                                android:valueTo="0"
+                                android:interpolator="@android:anim/linear_interpolator"/>
+                <!-- Linear fade in-->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="100"
+                                android:valueFrom="0"
+                                android:valueTo="1"
+                                android:interpolator="@android:anim/linear_interpolator" />
+                <!-- Linear fade out -->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="1700"
+                                android:valueFrom="1"
+                                android:valueTo="0"
+                                android:interpolator="@android:anim/linear_interpolator"/>
+                <!-- Linear fade in-->
+                <objectAnimator android:propertyName="alpha"
+                                android:duration="100"
+                                android:startOffset="100"
+                                android:valueFrom="0"
+                                android:valueTo="1"
+                                android:interpolator="@android:anim/linear_interpolator" />
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="icon">
+        <aapt:attr name="android:animation">
+            <set android:ordering="sequentially">
+                <!-- Icon rotation with start timing offset after fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:startOffset="100"
+                                android:duration="600"
+                                android:valueFrom="90"
+                                android:valueTo="180">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+
+                <!-- Reset rotation position for fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:startOffset="1300"
+                                android:duration="100"
+                                android:valueFrom="90"
+                                android:valueTo="90"/>
+
+                <!-- Icon rotation with start timing offset after fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:duration="600"
+                                android:valueFrom="90"
+                                android:valueTo="180">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+
+                <!-- Reset rotation position for fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:startOffset="1300"
+                                android:duration="100"
+                                android:valueFrom="90"
+                                android:valueTo="90"/>
+
+                <!-- Icon rotation with start timing offset after fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:duration="600"
+                                android:valueFrom="90"
+                                android:valueTo="180">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+
+                <!-- Reset rotation position for fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:startOffset="1300"
+                                android:duration="100"
+                                android:valueFrom="90"
+                                android:valueTo="90"/>
+
+                <!-- Icon rotation with start timing offset after fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:duration="600"
+                                android:valueFrom="90"
+                                android:valueTo="180">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+
+                <!-- Reset rotation position for fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:startOffset="1300"
+                                android:duration="100"
+                                android:valueFrom="90"
+                                android:valueTo="90"/>
+
+                <!-- Icon rotation with start timing offset after fade in -->
+                <objectAnimator android:propertyName="rotation"
+                                android:duration="600"
+                                android:valueFrom="90"
+                                android:valueTo="180">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
\ No newline at end of file
diff --git a/quickstep/res/layout/taskbar.xml b/quickstep/res/layout/taskbar.xml
index e680233..dfa17d6 100644
--- a/quickstep/res/layout/taskbar.xml
+++ b/quickstep/res/layout/taskbar.xml
@@ -13,12 +13,13 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-
 <com.android.launcher3.taskbar.TaskbarDragLayer
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     android:id="@+id/taskbar_container"
     android:layout_width="wrap_content"
-    android:layout_height="wrap_content">
+    android:layout_height="wrap_content"
+    android:clipChildren="false">
 
     <com.android.launcher3.taskbar.TaskbarView
         android:id="@+id/taskbar_view"
@@ -26,30 +27,43 @@
         android:layout_height="wrap_content"
         android:gravity="center"
         android:forceHasOverlappingRendering="false"
+        android:layout_gravity="bottom"
+        android:clipChildren="false" />
+
+    <FrameLayout
+        android:id="@+id/navbuttons_view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
         android:layout_gravity="bottom" >
 
         <LinearLayout
-            android:id="@+id/system_button_layout"
+            android:id="@+id/start_nav_buttons"
             android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
+            android:layout_height="match_parent"
+            android:orientation="horizontal"
             android:paddingLeft="@dimen/taskbar_nav_buttons_spacing"
             android:paddingRight="@dimen/taskbar_nav_buttons_spacing"
-            android:forceHasOverlappingRendering="false"
-            android:gravity="center" />
+            android:gravity="center_vertical"
+            android:layout_gravity="start"/>
 
-        <LinearLayout
-            android:id="@+id/hotseat_icons_layout"
+        <FrameLayout
+            android:id="@+id/end_nav_buttons"
             android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:forceHasOverlappingRendering="false"
-            android:gravity="center" />
+            android:layout_height="match_parent"
+            android:paddingLeft="@dimen/taskbar_nav_buttons_spacing"
+            android:paddingRight="@dimen/taskbar_nav_buttons_spacing"
+            android:gravity="center_vertical"
+            android:layout_gravity="end"/>
+    </FrameLayout>
 
-    </com.android.launcher3.taskbar.TaskbarView>
-
-    <com.android.launcher3.taskbar.ImeBarView
-        android:id="@+id/ime_bar_view"
-        android:layout_width="wrap_content"
+    <View
+        android:id="@+id/stashed_handle"
+        tools:comment1="The actual size and shape will be set as a ViewOutlineProvider at runtime"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:visibility="gone"/>
+        tools:comment2="TODO: Tint dynamically"
+        android:background="?android:attr/textColorPrimary"
+        android:clipToOutline="true"
+        android:layout_gravity="bottom"/>
 
 </com.android.launcher3.taskbar.TaskbarDragLayer>
\ No newline at end of file
diff --git a/quickstep/res/layout/taskbar_nav_button.xml b/quickstep/res/layout/taskbar_nav_button.xml
new file mode 100644
index 0000000..985f928
--- /dev/null
+++ b/quickstep/res/layout/taskbar_nav_button.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ImageView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/taskbar_nav_buttons_size"
+    android:layout_height="@dimen/taskbar_nav_buttons_size"
+    android:background="@drawable/taskbar_icon_click_feedback_roundrect"
+    android:scaleType="center"/>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 6cc64e0..1febc88 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -16,7 +16,7 @@
 
 <resources>
     <dimen name="task_thumbnail_icon_size">48dp</dimen>
-    <dimen name="task_thumbnail_icon_size_grid">32dp</dimen>
+    <dimen name="task_thumbnail_icon_size_grid">40dp</dimen>
     <!-- For screens without rounded corners -->
     <dimen name="task_corner_radius_small">2dp</dimen>
     <!-- For Launchers that want to override the default dialog corner radius -->
@@ -39,10 +39,10 @@
     <dimen name="overview_actions_horizontal_margin">16dp</dimen>
 
     <dimen name="overview_grid_top_margin">77dp</dimen>
-    <dimen name="overview_grid_bottom_margin">90dp</dimen>
+    <dimen name="overview_grid_bottom_margin">70dp</dimen>
     <dimen name="overview_grid_side_margin">54dp</dimen>
     <dimen name="overview_grid_row_spacing">42dp</dimen>
-    <dimen name="overview_grid_focus_vertical_margin">90dp</dimen>
+    <dimen name="overview_grid_focus_vertical_margin">40dp</dimen>
     <dimen name="split_placeholder_size">110dp</dimen>
 
     <!-- These speeds are in dp/s -->
@@ -150,11 +150,12 @@
 
     <!-- Taskbar -->
     <dimen name="taskbar_size">60dp</dimen>
-    <dimen name="taskbar_icon_size">44dp</dimen>
     <dimen name="taskbar_icon_touch_size">48dp</dimen>
     <dimen name="taskbar_icon_drag_icon_size">54dp</dimen>
-    <!-- Note that this applies to both sides of all icons, so visible space is double this. -->
-    <dimen name="taskbar_icon_spacing">8dp</dimen>
     <dimen name="taskbar_folder_margin">16dp</dimen>
     <dimen name="taskbar_nav_buttons_spacing">16dp</dimen>
+    <dimen name="taskbar_nav_buttons_size">48dp</dimen>
+    <dimen name="taskbar_stashed_size">24dp</dimen>
+    <dimen name="taskbar_stashed_handle_width">220dp</dimen>
+    <dimen name="taskbar_stashed_handle_height">6dp</dimen>
 </resources>
diff --git a/quickstep/robolectric_tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java b/quickstep/robolectric_tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
index 7c97b93..1386ac0 100644
--- a/quickstep/robolectric_tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
+++ b/quickstep/robolectric_tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
@@ -41,19 +41,11 @@
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
 import com.android.launcher3.model.QuickstepModelDelegate.PredictorState;
-import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.LauncherAppWidgetInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.shadows.ShadowDeviceFlag;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.LauncherModelHelper;
-import com.android.launcher3.util.ViewOnDrawExecutor;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
-import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -67,9 +59,6 @@
 import org.robolectric.shadows.ShadowPackageManager;
 import org.robolectric.util.ReflectionHelpers;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.stream.Collectors;
 
@@ -239,63 +228,8 @@
         }
 
         @Override
-        public int getPageToBindSynchronously() {
-            return 0;
+        public IntSet getPagesToBindSynchronously() {
+            return IntSet.wrap(0);
         }
-
-        @Override
-        public void clearPendingBinds() { }
-
-        @Override
-        public void startBinding() { }
-
-        @Override
-        public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) { }
-
-        @Override
-        public void bindScreens(IntArray orderedScreenIds) { }
-
-        @Override
-        public void finishFirstPageBind(ViewOnDrawExecutor executor) { }
-
-        @Override
-        public void finishBindingItems(int pageBoundFirst) { }
-
-        @Override
-        public void preAddApps() { }
-
-        @Override
-        public void bindAppsAdded(IntArray newScreens, ArrayList<ItemInfo> addNotAnimated,
-                ArrayList<ItemInfo> addAnimated) { }
-
-        @Override
-        public void bindIncrementalDownloadProgressUpdated(AppInfo app) { }
-
-        @Override
-        public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) { }
-
-        @Override
-        public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets) { }
-
-        @Override
-        public void bindRestoreItemsChange(HashSet<ItemInfo> updates) { }
-
-        @Override
-        public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher) { }
-
-        @Override
-        public void bindAllWidgets(List<WidgetsListBaseEntry> widgets) { }
-
-        @Override
-        public void onPageBoundSynchronously(int page) { }
-
-        @Override
-        public void executeOnNextDraw(ViewOnDrawExecutor executor) { }
-
-        @Override
-        public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap) { }
-
-        @Override
-        public void bindAllApplications(AppInfo[] apps, int flags) { }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 32e2f1a..ecd38b4 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.LauncherState.NO_OFFSET;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
+import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
@@ -30,12 +31,15 @@
 import android.animation.ValueAnimator;
 import android.app.ActivityOptions;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
 import android.content.ServiceConnection;
 import android.os.Bundle;
 import android.os.CancellationSignal;
+import android.os.Handler;
 import android.os.IBinder;
+import android.util.Log;
 import android.view.View;
 import android.window.SplashScreen;
 
@@ -56,6 +60,8 @@
 import com.android.launcher3.taskbar.TaskbarStateHandler;
 import com.android.launcher3.uioverrides.RecentsViewStateController;
 import com.android.launcher3.util.ActivityOptionsWrapper;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.ObjectWrapper;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.quickstep.RecentsModel;
@@ -85,6 +91,11 @@
 public abstract class BaseQuickstepLauncher extends Launcher
         implements NavigationModeChangeListener {
 
+    private static final long BACKOFF_MILLIS = 1000;
+
+    // Max backoff caps at 5 mins
+    private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000;
+
     private DepthController mDepthController = new DepthController(this);
     private QuickstepTransitionManager mAppTransitionManager;
 
@@ -104,12 +115,24 @@
         public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
             mTaskbarManager = ((TISBinder) iBinder).getTaskbarManager();
             mTaskbarManager.setLauncher(BaseQuickstepLauncher.this);
+            Log.d(TAG, "TIS service connected");
+            resetServiceBindRetryState();
         }
 
         @Override
         public void onServiceDisconnected(ComponentName componentName) { }
+
+        @Override
+        public void onBindingDied(ComponentName name) {
+            Log.w(TAG, "TIS binding died");
+            internalBindToTIS();
+        }
     };
+
+    private final Runnable mConnectionRunnable = this::internalBindToTIS;
+    private short mConnectionAttempts;
     private final TaskbarStateHandler mTaskbarStateHandler = new TaskbarStateHandler(this);
+    private final Handler mHandler = new Handler();
 
     // Will be updated when dragging from taskbar.
     private @Nullable DragOptions mNextWorkspaceDragOptions = null;
@@ -128,11 +151,11 @@
 
         SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this);
 
-
         unbindService(mTisBinderConnection);
         if (mTaskbarManager != null) {
-            mTaskbarManager.setLauncher(null);
+            mTaskbarManager.clearLauncher(this);
         }
+        resetServiceBindRetryState();
         super.onDestroy();
     }
 
@@ -260,8 +283,33 @@
         mAppTransitionManager = new QuickstepTransitionManager(this);
         mAppTransitionManager.registerRemoteAnimations();
 
-        bindService(new Intent(this, TouchInteractionService.class), mTisBinderConnection, 0);
+        internalBindToTIS();
+    }
 
+    /**
+     * Binds {@link #mTisBinderConnection} to {@link TouchInteractionService}. If the binding fails,
+     * attempts to retry via {@link #mConnectionRunnable}
+     */
+    private void internalBindToTIS() {
+        boolean bound = bindService(new Intent(this, TouchInteractionService.class),
+                        mTisBinderConnection, 0);
+        if (bound) {
+            resetServiceBindRetryState();
+            return;
+        }
+
+        Log.w(TAG, "Retrying TIS Binder connection attempt: " + mConnectionAttempts);
+        final long timeoutMs = (long) Math.min(
+                Math.scalb(BACKOFF_MILLIS, mConnectionAttempts), MAX_BACKOFF_MILLIS);
+        mHandler.postDelayed(mConnectionRunnable, timeoutMs);
+        mConnectionAttempts++;
+    }
+
+    private void resetServiceBindRetryState() {
+        if (mHandler.hasCallbacks(mConnectionRunnable)) {
+            mHandler.removeCallbacks(mConnectionRunnable);
+        }
+        mConnectionAttempts = 0;
     }
 
     public void setTaskbarUIController(LauncherTaskbarUIController taskbarUIController) {
@@ -351,14 +399,6 @@
     }
 
     @Override
-    public float getNormalTaskbarScale() {
-        if (mTaskbarUIController != null) {
-            return mTaskbarUIController.getTaskbarScaleOnHome();
-        }
-        return super.getNormalTaskbarScale();
-    }
-
-    @Override
     public void onDragLayerHierarchyChanged() {
         onLauncherStateOrFocusChanged();
     }
@@ -412,8 +452,8 @@
     }
 
     @Override
-    public void finishBindingItems(int pageBoundFirst) {
-        super.finishBindingItems(pageBoundFirst);
+    public void finishBindingItems(IntSet pagesBoundFirst) {
+        super.finishBindingItems(pagesBoundFirst);
         // Instantiate and initialize WellbeingModel now that its loading won't interfere with
         // populating workspace.
         // TODO: Find a better place for this
@@ -481,4 +521,15 @@
     public void setHintUserWillBeActive() {
         addActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE);
     }
+
+    @Override
+    public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
+        super.onDisplayInfoChanged(context, info, flags);
+        // When changing screens with live tile active, finish the recents animation to close
+        // overview as it should be an interim state
+        if ((flags & CHANGE_ACTIVE_SCREEN) != 0 && ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            RecentsView recentsView = getOverviewPanel();
+            recentsView.finishRecentsAnimation(/* toRecents= */ true, null);
+        }
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 5bb76d6..ebf8fb7 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -1088,7 +1088,15 @@
     }
 
     private boolean launcherIsATargetWithMode(RemoteAnimationTargetCompat[] targets, int mode) {
-        return taskIsATargetWithMode(targets, mLauncher.getTaskId(), mode);
+        for (RemoteAnimationTargetCompat target : targets) {
+            if (target.mode == mode && target.taskInfo != null
+                    // Compare component name instead of task-id because transitions will promote
+                    // the target up to the root task while getTaskId returns the leaf.
+                    && target.taskInfo.topActivity.equals(mLauncher.getComponentName())) {
+                return true;
+            }
+        }
+        return false;
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
index 14b0c5d..c7c2567 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
@@ -85,7 +85,7 @@
         mSampleHotseat = findViewById(R.id.sample_prediction);
 
         DeviceProfile grid = mActivityContext.getDeviceProfile();
-        Rect padding = grid.getHotseatLayoutPadding();
+        Rect padding = grid.getHotseatLayoutPadding(getContext());
 
         mSampleHotseat.getLayoutParams().height = grid.cellHeightPx;
         mSampleHotseat.setGridSize(grid.numShownHotseatIcons, 1);
diff --git a/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java b/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java
deleted file mode 100644
index 540f748..0000000
--- a/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.taskbar;
-
-import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK;
-import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME;
-import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH;
-import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS;
-
-import android.annotation.DrawableRes;
-import android.view.View;
-import android.widget.ImageView;
-
-import com.android.launcher3.R;
-import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
-
-/**
- * Creates Buttons for Taskbar for 3 button nav.
- * Can add animations and state management for buttons in this class as things progress.
- */
-public class ButtonProvider {
-
-    private final int mMarginLeftRight;
-    private final TaskbarActivityContext mContext;
-
-    public ButtonProvider(TaskbarActivityContext context) {
-        mContext = context;
-        mMarginLeftRight = context.getResources()
-                .getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
-    }
-
-    public View getBack() {
-        // Back button
-        return getButtonForDrawable(R.drawable.ic_sysbar_back, BUTTON_BACK);
-    }
-
-    public View getDown() {
-        // Ime down button
-        return getButtonForDrawable(R.drawable.ic_sysbar_back, BUTTON_BACK);
-    }
-
-    public View getHome() {
-        // Home button
-        return getButtonForDrawable(R.drawable.ic_sysbar_home, BUTTON_HOME);
-    }
-
-    public View getRecents() {
-        // Recents button
-        return getButtonForDrawable(R.drawable.ic_sysbar_recent, BUTTON_RECENTS);
-    }
-
-    public View getImeSwitcher() {
-        // IME Switcher Button
-        return getButtonForDrawable(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH);
-    }
-
-    private View getButtonForDrawable(@DrawableRes int drawableId, @TaskbarButton int buttonType) {
-        ImageView buttonView = new ImageView(mContext);
-        buttonView.setImageResource(drawableId);
-        buttonView.setBackgroundResource(R.drawable.taskbar_icon_click_feedback_roundrect);
-        buttonView.setPadding(mMarginLeftRight, 0, mMarginLeftRight, 0);
-        buttonView.setOnClickListener(view -> mContext.onNavigationButtonClick(buttonType));
-        return buttonView;
-    }
-
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java b/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java
deleted file mode 100644
index 287caab..0000000
--- a/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.taskbar;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.RelativeLayout;
-
-import com.android.launcher3.views.ActivityContext;
-
-public class ImeBarView extends RelativeLayout {
-
-    private ButtonProvider mButtonProvider;
-    private View mImeView;
-
-    public ImeBarView(Context context) {
-        this(context, null);
-    }
-
-    public ImeBarView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public ImeBarView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    public void init(ButtonProvider buttonProvider) {
-        mButtonProvider = buttonProvider;
-
-        ActivityContext context = getActivityContext();
-        RelativeLayout.LayoutParams imeParams = new RelativeLayout.LayoutParams(
-                context.getDeviceProfile().iconSizePx,
-                context.getDeviceProfile().iconSizePx
-        );
-        RelativeLayout.LayoutParams downParams = new RelativeLayout.LayoutParams(imeParams);
-
-        imeParams.addRule(ALIGN_PARENT_END);
-        imeParams.setMarginEnd(context.getDeviceProfile().iconSizePx);
-        downParams.setMarginStart(context.getDeviceProfile().iconSizePx);
-        downParams.addRule(ALIGN_PARENT_START);
-
-        // Down Arrow
-        View downView = mButtonProvider.getDown();
-        downView.setLayoutParams(downParams);
-        downView.setRotation(-90);
-        addView(downView);
-
-        // IME switcher button
-        mImeView = mButtonProvider.getImeSwitcher();
-        mImeView.setLayoutParams(imeParams);
-        addView(mImeView);
-    }
-
-    public void setImeSwitcherVisibility(boolean show) {
-        mImeView.setVisibility(show ? VISIBLE : GONE);
-    }
-
-    private <T extends Context & ActivityContext> T getActivityContext() {
-        return ActivityContext.lookupContext(getContext());
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index c2d107c..06fd660 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -15,42 +15,58 @@
  */
 package com.android.launcher3.taskbar;
 
+import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
+import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_HOME;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.view.MotionEvent;
 
-import androidx.annotation.Nullable;
+import androidx.annotation.NonNull;
 
 import com.android.launcher3.BaseQuickstepLauncher;
-import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.QuickstepTransitionManager;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.anim.AnimatorListeners;
+import com.android.launcher3.util.MultiValueAlpha;
+import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
+import com.android.quickstep.AnimatedFloat;
+import com.android.quickstep.RecentsAnimationCallbacks;
+import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
+import com.android.quickstep.RecentsAnimationController;
+import com.android.systemui.shared.recents.model.ThumbnailData;
 
 
 /**
  * A data source which integrates with a Launcher instance
- * TODO: Rename to have Launcher prefix
  */
-
 public class LauncherTaskbarUIController extends TaskbarUIController {
 
     private final BaseQuickstepLauncher mLauncher;
     private final TaskbarStateHandler mTaskbarStateHandler;
-    private final TaskbarAnimationController mTaskbarAnimationController;
     private final TaskbarHotseatController mHotseatController;
 
     private final TaskbarActivityContext mContext;
-    final TaskbarDragLayer mTaskbarDragLayer;
-    final TaskbarView mTaskbarView;
+    private final TaskbarDragLayer mTaskbarDragLayer;
+    private final TaskbarView mTaskbarView;
 
-    private @Nullable Animator mAnimator;
-    private boolean mIsAnimatingToLauncher;
+    private final AnimatedFloat mIconAlignmentForResumedState =
+            new AnimatedFloat(this::onIconAlignmentRatioChanged);
+    private final AnimatedFloat mIconAlignmentForGestureState =
+            new AnimatedFloat(this::onIconAlignmentRatioChanged);
+
+    // Initialized in init.
+    private TaskbarControllers mControllers;
+    private AnimatedFloat mTaskbarBackgroundAlpha;
+    private AlphaProperty mIconAlphaForHome;
+    private boolean mIsAnimatingToLauncherViaResume;
+    private boolean mIsAnimatingToLauncherViaGesture;
+    private TaskbarKeyguardController mKeyguardController;
+
+    private LauncherState mTargetStateOverride = null;
 
     public LauncherTaskbarUIController(
             BaseQuickstepLauncher launcher, TaskbarActivityContext context) {
@@ -60,150 +76,168 @@
 
         mLauncher = launcher;
         mTaskbarStateHandler = mLauncher.getTaskbarStateHandler();
-        mTaskbarAnimationController = new TaskbarAnimationController(mLauncher,
-                createTaskbarAnimationControllerCallbacks());
         mHotseatController = new TaskbarHotseatController(
                 mLauncher, mTaskbarView::updateHotseatItems);
     }
 
     @Override
-    protected void onCreate() {
-        mTaskbarStateHandler.setAnimationController(mTaskbarAnimationController);
-        mTaskbarAnimationController.init();
+    protected void init(TaskbarControllers taskbarControllers) {
+        mControllers = taskbarControllers;
+
+        mTaskbarBackgroundAlpha = mControllers.taskbarDragLayerController
+                .getTaskbarBackgroundAlpha();
+
+        MultiValueAlpha taskbarIconAlpha = mControllers.taskbarViewController.getTaskbarIconAlpha();
+        mIconAlphaForHome = taskbarIconAlpha.getProperty(ALPHA_INDEX_HOME);
+
         mHotseatController.init();
-        setTaskbarViewVisible(!mLauncher.hasBeenResumed());
-        alignRealHotseatWithTaskbar();
         mLauncher.setTaskbarUIController(this);
+        mKeyguardController = taskbarControllers.taskbarKeyguardController;
+
+        onLauncherResumedOrPaused(mLauncher.hasBeenResumed());
+        mIconAlignmentForResumedState.finishAnimation();
+        onIconAlignmentRatioChanged();
     }
 
     @Override
     protected void onDestroy() {
-        if (mAnimator != null) {
-            // End this first, in case it relies on properties that are about to be cleaned up.
-            mAnimator.end();
-        }
-        mTaskbarStateHandler.setAnimationController(null);
-        mTaskbarAnimationController.cleanup();
+        onLauncherResumedOrPaused(false);
+        mIconAlignmentForResumedState.finishAnimation();
+        mIconAlignmentForGestureState.finishAnimation();
+
         mHotseatController.cleanup();
-        setTaskbarViewVisible(true);
         mLauncher.getHotseat().setIconsAlpha(1f);
         mLauncher.setTaskbarUIController(null);
     }
 
     @Override
     protected boolean isTaskbarTouchable() {
-        return !mIsAnimatingToLauncher;
+        return !isAnimatingToLauncher() && !mControllers.taskbarStashController.isStashed();
     }
 
-    private TaskbarAnimationControllerCallbacks createTaskbarAnimationControllerCallbacks() {
-        return new TaskbarAnimationControllerCallbacks() {
-            @Override
-            public void updateTaskbarBackgroundAlpha(float alpha) {
-                mTaskbarDragLayer.setTaskbarBackgroundAlpha(alpha);
-            }
+    private boolean isAnimatingToLauncher() {
+        return mIsAnimatingToLauncherViaResume || mIsAnimatingToLauncherViaGesture;
+    }
 
-            @Override
-            public void updateTaskbarVisibilityAlpha(float alpha) {
-                mTaskbarView.setAlpha(alpha);
-            }
-
-            @Override
-            public void updateImeBarVisibilityAlpha(float alpha) {
-                mTaskbarDragLayer.updateImeBarVisibilityAlpha(alpha);
-            }
-
-            @Override
-            public void updateTaskbarScale(float scale) {
-                mTaskbarView.setScaleX(scale);
-                mTaskbarView.setScaleY(scale);
-            }
-
-            @Override
-            public void updateTaskbarTranslationY(float translationY) {
-                if (translationY < 0) {
-                    // Resize to accommodate the max translation we'll reach.
-                    mContext.setTaskbarWindowHeight(mContext.getDeviceProfile().taskbarSize
-                            + mLauncher.getHotseat().getTaskbarOffsetY());
-                } else {
-                    mContext.setTaskbarWindowHeight(mContext.getDeviceProfile().taskbarSize);
-                }
-                mTaskbarView.setTranslationY(translationY);
-            }
-        };
+    @Override
+    protected void updateContentInsets(Rect outContentInsets) {
+        int contentHeight = mControllers.taskbarStashController.getContentHeight();
+        outContentInsets.top = mTaskbarDragLayer.getHeight() - contentHeight;
     }
 
     /**
      * Should be called from onResume() and onPause(), and animates the Taskbar accordingly.
      */
     public void onLauncherResumedOrPaused(boolean isResumed) {
-        long duration = QuickstepTransitionManager.CONTENT_ALPHA_DURATION;
-        if (mAnimator != null) {
-            mAnimator.cancel();
-        }
-        if (isResumed) {
-            mAnimator = createAnimToLauncher(null, duration);
-        } else {
-            mAnimator = createAnimToApp(duration);
-        }
-        mAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mAnimator = null;
+        if (mKeyguardController.isScreenOff()) {
+            if (!isResumed) {
+                return;
+            } else {
+                // Resuming implicitly means device unlocked
+                mKeyguardController.setScreenOn();
             }
-        });
-        mAnimator.start();
+        }
+
+        long duration = QuickstepTransitionManager.CONTENT_ALPHA_DURATION;
+        ObjectAnimator anim = mIconAlignmentForResumedState.animateToValue(
+                getCurrentIconAlignmentRatio(), isResumed ? 1 : 0)
+                .setDuration(duration);
+
+        anim.addListener(AnimatorListeners.forEndCallback(
+                () -> mIsAnimatingToLauncherViaResume = false));
+        anim.start();
+        mIsAnimatingToLauncherViaResume = isResumed;
+
+        if (!isResumed) {
+            TaskbarStashController stashController = mControllers.taskbarStashController;
+            stashController.animateToIsStashed(stashController.isStashedInApp(), duration);
+        }
     }
 
     /**
-     * Create Taskbar animation when going from an app to Launcher.
+     * Create Taskbar animation when going from an app to Launcher as part of recents transition.
      * @param toState If known, the state we will end up in when reaching Launcher.
+     * @param callbacks callbacks to track the recents animation lifecycle. The state change is
+     *                 automatically reset once the recents animation finishes
      */
-    public Animator createAnimToLauncher(@Nullable LauncherState toState, long duration) {
-        PendingAnimation anim = new PendingAnimation(duration);
-        anim.add(mTaskbarAnimationController.createAnimToBackgroundAlpha(0, duration));
-        if (toState != null) {
-            mTaskbarStateHandler.setStateWithAnimation(toState, new StateAnimationConfig(), anim);
-        }
-
-        anim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationStart(Animator animation) {
-                mIsAnimatingToLauncher = true;
-                mTaskbarView.setHolesAllowedInLayout(true);
-                mTaskbarView.updateHotseatItemsVisibility();
-            }
-
+    public Animator createAnimToLauncher(@NonNull LauncherState toState,
+            @NonNull RecentsAnimationCallbacks callbacks,
+            long duration) {
+        TaskbarStashController stashController = mControllers.taskbarStashController;
+        ObjectAnimator animator = mIconAlignmentForGestureState
+                .animateToValue(1)
+                .setDuration(duration);
+        animator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
-                mIsAnimatingToLauncher = false;
-                setTaskbarViewVisible(false);
+                mTargetStateOverride = null;
+                animator.removeListener(this);
+            }
+
+            @Override
+            public void onAnimationStart(Animator animation) {
+                mTargetStateOverride = toState;
+                mIsAnimatingToLauncherViaGesture = true;
+                // TODO: base this on launcher state
+                stashController.animateToIsStashed(false, duration);
             }
         });
+        callbacks.addListener(new RecentsAnimationListener() {
+            @Override
+            public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
+                endGestureStateOverride(true);
+            }
 
-        return anim.buildAnim();
+            @Override
+            public void onRecentsAnimationFinished(RecentsAnimationController controller) {
+                endGestureStateOverride(!controller.getFinishTargetIsLauncher());
+            }
+
+            private void endGestureStateOverride(boolean finishedToApp) {
+                callbacks.removeListener(this);
+                mIsAnimatingToLauncherViaGesture = false;
+
+                mIconAlignmentForGestureState
+                        .animateToValue(0)
+                        .start();
+
+                if (finishedToApp) {
+                    // We only need this for the exiting live tile case.
+                    stashController.animateToIsStashed(stashController.isStashedInApp());
+                }
+            }
+        });
+        return animator;
     }
 
-    private Animator createAnimToApp(long duration) {
-        PendingAnimation anim = new PendingAnimation(duration);
-        anim.add(mTaskbarAnimationController.createAnimToBackgroundAlpha(1, duration));
-        anim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationStart(Animator animation) {
-                mTaskbarView.updateHotseatItemsVisibility();
-                setTaskbarViewVisible(true);
-            }
+    private float getCurrentIconAlignmentRatio() {
+        return  Math.max(mIconAlignmentForResumedState.value, mIconAlignmentForGestureState.value);
+    }
 
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mTaskbarView.setHolesAllowedInLayout(false);
-            }
-        });
-        return anim.buildAnim();
+    private void onIconAlignmentRatioChanged() {
+        if (mControllers == null) {
+            return;
+        }
+        float alignment = getCurrentIconAlignmentRatio();
+        mControllers.taskbarViewController.setLauncherIconAlignment(
+                alignment, mLauncher.getDeviceProfile());
+
+        mTaskbarBackgroundAlpha.updateValue(1 - alignment);
+
+        LauncherState state = mTargetStateOverride != null ? mTargetStateOverride
+                : mLauncher.getStateManager().getState();
+        if ((state.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
+            // If the hotseat icons are visible, then switch taskbar in last frame
+            setTaskbarViewVisible(alignment < 1);
+        } else {
+            mLauncher.getHotseat().setIconsAlpha(1);
+            mIconAlphaForHome.setValue(1 - alignment);
+        }
     }
 
     @Override
-    protected void onImeVisible(TaskbarDragLayer containerView, boolean isVisible) {
-        mTaskbarAnimationController.animateToVisibilityForIme(isVisible ? 0 : 1);
+    public boolean onLongPressToUnstashTaskbar() {
+        return mControllers.taskbarStashController.onLongPressToUnstashTaskbar();
     }
 
     /**
@@ -222,54 +256,11 @@
     }
 
     public boolean isDraggingItem() {
-        return mTaskbarView.isDraggingItem();
-    }
-
-    /**
-     * Pads the Hotseat to line up exactly with Taskbar's copy of the Hotseat.
-     */
-    @Override
-    public void alignRealHotseatWithTaskbar() {
-        Rect hotseatBounds = new Rect();
-        DeviceProfile grid = mLauncher.getDeviceProfile();
-        int hotseatHeight = grid.workspacePadding.bottom + grid.taskbarSize;
-        int taskbarOffset = mLauncher.getHotseat().getTaskbarOffsetY();
-        int hotseatTopDiff = hotseatHeight - grid.taskbarSize - taskbarOffset;
-        int hotseatBottomDiff = taskbarOffset;
-
-        RectF hotseatBoundsF = mTaskbarView.getHotseatBounds();
-        Utilities.scaleRectFAboutPivot(hotseatBoundsF, getTaskbarScaleOnHome(),
-                mTaskbarView.getPivotX(), mTaskbarView.getPivotY());
-        hotseatBoundsF.round(hotseatBounds);
-        mLauncher.getHotseat().setPadding(hotseatBounds.left,
-                hotseatBounds.top + hotseatTopDiff,
-                mTaskbarView.getWidth() - hotseatBounds.right,
-                mTaskbarView.getHeight() - hotseatBounds.bottom + hotseatBottomDiff);
-    }
-
-    /**
-     * Returns the ratio of the taskbar icon size on home vs in an app.
-     */
-    public float getTaskbarScaleOnHome() {
-        DeviceProfile inAppDp = mContext.getDeviceProfile();
-        DeviceProfile onHomeDp = mLauncher.getDeviceProfile();
-        return (float) onHomeDp.cellWidthPx / inAppDp.cellWidthPx;
+        return mContext.getDragController().isDragging();
     }
 
     void setTaskbarViewVisible(boolean isVisible) {
-        mTaskbarView.setIconsVisibility(isVisible);
+        mIconAlphaForHome.setValue(isVisible ? 1 : 0);
         mLauncher.getHotseat().setIconsAlpha(isVisible ? 0f : 1f);
     }
-
-    /**
-     * Contains methods that TaskbarAnimationController can call to interface with
-     * TaskbarController.
-     */
-    protected interface TaskbarAnimationControllerCallbacks {
-        void updateTaskbarBackgroundAlpha(float alpha);
-        void updateTaskbarVisibilityAlpha(float alpha);
-        void updateImeBarVisibilityAlpha(float alpha);
-        void updateTaskbarScale(float scale);
-        void updateTaskbarTranslationY(float translationY);
-    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
new file mode 100644
index 0000000..1edeba0
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -0,0 +1,392 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y;
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y_LONG_CLICK;
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK;
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME;
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH;
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS;
+import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_IME;
+import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_KEYGUARD;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
+
+import android.animation.ObjectAnimator;
+import android.annotation.DrawableRes;
+import android.annotation.IdRes;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.Region.Op;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.util.Property;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnHoverListener;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AlphaUpdateListener;
+import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
+import com.android.launcher3.taskbar.contextual.RotationButton;
+import com.android.launcher3.taskbar.contextual.RotationButtonController;
+import com.android.launcher3.util.MultiValueAlpha;
+import com.android.quickstep.AnimatedFloat;
+
+import java.util.ArrayList;
+import java.util.function.IntPredicate;
+
+/**
+ * Controller for managing nav bar buttons in taskbar
+ */
+public class NavbarButtonsViewController {
+
+    private final Rect mTempRect = new Rect();
+
+    private static final int FLAG_SWITCHER_SUPPORTED = 1 << 0;
+    private static final int FLAG_IME_VISIBLE = 1 << 1;
+    private static final int FLAG_ROTATION_BUTTON_VISIBLE = 1 << 2;
+    private static final int FLAG_A11Y_VISIBLE = 1 << 3;
+    private static final int FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE = 1 << 4;
+    private static final int FLAG_KEYGUARD_VISIBLE = 1 << 5;
+
+    private static final int MASK_IME_SWITCHER_VISIBLE = FLAG_SWITCHER_SUPPORTED | FLAG_IME_VISIBLE;
+
+    private View.OnLongClickListener mA11yLongClickListener;
+    private final ArrayList<StatePropertyHolder> mPropertyHolders = new ArrayList<>();
+    private final ArrayList<View> mAllButtons = new ArrayList<>();
+    private int mState;
+
+    private final TaskbarActivityContext mContext;
+    private final FrameLayout mNavButtonsView;
+    private final ViewGroup mStartContainer;
+    private final ViewGroup mEndContainer;
+
+    // Initialized in init.
+    private TaskbarControllers mControllers;
+    private View mA11yButton;
+    private int mSysuiStateFlags;
+    private View mBackButton;
+
+    public NavbarButtonsViewController(TaskbarActivityContext context, FrameLayout navButtonsView) {
+        mContext = context;
+        mNavButtonsView = navButtonsView;
+        mStartContainer = mNavButtonsView.findViewById(R.id.start_nav_buttons);
+        mEndContainer = mNavButtonsView.findViewById(R.id.end_nav_buttons);
+    }
+
+    /**
+     * Initializes the controller
+     */
+    public void init(TaskbarControllers controllers) {
+        mControllers = controllers;
+        mNavButtonsView.getLayoutParams().height = mContext.getDeviceProfile().taskbarSize;
+
+        mA11yLongClickListener = view -> {
+            mControllers.navButtonController.onButtonClick(BUTTON_A11Y_LONG_CLICK);
+            return true;
+        };
+
+        mPropertyHolders.add(new StatePropertyHolder(
+                mControllers.taskbarViewController.getTaskbarIconAlpha()
+                        .getProperty(ALPHA_INDEX_IME),
+                flags -> (flags & FLAG_IME_VISIBLE) == 0, MultiValueAlpha.VALUE, 1, 0));
+
+        // IME switcher
+        View imeSwitcherButton = addButton(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH,
+                mEndContainer, mControllers.navButtonController, R.id.ime_switcher);
+        mPropertyHolders.add(new StatePropertyHolder(imeSwitcherButton,
+                flags -> ((flags & MASK_IME_SWITCHER_VISIBLE) == MASK_IME_SWITCHER_VISIBLE)
+                        && ((flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0)
+                        && ((flags & FLAG_A11Y_VISIBLE) == 0)));
+
+        mBackButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK,
+                mStartContainer, mControllers.navButtonController, R.id.back);
+        // Rotate when Ime visible
+        mPropertyHolders.add(new StatePropertyHolder(mBackButton,
+                flags -> (flags & FLAG_IME_VISIBLE) == 0, View.ROTATION, 0,
+                Utilities.isRtl(mContext.getResources()) ? 90 : -90));
+
+        if (mContext.isThreeButtonNav()) {
+            initButtons(mStartContainer, mEndContainer, mControllers.navButtonController);
+
+            // Animate taskbar background when IME shows
+            mPropertyHolders.add(new StatePropertyHolder(
+                    mControllers.taskbarDragLayerController.getNavbarBackgroundAlpha(),
+                    flags -> (flags & FLAG_IME_VISIBLE) == 0,
+                    AnimatedFloat.VALUE, 0, 1));
+            mPropertyHolders.add(new StatePropertyHolder(
+                    mControllers.taskbarViewController.getTaskbarIconAlpha()
+                            .getProperty(ALPHA_INDEX_KEYGUARD),
+                    flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0, MultiValueAlpha.VALUE, 1, 0));
+
+            // Rotation button
+            RotationButton rotationButton = new RotationButtonImpl(
+                    addButton(mEndContainer, R.id.rotate_suggestion));
+            rotationButton.hide();
+            mControllers.rotationButtonController.setRotationButton(rotationButton);
+        } else {
+            mControllers.rotationButtonController.setRotationButton(new RotationButton() {});
+            // Show when IME is visible
+            mPropertyHolders.add(new StatePropertyHolder(mBackButton,
+                    flags -> (flags & FLAG_IME_VISIBLE) != 0));
+        }
+
+        applyState();
+        mPropertyHolders.forEach(StatePropertyHolder::endAnimation);
+    }
+
+    private void initButtons(ViewGroup startContainer, ViewGroup endContainer,
+            TaskbarNavButtonController navButtonController) {
+
+        // Hide when keyguard is showing, show when bouncer is showing
+        mPropertyHolders.add(new StatePropertyHolder(mBackButton,
+                flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 ||
+                        (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0));
+
+        // home and recents buttons
+        View homeButton = addButton(R.drawable.ic_sysbar_home, BUTTON_HOME, startContainer,
+                navButtonController, R.id.home);
+        mPropertyHolders.add(new StatePropertyHolder(homeButton,
+                flags -> (flags & FLAG_IME_VISIBLE) == 0 &&
+                        (flags & FLAG_KEYGUARD_VISIBLE) == 0));
+        View recentsButton = addButton(R.drawable.ic_sysbar_recent, BUTTON_RECENTS,
+                startContainer, navButtonController, R.id.recent_apps);
+        mPropertyHolders.add(new StatePropertyHolder(recentsButton,
+                flags -> (flags & FLAG_IME_VISIBLE) == 0 &&
+                        (flags & FLAG_KEYGUARD_VISIBLE) == 0));
+
+        // A11y button
+        mA11yButton = addButton(R.drawable.ic_sysbar_accessibility_button, BUTTON_A11Y,
+                endContainer, navButtonController, R.id.accessibility_button);
+        mPropertyHolders.add(new StatePropertyHolder(mA11yButton,
+                flags -> (flags & FLAG_A11Y_VISIBLE) != 0
+                        && (flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0));
+        mA11yButton.setOnLongClickListener(mA11yLongClickListener);
+    }
+
+    public void updateStateForSysuiFlags(int systemUiStateFlags, boolean forceUpdate) {
+        boolean isImeVisible = (systemUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0;
+        boolean isImeSwitcherShowing = (systemUiStateFlags & SYSUI_STATE_IME_SWITCHER_SHOWING) != 0;
+        boolean a11yVisible = (systemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
+        boolean a11yLongClickable =
+                (systemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
+
+        if (!forceUpdate && systemUiStateFlags == mSysuiStateFlags) {
+            return;
+        }
+        mSysuiStateFlags = systemUiStateFlags;
+
+        updateStateForFlag(FLAG_IME_VISIBLE, isImeVisible);
+        updateStateForFlag(FLAG_SWITCHER_SUPPORTED, isImeSwitcherShowing);
+        updateStateForFlag(FLAG_A11Y_VISIBLE, a11yVisible);
+        if (mA11yButton != null) {
+            // Only used in 3 button
+            mA11yButton.setLongClickable(a11yLongClickable);
+        }
+        applyState();
+    }
+
+    /**
+     * Should be called when we need to show back button for bouncer
+     */
+    public void setBackForBouncer(boolean isBouncerVisible) {
+        updateStateForFlag(FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE, isBouncerVisible);
+        applyState();
+    }
+
+    /**
+     * Slightly misnamed, but should be called when only keyguard OR AOD is showing
+     */
+    public void setKeyguardVisible(boolean isKeyguardVisible) {
+        updateStateForFlag(FLAG_KEYGUARD_VISIBLE, isKeyguardVisible);
+        applyState();
+    }
+
+    /**
+     * Returns true if IME bar is visible
+     */
+    public boolean isImeVisible() {
+        return (mState & FLAG_IME_VISIBLE) != 0;
+    }
+
+    /**
+     * Adds the bounds corresponding to all visible buttons to provided region
+     */
+    public void addVisibleButtonsRegion(TaskbarDragLayer parent, Region outRegion) {
+        int count = mAllButtons.size();
+        for (int i = 0; i < count; i++) {
+            View button = mAllButtons.get(i);
+            if (button.getVisibility() == View.VISIBLE) {
+                parent.getDescendantRectRelativeToSelf(button, mTempRect);
+                outRegion.op(mTempRect, Op.UNION);
+            }
+        }
+    }
+
+    /**
+     * Does not call {@link #applyState()}. Don't forget to!
+     */
+    private void updateStateForFlag(int flag, boolean enabled) {
+        if (enabled) {
+            mState |= flag;
+        } else {
+            mState &= ~flag;
+        }
+    }
+
+    private void applyState() {
+        int count = mPropertyHolders.size();
+        for (int i = 0; i < count; i++) {
+            mPropertyHolders.get(i).setState(mState);
+        }
+    }
+
+    private ImageView addButton(@DrawableRes int drawableId, @TaskbarButton int buttonType,
+            ViewGroup parent, TaskbarNavButtonController navButtonController, @IdRes int id) {
+        ImageView buttonView = addButton(parent, id);
+        buttonView.setImageResource(drawableId);
+        buttonView.setOnClickListener(view -> navButtonController.onButtonClick(buttonType));
+        return buttonView;
+    }
+
+    private ImageView addButton(ViewGroup parent, int id) {
+        ImageView buttonView = (ImageView) mContext.getLayoutInflater()
+                .inflate(R.layout.taskbar_nav_button, parent, false);
+        buttonView.setId(id);
+        parent.addView(buttonView);
+        mAllButtons.add(buttonView);
+        return buttonView;
+    }
+
+    private class RotationButtonImpl implements RotationButton {
+
+        private final ImageView mButton;
+        private AnimatedVectorDrawable mImageDrawable;
+
+        RotationButtonImpl(ImageView button) {
+            mButton = button;
+        }
+
+        @Override
+        public void setRotationButtonController(RotationButtonController rotationButtonController) {
+            // TODO(b/187754252) UI polish, different icons based on light/dark context, etc
+            mImageDrawable = (AnimatedVectorDrawable) mButton.getContext()
+                    .getDrawable(rotationButtonController.getIconResId());
+            mButton.setImageDrawable(mImageDrawable);
+            mImageDrawable.setCallback(mButton);
+        }
+
+        @Override
+        public View getCurrentView() {
+            return mButton;
+        }
+
+        @Override
+        public void show() {
+            mButton.setVisibility(View.VISIBLE);
+            mState |= FLAG_ROTATION_BUTTON_VISIBLE;
+            applyState();
+        }
+
+        @Override
+        public void hide() {
+            mButton.setVisibility(View.GONE);
+            mState &= ~FLAG_ROTATION_BUTTON_VISIBLE;
+            applyState();
+        }
+
+        @Override
+        public boolean isVisible() {
+            return mButton.getVisibility() == View.VISIBLE;
+        }
+
+        @Override
+        public void updateIcon(int lightIconColor, int darkIconColor) {
+            // TODO(b/187754252): UI Polish
+        }
+
+        @Override
+        public void setOnClickListener(OnClickListener onClickListener) {
+            mButton.setOnClickListener(onClickListener);
+        }
+
+        @Override
+        public void setOnHoverListener(OnHoverListener onHoverListener) {
+            mButton.setOnHoverListener(onHoverListener);
+        }
+
+        @Override
+        public AnimatedVectorDrawable getImageDrawable() {
+            return mImageDrawable;
+        }
+
+        @Override
+        public void setDarkIntensity(float darkIntensity) {
+            // TODO(b/187754252) UI polish
+        }
+
+        @Override
+        public boolean acceptRotationProposal() {
+            return mButton.isAttachedToWindow();
+        }
+    }
+
+    private static class StatePropertyHolder {
+
+        private final float mEnabledValue, mDisabledValue;
+        private final ObjectAnimator mAnimator;
+        private final IntPredicate mEnableCondition;
+
+        private boolean mIsEnabled = true;
+
+        StatePropertyHolder(View view, IntPredicate enableCondition) {
+            this(view, enableCondition, LauncherAnimUtils.VIEW_ALPHA, 1, 0);
+            mAnimator.addListener(new AlphaUpdateListener(view));
+        }
+
+        <T> StatePropertyHolder(T target, IntPredicate enabledCondition,
+                Property<T, Float> property, float enabledValue, float disabledValue) {
+            mEnableCondition = enabledCondition;
+            mEnabledValue = enabledValue;
+            mDisabledValue = disabledValue;
+            mAnimator = ObjectAnimator.ofFloat(target, property, enabledValue, disabledValue);
+        }
+
+        public void setState(int flags) {
+            boolean isEnabled = mEnableCondition.test(flags);
+            if (mIsEnabled != isEnabled) {
+                mIsEnabled = isEnabled;
+                mAnimator.cancel();
+                mAnimator.setFloatValues(mIsEnabled ? mEnabledValue : mDisabledValue);
+                mAnimator.start();
+            }
+        }
+
+        public void endAnimation() {
+            if (mAnimator.isRunning()) {
+                mAnimator.end();
+            }
+        }
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
new file mode 100644
index 0000000..8c14ff6
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import android.animation.Animator;
+import android.content.res.Resources;
+import android.graphics.Outline;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.R;
+import com.android.launcher3.anim.RevealOutlineAnimation;
+import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
+import com.android.quickstep.AnimatedFloat;
+
+/**
+ * Handles properties/data collection, then passes the results to our stashed handle View to render.
+ */
+public class StashedHandleViewController {
+
+    private final TaskbarActivityContext mActivity;
+    private final View mStashedHandleView;
+    private final int mStashedHandleWidth;
+    private final int mStashedHandleHeight;
+    private final AnimatedFloat mTaskbarStashedHandleAlpha = new AnimatedFloat(
+            this::updateStashedHandleAlpha);
+
+    // Initialized in init.
+    private TaskbarControllers mControllers;
+
+    // The bounds we want to clip to in the settled state when showing the stashed handle.
+    private final Rect mStashedHandleBounds = new Rect();
+    private float mStashedHandleRadius;
+
+    private boolean mIsAtStashedRevealBounds = true;
+
+    public StashedHandleViewController(TaskbarActivityContext activity, View stashedHandleView) {
+        mActivity = activity;
+        mStashedHandleView = stashedHandleView;
+        final Resources resources = mActivity.getResources();
+        mStashedHandleWidth = resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width);
+        mStashedHandleHeight = resources.getDimensionPixelSize(
+                R.dimen.taskbar_stashed_handle_height);
+    }
+
+    public void init(TaskbarControllers controllers) {
+        mControllers = controllers;
+        mStashedHandleView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize;
+
+        updateStashedHandleAlpha();
+
+        final int stashedTaskbarHeight = mControllers.taskbarStashController.getStashedHeight();
+        mStashedHandleView.setOutlineProvider(new ViewOutlineProvider() {
+            @Override
+            public void getOutline(View view, Outline outline) {
+                final int stashedCenterX = view.getWidth() / 2;
+                final int stashedCenterY = view.getHeight() - stashedTaskbarHeight / 2;
+                mStashedHandleBounds.set(
+                        stashedCenterX - mStashedHandleWidth / 2,
+                        stashedCenterY - mStashedHandleHeight / 2,
+                        stashedCenterX + mStashedHandleWidth / 2,
+                        stashedCenterY + mStashedHandleHeight / 2);
+                mStashedHandleRadius = view.getHeight() / 2f;
+                outline.setRoundRect(mStashedHandleBounds, mStashedHandleRadius);
+            }
+        });
+    }
+
+    public AnimatedFloat getStashedHandleAlpha() {
+        return mTaskbarStashedHandleAlpha;
+    }
+
+    /**
+     * Creates and returns a {@link RevealOutlineAnimation} Animator that updates the stashed handle
+     * shape and size. When stashed, the shape is a thin rounded pill. When unstashed, the shape
+     * morphs into the size of where the taskbar icons will be.
+     */
+    public @Nullable Animator createRevealAnimToIsStashed(boolean isStashed) {
+        if (mIsAtStashedRevealBounds == isStashed) {
+            return null;
+        }
+        mIsAtStashedRevealBounds = isStashed;
+        final RevealOutlineAnimation handleRevealProvider = new RoundedRectRevealOutlineProvider(
+                mStashedHandleRadius, mStashedHandleRadius,
+                mControllers.taskbarViewController.getIconLayoutBounds(), mStashedHandleBounds);
+        return handleRevealProvider.createRevealAnimator(mStashedHandleView, !isStashed);
+    }
+
+    protected void updateStashedHandleAlpha() {
+        mStashedHandleView.setAlpha(mTaskbarStashedHandleAlpha.value);
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 4ba0ee0..e11f4c1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -17,7 +17,7 @@
 
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
 
 import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
 import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR;
@@ -28,9 +28,7 @@
 import android.content.Intent;
 import android.content.pm.LauncherApps;
 import android.graphics.PixelFormat;
-import android.graphics.Point;
 import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
 import android.os.Process;
 import android.os.SystemProperties;
 import android.util.Log;
@@ -40,31 +38,25 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.WindowManager;
+import android.widget.FrameLayout;
 import android.widget.Toast;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.DragSource;
-import com.android.launcher3.DropTarget;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.R;
-import com.android.launcher3.dragndrop.DragController;
-import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.dragndrop.DragView;
-import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.model.data.FolderInfo;
-import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
+import com.android.launcher3.taskbar.contextual.RotationButtonController;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.TraceHelper;
+import com.android.launcher3.util.ViewCache;
 import com.android.launcher3.views.ActivityContext;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
@@ -88,45 +80,55 @@
     private final DeviceProfile mDeviceProfile;
     private final LayoutInflater mLayoutInflater;
     private final TaskbarDragLayer mDragLayer;
-    private final TaskbarIconController mIconController;
-    private final MyDragController mDragController;
+    private final TaskbarControllers mControllers;
 
     private final WindowManager mWindowManager;
     private WindowManager.LayoutParams mWindowLayoutParams;
+    private boolean mIsFullscreen;
+    // The size we should return to when we call setTaskbarWindowFullscreen(false)
+    private int mLastRequestedNonFullscreenHeight;
 
     private final SysUINavigationMode.Mode mNavMode;
-    private final TaskbarNavButtonController mNavButtonController;
+    private final ViewCache mViewCache = new ViewCache();
 
     private final boolean mIsSafeModeEnabled;
-
-    @NonNull
-    private TaskbarUIController mUIController = TaskbarUIController.DEFAULT;
-
-    private final View.OnClickListener mOnTaskbarIconClickListener;
-    private final View.OnLongClickListener mOnTaskbarIconLongClickListener;
+    private boolean mIsDestroyed = false;
 
     public TaskbarActivityContext(Context windowContext, DeviceProfile dp,
             TaskbarNavButtonController buttonController) {
         super(windowContext, Themes.getActivityThemeRes(windowContext));
         mDeviceProfile = dp;
-        mNavButtonController = buttonController;
+
         mNavMode = SysUINavigationMode.getMode(windowContext);
         mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
                 () -> getPackageManager().isSafeMode());
 
-        mOnTaskbarIconLongClickListener =
-                new TaskbarDragController(this)::startSystemDragOnLongClick;
-        mOnTaskbarIconClickListener = this::onTaskbarIconClicked;
-
         float taskbarIconSize = getResources().getDimension(R.dimen.taskbar_icon_size);
+        mDeviceProfile.updateIconSize(1, getResources());
         float iconScale = taskbarIconSize / mDeviceProfile.iconSizePx;
         mDeviceProfile.updateIconSize(iconScale, getResources());
 
         mLayoutInflater = LayoutInflater.from(this).cloneInContext(this);
-        mDragLayer = (TaskbarDragLayer) mLayoutInflater
-                .inflate(R.layout.taskbar, null, false);
-        mIconController = new TaskbarIconController(this, mDragLayer);
-        mDragController = new MyDragController(this);
+
+        // Inflate views.
+        mDragLayer = (TaskbarDragLayer) mLayoutInflater.inflate(
+                R.layout.taskbar, null, false);
+        TaskbarView taskbarView = mDragLayer.findViewById(R.id.taskbar_view);
+        FrameLayout navButtonsView = mDragLayer.findViewById(R.id.navbuttons_view);
+        View stashedHandleView = mDragLayer.findViewById(R.id.stashed_handle);
+
+        // Construct controllers.
+        mControllers = new TaskbarControllers(this,
+                new TaskbarDragController(this),
+                buttonController,
+                new NavbarButtonsViewController(this, navButtonsView),
+                new RotationButtonController(this, R.color.popup_color_primary_light,
+                        R.color.popup_color_primary_light),
+                new TaskbarDragLayerController(this, mDragLayer),
+                new TaskbarViewController(this, taskbarView),
+                new TaskbarKeyguardController(this),
+                new StashedHandleViewController(this, stashedHandleView),
+                new TaskbarStashController(this));
 
         Display display = windowContext.getDisplay();
         Context c = display.getDisplayId() == Display.DEFAULT_DISPLAY
@@ -136,10 +138,11 @@
     }
 
     public void init() {
+        mLastRequestedNonFullscreenHeight = mDeviceProfile.taskbarSize;
         mWindowLayoutParams = new WindowManager.LayoutParams(
                 MATCH_PARENT,
-                mDeviceProfile.taskbarSize,
-                TYPE_APPLICATION_OVERLAY,
+                mLastRequestedNonFullscreenHeight,
+                TYPE_NAVIGATION_BAR_PANEL,
                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                 PixelFormat.TRANSLUCENT);
         mWindowLayoutParams.setTitle(WINDOW_TITLE);
@@ -148,7 +151,6 @@
         mWindowLayoutParams.setFitInsetsTypes(0);
         mWindowLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
         mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-        mWindowLayoutParams.setSystemApplicationOverlay(true);
 
         WindowManagerWrapper wmWrapper = WindowManagerWrapper.getInstance();
         wmWrapper.setProvidesInsetsTypes(
@@ -156,23 +158,14 @@
                 new int[] { ITYPE_EXTRA_NAVIGATION_BAR, ITYPE_BOTTOM_TAPPABLE_ELEMENT }
         );
 
-        mIconController.init(mOnTaskbarIconClickListener, mOnTaskbarIconLongClickListener);
+        // Initialize controllers after all are constructed.
+        mControllers.init();
+
         mWindowManager.addView(mDragLayer, mWindowLayoutParams);
     }
 
-    /**
-     * Updates the TaskbarContainer height (pass deviceProfile.taskbarSize to reset).
-     */
-    public void setTaskbarWindowHeight(int height) {
-        if (mWindowLayoutParams.height == height) {
-            return;
-        }
-        mWindowLayoutParams.height = height;
-        mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
-    }
-
-    public boolean canShowNavButtons() {
-        return ENABLE_THREE_BUTTON_TASKBAR && mNavMode == Mode.THREE_BUTTONS;
+    public boolean isThreeButtonNav() {
+        return mNavMode == Mode.THREE_BUTTONS;
     }
 
     @Override
@@ -192,59 +185,89 @@
 
     @Override
     public Rect getFolderBoundingBox() {
-        return mDragLayer.getFolderBoundingBox();
+        return mControllers.taskbarDragLayerController.getFolderBoundingBox();
     }
 
     @Override
-    public DragController getDragController() {
-        return mDragController;
+    public TaskbarDragController getDragController() {
+        return mControllers.taskbarDragController;
+    }
+
+    @Override
+    public ViewCache getViewCache() {
+        return mViewCache;
     }
 
     /**
      * Sets a new data-source for this taskbar instance
      */
     public void setUIController(@NonNull TaskbarUIController uiController) {
-        mUIController.onDestroy();
-        mUIController = uiController;
-        mIconController.setUIController(mUIController);
-        mUIController.onCreate();
+        mControllers.uiController.onDestroy();
+        mControllers.uiController = uiController;
+        mControllers.uiController.init(mControllers);
     }
 
     /**
      * Called when this instance of taskbar is no longer needed
      */
     public void onDestroy() {
+        mIsDestroyed = true;
         setUIController(TaskbarUIController.DEFAULT);
-        mIconController.onDestroy();
+        mControllers.onDestroy();
         mWindowManager.removeViewImmediate(mDragLayer);
     }
 
-    void onNavigationButtonClick(@TaskbarButton int buttonType) {
-        mNavButtonController.onButtonClick(buttonType);
+    public void updateSysuiStateFlags(int systemUiStateFlags, boolean forceUpdate) {
+        mControllers.navbarButtonsViewController.updateStateForSysuiFlags(
+                systemUiStateFlags, forceUpdate);
+        mControllers.taskbarViewController.setImeIsVisible(
+                mControllers.navbarButtonsViewController.isImeVisible());
+        mControllers.taskbarKeyguardController.updateStateForSysuiFlags(systemUiStateFlags);
     }
 
-    /**
-     * Should be called when the IME visibility changes, so we can hide/show Taskbar accordingly.
-     */
-    public void setImeIsVisible(boolean isImeVisible) {
-        mIconController.setImeIsVisible(isImeVisible);
+    public void onRotationProposal(int rotation, boolean isValid) {
+        mControllers.rotationButtonController.onRotationProposal(rotation, isValid);
     }
 
-    /**
-     * When in 3 button nav, the above doesn't get called since we prevent sysui nav bar from
-     * instantiating at all, which is what's responsible for sending sysui state flags over.
-     *
-     * @param vis IME visibility flag
-     */
-    public void updateImeStatus(int displayId, int vis, boolean showImeSwitcher) {
-        mIconController.updateImeStatus(displayId, vis, showImeSwitcher);
+    public void disableNavBarElements(int displayId, int state1, int state2, boolean animate) {
+        if (displayId != getDisplayId()) {
+            return;
+        }
+        mControllers.rotationButtonController.onDisable2FlagChanged(state2);
+        mControllers.taskbarKeyguardController.disableNavbarElements(state1, state2);
+    }
+
+    public void onSystemBarAttributesChanged(int displayId, int behavior) {
+        mControllers.rotationButtonController.onBehaviorChanged(displayId, behavior);
     }
 
     /**
      * Updates the TaskbarContainer to MATCH_PARENT vs original Taskbar size.
      */
-    protected void setTaskbarWindowFullscreen(boolean fullscreen) {
-        setTaskbarWindowHeight(fullscreen ? MATCH_PARENT : getDeviceProfile().taskbarSize);
+    public void setTaskbarWindowFullscreen(boolean fullscreen) {
+        mIsFullscreen = fullscreen;
+        setTaskbarWindowHeight(fullscreen ? MATCH_PARENT : mLastRequestedNonFullscreenHeight);
+    }
+
+    /**
+     * Updates the TaskbarContainer height (pass deviceProfile.taskbarSize to reset).
+     */
+    public void setTaskbarWindowHeight(int height) {
+        if (mWindowLayoutParams.height == height || mIsDestroyed) {
+            return;
+        }
+        if (height != MATCH_PARENT) {
+            mLastRequestedNonFullscreenHeight = height;
+            if (mIsFullscreen) {
+                // We still need to be fullscreen, so defer any change to our height until we call
+                // setTaskbarWindowFullscreen(false). For example, this could happen when dragging
+                // from the gesture region, as the drag will cancel the gesture and reset launcher's
+                // state, which in turn normally would reset the taskbar window height as well.
+                return;
+            }
+        }
+        mWindowLayoutParams.height = height;
+        mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
     }
 
     protected void onTaskbarIconClicked(View view) {
@@ -262,8 +285,8 @@
                 folder.animateOpen();
 
                 folder.iterateOverItems((itemInfo, itemView) -> {
-                    itemView.setOnClickListener(mOnTaskbarIconClickListener);
-                    itemView.setOnLongClickListener(mOnTaskbarIconLongClickListener);
+                    mControllers.taskbarViewController
+                            .setClickAndLongClickListenersForIcon(itemView);
                     // To play haptic when dragging, like other Taskbar items do.
                     itemView.setHapticFeedbackEnabled(true);
                     return false;
@@ -271,7 +294,9 @@
             });
         } else if (tag instanceof WorkspaceItemInfo) {
             WorkspaceItemInfo info = (WorkspaceItemInfo) tag;
-            if (!(info.isDisabled() && ItemClickHandler.handleDisabledItemClicked(info, this))) {
+            if (info.isDisabled()) {
+                ItemClickHandler.handleDisabledItemClicked(info, this);
+            } else {
                 Intent intent = new Intent(info.getIntent())
                         .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                 try {
@@ -307,27 +332,4 @@
 
         AbstractFloatingView.closeAllOpenViews(this);
     }
-
-    private static class MyDragController extends DragController<TaskbarActivityContext> {
-        MyDragController(TaskbarActivityContext activity) {
-            super(activity);
-        }
-
-        @Override
-        protected DragView startDrag(@Nullable Drawable drawable, @Nullable View view,
-                DraggableView originalView, int dragLayerX, int dragLayerY, DragSource source,
-                ItemInfo dragInfo, Point dragOffset, Rect dragRegion, float initialDragViewScale,
-                float dragViewScaleOnDrop, DragOptions options) {
-            return null;
-        }
-
-        @Override
-        protected void exitDrag() {
-        }
-
-        @Override
-        protected DropTarget getDefaultDropTarget(int[] dropCoordinates) {
-            return null;
-        }
-    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java
deleted file mode 100644
index e20ddf8..0000000
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.taskbar;
-
-import static com.android.launcher3.LauncherState.TASKBAR;
-
-import android.animation.Animator;
-
-import com.android.launcher3.BaseQuickstepLauncher;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.taskbar.LauncherTaskbarUIController.TaskbarAnimationControllerCallbacks;
-import com.android.quickstep.AnimatedFloat;
-import com.android.quickstep.SystemUiProxy;
-import com.android.systemui.shared.system.QuickStepContract;
-
-/**
- * Works with TaskbarController to update the TaskbarView's visual properties based on factors such
- * as LauncherState, whether Launcher is in the foreground, etc.
- */
-public class TaskbarAnimationController {
-
-    private static final long IME_VISIBILITY_ALPHA_DURATION = 120;
-
-    private final BaseQuickstepLauncher mLauncher;
-    private final TaskbarAnimationControllerCallbacks mTaskbarCallbacks;
-
-    // Background alpha.
-    private final AnimatedFloat mTaskbarBackgroundAlpha = new AnimatedFloat(
-            this::onTaskbarBackgroundAlphaChanged);
-
-    // Overall visibility.
-    private final AnimatedFloat mTaskbarVisibilityAlphaForLauncherState = new AnimatedFloat(
-            this::updateVisibilityAlpha);
-    private final AnimatedFloat mTaskbarVisibilityAlphaForIme = new AnimatedFloat(
-            this::updateVisibilityAlphaForIme);
-
-    // Scale.
-    private final AnimatedFloat mTaskbarScaleForLauncherState = new AnimatedFloat(
-            this::updateScale);
-
-    // TranslationY.
-    private final AnimatedFloat mTaskbarTranslationYForLauncherState = new AnimatedFloat(
-            this::updateTranslationY);
-
-    public TaskbarAnimationController(BaseQuickstepLauncher launcher,
-            TaskbarAnimationControllerCallbacks taskbarCallbacks) {
-        mLauncher = launcher;
-        mTaskbarCallbacks = taskbarCallbacks;
-    }
-
-    protected void init() {
-        mTaskbarBackgroundAlpha.updateValue(mLauncher.hasBeenResumed() ? 0f : 1f);
-        boolean isVisibleForLauncherState = (mLauncher.getStateManager().getState()
-                .getVisibleElements(mLauncher) & TASKBAR) != 0;
-        mTaskbarVisibilityAlphaForLauncherState.updateValue(isVisibleForLauncherState ? 1f : 0f);
-        boolean isImeVisible = (SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags()
-                & QuickStepContract.SYSUI_STATE_IME_SHOWING) != 0;
-        mTaskbarVisibilityAlphaForIme.updateValue(isImeVisible ? 0f : 1f);
-
-        onTaskbarBackgroundAlphaChanged();
-        updateVisibilityAlpha();
-    }
-
-    protected void cleanup() {
-        setNavBarButtonAlpha(1f);
-    }
-
-    protected AnimatedFloat getTaskbarVisibilityForLauncherState() {
-        return mTaskbarVisibilityAlphaForLauncherState;
-    }
-
-    protected AnimatedFloat getTaskbarScaleForLauncherState() {
-        return mTaskbarScaleForLauncherState;
-    }
-
-    protected AnimatedFloat getTaskbarTranslationYForLauncherState() {
-        return mTaskbarTranslationYForLauncherState;
-    }
-
-    protected Animator createAnimToBackgroundAlpha(float toAlpha, long duration) {
-        return mTaskbarBackgroundAlpha.animateToValue(mTaskbarBackgroundAlpha.value, toAlpha)
-                .setDuration(duration);
-    }
-
-    protected void animateToVisibilityForIme(float toAlpha) {
-        mTaskbarVisibilityAlphaForIme.animateToValue(mTaskbarVisibilityAlphaForIme.value, toAlpha)
-                .setDuration(IME_VISIBILITY_ALPHA_DURATION).start();
-    }
-
-    private void onTaskbarBackgroundAlphaChanged() {
-        mTaskbarCallbacks.updateTaskbarBackgroundAlpha(mTaskbarBackgroundAlpha.value);
-        updateVisibilityAlpha();
-        updateScale();
-        updateTranslationY();
-    }
-
-    private void updateVisibilityAlpha() {
-        // We use mTaskbarBackgroundAlpha as a proxy for whether Launcher is resumed/paused, the
-        // assumption being that Taskbar should always be visible regardless of the current
-        // LauncherState if Launcher is paused.
-        float alphaDueToIme = mTaskbarVisibilityAlphaForIme.value;
-        float alphaDueToLauncher = Math.max(mTaskbarBackgroundAlpha.value,
-                mTaskbarVisibilityAlphaForLauncherState.value);
-        float taskbarAlpha = alphaDueToLauncher * alphaDueToIme;
-        mTaskbarCallbacks.updateTaskbarVisibilityAlpha(taskbarAlpha);
-
-        // Make the nav bar invisible if taskbar is visible.
-        setNavBarButtonAlpha(1f - taskbarAlpha);
-    }
-
-    private void updateVisibilityAlphaForIme() {
-        updateVisibilityAlpha();
-        float taskbarAlphaDueToIme = mTaskbarVisibilityAlphaForIme.value;
-        mTaskbarCallbacks.updateImeBarVisibilityAlpha(1f - taskbarAlphaDueToIme);
-    }
-
-    private void updateScale() {
-        // We use mTaskbarBackgroundAlpha as a proxy for whether Launcher is resumed/paused, the
-        // assumption being that Taskbar should always be at scale 1f regardless of the current
-        // LauncherState if Launcher is paused.
-        float scale = mTaskbarScaleForLauncherState.value;
-        scale = Utilities.mapRange(mTaskbarBackgroundAlpha.value, scale, 1f);
-        mTaskbarCallbacks.updateTaskbarScale(scale);
-    }
-
-    private void updateTranslationY() {
-        // We use mTaskbarBackgroundAlpha as a proxy for whether Launcher is resumed/paused, the
-        // assumption being that Taskbar should always be at translationY 0f regardless of the
-        // current LauncherState if Launcher is paused.
-        float translationY = mTaskbarTranslationYForLauncherState.value;
-        translationY = Utilities.mapRange(mTaskbarBackgroundAlpha.value, translationY, 0f);
-        mTaskbarCallbacks.updateTaskbarTranslationY(translationY);
-    }
-
-    private void setNavBarButtonAlpha(float navBarAlpha) {
-        SystemUiProxy.INSTANCE.get(mLauncher).setNavBarButtonAlpha(navBarAlpha, false);
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
new file mode 100644
index 0000000..8279a47
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.taskbar.contextual.RotationButtonController;
+
+/**
+ * Hosts various taskbar controllers to facilitate passing between one another.
+ */
+public class TaskbarControllers {
+
+    public final TaskbarActivityContext taskbarActivityContext;
+    public final TaskbarDragController taskbarDragController;
+    public final TaskbarNavButtonController navButtonController;
+    public final NavbarButtonsViewController navbarButtonsViewController;
+    public final RotationButtonController rotationButtonController;
+    public final TaskbarDragLayerController taskbarDragLayerController;
+    public final TaskbarViewController taskbarViewController;
+    public final TaskbarKeyguardController taskbarKeyguardController;
+    public final StashedHandleViewController stashedHandleViewController;
+    public final TaskbarStashController taskbarStashController;
+
+    /** Do not store this controller, as it may change at runtime. */
+    @NonNull public TaskbarUIController uiController = TaskbarUIController.DEFAULT;
+
+    public TaskbarControllers(TaskbarActivityContext taskbarActivityContext,
+            TaskbarDragController taskbarDragController,
+            TaskbarNavButtonController navButtonController,
+            NavbarButtonsViewController navbarButtonsViewController,
+            RotationButtonController rotationButtonController,
+            TaskbarDragLayerController taskbarDragLayerController,
+            TaskbarViewController taskbarViewController,
+            TaskbarKeyguardController taskbarKeyguardController,
+            StashedHandleViewController stashedHandleViewController,
+            TaskbarStashController taskbarStashController) {
+        this.taskbarActivityContext = taskbarActivityContext;
+        this.taskbarDragController = taskbarDragController;
+        this.navButtonController = navButtonController;
+        this.navbarButtonsViewController = navbarButtonsViewController;
+        this.rotationButtonController = rotationButtonController;
+        this.taskbarDragLayerController = taskbarDragLayerController;
+        this.taskbarViewController = taskbarViewController;
+        this.taskbarKeyguardController = taskbarKeyguardController;
+        this.stashedHandleViewController = stashedHandleViewController;
+        this.taskbarStashController = taskbarStashController;
+    }
+
+    /**
+     * Initializes all controllers. Note that controllers can now reference each other through this
+     * TaskbarControllers instance, but should be careful to only access things that were created
+     * in constructors for now, as some controllers may still be waiting for init().
+     */
+    public void init() {
+        navbarButtonsViewController.init(this);
+        if (taskbarActivityContext.isThreeButtonNav()) {
+            rotationButtonController.init();
+        }
+        taskbarDragLayerController.init(this);
+        taskbarViewController.init(this);
+        taskbarKeyguardController.init(navbarButtonsViewController);
+        stashedHandleViewController.init(this);
+        taskbarStashController.init(this);
+    }
+
+    /**
+     * Cleans up all controllers.
+     */
+    public void onDestroy() {
+        uiController.onDestroy();
+        rotationButtonController.onDestroy();
+        taskbarDragLayerController.onDestroy();
+        taskbarKeyguardController.onDestroy();
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index ee44927..67ebc02 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -20,19 +20,35 @@
 
 import android.content.ClipData;
 import android.content.ClipDescription;
-import android.content.Context;
 import android.content.Intent;
 import android.content.pm.LauncherApps;
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
 import android.view.DragEvent;
+import android.view.MotionEvent;
 import android.view.View;
 
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.DragSource;
+import com.android.launcher3.DropTarget;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
+import com.android.launcher3.accessibility.DragViewStateAnnouncer;
+import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.dragndrop.DragDriver;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.dragndrop.DraggableView;
+import com.android.launcher3.graphics.DragPreviewProvider;
+import com.android.launcher3.icons.FastBitmapDrawable;
+import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ClipDescriptionCompat;
@@ -41,14 +57,20 @@
 /**
  * Handles long click on Taskbar items to start a system drag and drop operation.
  */
-public class TaskbarDragController {
+public class TaskbarDragController extends DragController<TaskbarActivityContext>  {
 
-    private final Context mContext;
     private final int mDragIconSize;
+    private final int[] mTempXY = new int[2];
 
-    public TaskbarDragController(Context context) {
-        mContext = context;
-        Resources resources = mContext.getResources();
+    // Where the initial touch was relative to the dragged icon.
+    private int mRegistrationX;
+    private int mRegistrationY;
+
+    private boolean mIsSystemDragInProgress;
+
+    public TaskbarDragController(TaskbarActivityContext activity) {
+        super(activity);
+        Resources resources = mActivity.getResources();
         mDragIconSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_drag_icon_size);
     }
 
@@ -57,18 +79,146 @@
      * generate the ClipDescription and Intent.
      * @return Whether {@link View#startDragAndDrop} started successfully.
      */
-    protected boolean startSystemDragOnLongClick(View view) {
+    protected boolean startDragOnLongClick(View view) {
         if (!(view instanceof BubbleTextView)) {
             return false;
         }
 
         BubbleTextView btv = (BubbleTextView) view;
-        View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view) {
+
+        mActivity.setTaskbarWindowFullscreen(true);
+        view.post(() -> {
+            startInternalDrag(btv);
+            btv.setVisibility(INVISIBLE);
+        });
+        return true;
+    }
+
+    private void startInternalDrag(BubbleTextView btv) {
+        float iconScale = 1f;
+        Drawable icon = btv.getIcon();
+        if (icon instanceof FastBitmapDrawable) {
+            iconScale = ((FastBitmapDrawable) icon).getAnimatedScale();
+        }
+
+        // Clear the pressed state if necessary
+        btv.clearFocus();
+        btv.setPressed(false);
+        btv.clearPressedBackground();
+
+        final DragPreviewProvider previewProvider = new DragPreviewProvider(btv);
+        final Drawable drawable = previewProvider.createDrawable();
+        final float scale = previewProvider.getScaleAndPosition(drawable, mTempXY);
+        int dragLayerX = mTempXY[0];
+        int dragLayerY = mTempXY[1];
+
+        Rect dragRect = new Rect();
+        btv.getSourceVisualDragBounds(dragRect);
+        dragLayerY += dragRect.top;
+
+        DragOptions dragOptions = new DragOptions();
+        // TODO: open popup/pre-drag
+        // PopupContainerWithArrow popupContainer = PopupContainerWithArrow.showForIcon(view);
+        // if (popupContainer != null) {
+        //     dragOptions.preDragCondition = popupContainer.createPreDragCondition();
+        // }
+
+        startDrag(
+                drawable,
+                /* view = */ null,
+                /* originalView = */ btv,
+                dragLayerX,
+                dragLayerY,
+                (View target, DropTarget.DragObject d, boolean success) -> {} /* DragSource */,
+                (WorkspaceItemInfo) btv.getTag(),
+                /* dragVisualizeOffset = */ null,
+                dragRect,
+                scale * iconScale,
+                scale,
+                dragOptions);
+    }
+
+    @Override
+    protected DragView startDrag(@Nullable Drawable drawable, @Nullable View view,
+            DraggableView originalView, int dragLayerX, int dragLayerY, DragSource source,
+            ItemInfo dragInfo, Point dragOffset, Rect dragRegion, float initialDragViewScale,
+            float dragViewScaleOnDrop, DragOptions options) {
+        mOptions = options;
+
+        mRegistrationX = mMotionDown.x - dragLayerX;
+        mRegistrationY = mMotionDown.y - dragLayerY;
+
+        final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;
+        final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;
+
+        mLastDropTarget = null;
+
+        mDragObject = new DropTarget.DragObject(mActivity.getApplicationContext());
+        mDragObject.originalView = originalView;
+
+        mIsInPreDrag = mOptions.preDragCondition != null
+                && !mOptions.preDragCondition.shouldStartDrag(0);
+
+        float scalePx = mDragIconSize - dragRegion.width();
+        final DragView dragView = mDragObject.dragView = new TaskbarDragView(
+                mActivity,
+                drawable,
+                mRegistrationX,
+                mRegistrationY,
+                initialDragViewScale,
+                dragViewScaleOnDrop,
+                scalePx);
+        dragView.setItemInfo(dragInfo);
+        mDragObject.dragComplete = false;
+
+        mDragObject.xOffset = mMotionDown.x - (dragLayerX + dragRegionLeft);
+        mDragObject.yOffset = mMotionDown.y - (dragLayerY + dragRegionTop);
+
+        mDragDriver = DragDriver.create(this, mOptions, /* secondaryEventConsumer = */ ev -> {});
+        if (!mOptions.isAccessibleDrag) {
+            mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView);
+        }
+
+        mDragObject.dragSource = source;
+        mDragObject.dragInfo = dragInfo;
+        mDragObject.originalDragInfo = mDragObject.dragInfo.makeShallowCopy();
+
+        if (dragRegion != null) {
+            dragView.setDragRegion(new Rect(dragRegion));
+        }
+
+        dragView.show(mLastTouch.x, mLastTouch.y);
+        mDistanceSinceScroll = 0;
+
+        if (!mIsInPreDrag) {
+            callOnDragStart();
+        } else if (mOptions.preDragCondition != null) {
+            mOptions.preDragCondition.onPreDragStart(mDragObject);
+        }
+
+        handleMoveEvent(mLastTouch.x, mLastTouch.y);
+
+        return dragView;
+    }
+
+    @Override
+    protected void callOnDragStart() {
+        super.callOnDragStart();
+        // Pre-drag has ended, start the global system drag.
+        AbstractFloatingView.closeAllOpenViews(mActivity);
+        startSystemDrag((BubbleTextView) mDragObject.originalView);
+    }
+
+    private void startSystemDrag(BubbleTextView btv) {
+        View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(btv) {
+
             @Override
             public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
                 shadowSize.set(mDragIconSize, mDragIconSize);
-                // TODO: should be based on last touch point on the icon.
-                shadowTouchPoint.set(shadowSize.x / 2, shadowSize.y / 2);
+                // The registration point was taken before the icon scaled to mDragIconSize, so
+                // offset the registration to where the touch is on the new size.
+                int offset = (mDragIconSize - btv.getIconSize()) / 2;
+                shadowTouchPoint.set(mRegistrationX + offset, mRegistrationY + offset);
             }
 
             @Override
@@ -81,12 +231,12 @@
             }
         };
 
-        Object tag = view.getTag();
+        Object tag = btv.getTag();
         ClipDescription clipDescription = null;
         Intent intent = null;
         if (tag instanceof WorkspaceItemInfo) {
             WorkspaceItemInfo item = (WorkspaceItemInfo) tag;
-            LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
+            LauncherApps launcherApps = mActivity.getSystemService(LauncherApps.class);
             clipDescription = new ClipDescription(item.title,
                     new String[] {
                             item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
@@ -116,28 +266,82 @@
 
         if (clipDescription != null && intent != null) {
             ClipData clipData = new ClipData(clipDescription, new ClipData.Item(intent));
-            view.setOnDragListener(getDraggedViewDragListener());
-            return view.startDragAndDrop(clipData, shadowBuilder, null /* localState */,
-                    View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_OPAQUE);
+            if (btv.startDragAndDrop(clipData, shadowBuilder, null /* localState */,
+                    View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_OPAQUE)) {
+                onSystemDragStarted();
+            }
         }
-        return false;
     }
 
-    /**
-     * Hide the original Taskbar item while it is being dragged.
-     */
-    private View.OnDragListener getDraggedViewDragListener() {
-        return (view, dragEvent) -> {
+    private void onSystemDragStarted() {
+        mIsSystemDragInProgress = true;
+        mActivity.getDragLayer().setOnDragListener((view, dragEvent) -> {
             switch (dragEvent.getAction()) {
                 case DragEvent.ACTION_DRAG_STARTED:
-                    view.setVisibility(INVISIBLE);
+                    // Return true to tell system we are interested in events, so we get DRAG_ENDED.
                     return true;
                 case DragEvent.ACTION_DRAG_ENDED:
-                    view.setVisibility(VISIBLE);
-                    view.setOnDragListener(null);
+                    mIsSystemDragInProgress = false;
+                    maybeOnDragEnd();
                     return true;
             }
             return false;
-        };
+        });
+    }
+
+    @Override
+    public boolean isDragging() {
+        return super.isDragging() || mIsSystemDragInProgress;
+    }
+
+    private void maybeOnDragEnd() {
+        if (!isDragging()) {
+            ((View) mDragObject.originalView).setVisibility(VISIBLE);
+        }
+    }
+
+    @Override
+    protected void callOnDragEnd() {
+        super.callOnDragEnd();
+        maybeOnDragEnd();
+    }
+
+    @Override
+    protected float getX(MotionEvent ev) {
+        // We will resize to fill the screen while dragging, so use screen coordinates. This ensures
+        // we start at the correct position even though touch down is on the smaller DragLayer size.
+        return ev.getRawX();
+    }
+
+    @Override
+    protected float getY(MotionEvent ev) {
+        // We will resize to fill the screen while dragging, so use screen coordinates. This ensures
+        // we start at the correct position even though touch down is on the smaller DragLayer size.
+        return ev.getRawY();
+    }
+
+    @Override
+    protected Point getClampedDragLayerPos(float x, float y) {
+        // No need to clamp, as we will take up the entire screen.
+        mTmpPoint.set(Math.round(x), Math.round(y));
+        return mTmpPoint;
+    }
+
+    @Override
+    protected void exitDrag() {
+        if (mDragObject != null) {
+            mActivity.getDragLayer().removeView(mDragObject.dragView);
+        }
+    }
+
+    @Override
+    public void addDropTarget(DropTarget target) {
+        // No-op as Taskbar currently doesn't support any drop targets internally.
+        // Note: if we do add internal DropTargets, we'll still need to ignore Folder.
+    }
+
+    @Override
+    protected DropTarget getDefaultDropTarget(int[] dropCoordinates) {
+        return null;
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
index 45ec911..cd1baf7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
@@ -18,14 +18,16 @@
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Paint;
-import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.view.View;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.R;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.systemui.shared.system.ViewTreeObserverWrapper;
@@ -37,14 +39,13 @@
  */
 public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
 
-    private final int mFolderMargin;
     private final Paint mTaskbarBackgroundPaint;
-
-    private TaskbarIconController.Callbacks mControllerCallbacks;
-    private TaskbarView mTaskbarView;
-
     private final OnComputeInsetsListener mTaskbarInsetsComputer = this::onComputeTaskbarInsets;
 
+    private TaskbarDragLayerController.TaskbarDragLayerCallbacks mControllerCallbacks;
+
+    private float mTaskbarBackgroundOffset;
+
     public TaskbarDragLayer(@NonNull Context context) {
         this(context, null);
     }
@@ -61,25 +62,25 @@
     public TaskbarDragLayer(@NonNull Context context, @Nullable AttributeSet attrs,
             int defStyleAttr, int defStyleRes) {
         super(context, attrs, 1 /* alphaChannelCount */);
-        mFolderMargin = getResources().getDimensionPixelSize(R.dimen.taskbar_folder_margin);
         mTaskbarBackgroundPaint = new Paint();
         mTaskbarBackgroundPaint.setColor(getResources().getColor(R.color.taskbar_background));
+        mTaskbarBackgroundPaint.setAlpha(0);
+    }
+
+    public void init(TaskbarDragLayerController.TaskbarDragLayerCallbacks callbacks) {
+        mControllerCallbacks = callbacks;
         recreateControllers();
     }
 
     @Override
     public void recreateControllers() {
-        mControllers = new TouchController[0];
-    }
-
-    public void init(TaskbarIconController.Callbacks callbacks, TaskbarView taskbarView) {
-        mControllerCallbacks = callbacks;
-        mTaskbarView = taskbarView;
+        mControllers = new TouchController[] {mActivity.getDragController()};
     }
 
     private void onComputeTaskbarInsets(InsetsInfo insetsInfo) {
         if (mControllerCallbacks != null) {
             mControllerCallbacks.updateInsetsTouchability(insetsInfo);
+            mControllerCallbacks.updateContentInsets(insetsInfo.contentInsets);
         }
     }
 
@@ -108,12 +109,6 @@
         return true;
     }
 
-    public void updateImeBarVisibilityAlpha(float alpha) {
-        if (mControllerCallbacks != null) {
-            mControllerCallbacks.updateImeBarVisibilityAlpha(alpha);
-        }
-    }
-
     @Override
     public void onViewRemoved(View child) {
         super.onViewRemoved(child);
@@ -124,22 +119,14 @@
 
     @Override
     protected void dispatchDraw(Canvas canvas) {
-        canvas.drawRect(0, canvas.getHeight() - mTaskbarView.getHeight(), canvas.getWidth(),
+        float backgroundHeight = mControllerCallbacks.getTaskbarBackgroundHeight()
+                * (1f - mTaskbarBackgroundOffset);
+        canvas.drawRect(0, canvas.getHeight() - backgroundHeight, canvas.getWidth(),
                 canvas.getHeight(), mTaskbarBackgroundPaint);
         super.dispatchDraw(canvas);
     }
 
     /**
-     * @return Bounds (in our coordinates) where an opened Folder can display.
-     */
-    protected Rect getFolderBoundingBox() {
-        Rect boundingBox = new Rect(0, 0, getWidth(), getHeight() - mTaskbarView.getHeight());
-        boundingBox.inset(mFolderMargin, mFolderMargin);
-        return boundingBox;
-    }
-
-
-    /**
      * Sets the alpha of the background color behind all the Taskbar contents.
      * @param alpha 0 is fully transparent, 1 is fully opaque.
      */
@@ -147,4 +134,19 @@
         mTaskbarBackgroundPaint.setAlpha((int) (alpha * 255));
         invalidate();
     }
+
+    /**
+     * Sets the translation of the background color behind all the Taskbar contents.
+     * @param offset 0 is fully onscreen, 1 is fully offscreen.
+     */
+    protected void setTaskbarBackgroundOffset(float offset) {
+        mTaskbarBackgroundOffset = offset;
+        invalidate();
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        TestLogging.recordMotionEvent(TestProtocol.SEQUENCE_MAIN, "Touch event", ev);
+        return super.dispatchTouchEvent(ev);
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
new file mode 100644
index 0000000..e15e9ff
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
+import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_FRAME;
+import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION;
+
+import android.content.res.Resources;
+import android.graphics.Rect;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.R;
+import com.android.launcher3.anim.AlphaUpdateListener;
+import com.android.quickstep.AnimatedFloat;
+import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo;
+
+/**
+ * Handles properties/data collection, then passes the results to TaskbarDragLayer to render.
+ */
+public class TaskbarDragLayerController {
+
+    private final TaskbarActivityContext mActivity;
+    private final TaskbarDragLayer mTaskbarDragLayer;
+    private final int mFolderMargin;
+    // Alpha properties for taskbar background.
+    private final AnimatedFloat mBgTaskbar = new AnimatedFloat(this::updateBackgroundAlpha);
+    private final AnimatedFloat mBgNavbar = new AnimatedFloat(this::updateBackgroundAlpha);
+    // Translation property for taskbar background.
+    private final AnimatedFloat mBgOffset = new AnimatedFloat(this::updateBackgroundOffset);
+
+    // Initialized in init.
+    private TaskbarControllers mControllers;
+
+    public TaskbarDragLayerController(TaskbarActivityContext activity,
+            TaskbarDragLayer taskbarDragLayer) {
+        mActivity = activity;
+        mTaskbarDragLayer = taskbarDragLayer;
+        final Resources resources = mTaskbarDragLayer.getResources();
+        mFolderMargin = resources.getDimensionPixelSize(R.dimen.taskbar_folder_margin);
+    }
+
+    public void init(TaskbarControllers controllers) {
+        mControllers = controllers;
+        mTaskbarDragLayer.init(new TaskbarDragLayerCallbacks());
+    }
+
+    public void onDestroy() {
+        mTaskbarDragLayer.onDestroy();
+    }
+
+    /**
+     * @return Bounds (in TaskbarDragLayer coordinates) where an opened Folder can display.
+     */
+    public Rect getFolderBoundingBox() {
+        Rect boundingBox = new Rect(0, 0, mTaskbarDragLayer.getWidth(),
+                mTaskbarDragLayer.getHeight() - mActivity.getDeviceProfile().taskbarSize);
+        boundingBox.inset(mFolderMargin, mFolderMargin);
+        return boundingBox;
+    }
+
+    public AnimatedFloat getTaskbarBackgroundAlpha() {
+        return mBgTaskbar;
+    }
+
+    public AnimatedFloat getNavbarBackgroundAlpha() {
+        return mBgNavbar;
+    }
+
+    public AnimatedFloat getTaskbarBackgroundOffset() {
+        return mBgOffset;
+    }
+
+    private void updateBackgroundAlpha() {
+        mTaskbarDragLayer.setTaskbarBackgroundAlpha(Math.max(mBgNavbar.value, mBgTaskbar.value));
+    }
+
+    private void updateBackgroundOffset() {
+        mTaskbarDragLayer.setTaskbarBackgroundOffset(mBgOffset.value);
+    }
+
+    /**
+     * Callbacks for {@link TaskbarDragLayer} to interact with its controller.
+     */
+    public class TaskbarDragLayerCallbacks {
+
+        /**
+         * Called to update the touchable insets.
+         * @see InsetsInfo#setTouchableInsets(int)
+         */
+        public void updateInsetsTouchability(InsetsInfo insetsInfo) {
+            insetsInfo.touchableRegion.setEmpty();
+            if (mActivity.isThreeButtonNav()) {
+                // Always have nav buttons be touchable
+                mControllers.navbarButtonsViewController.addVisibleButtonsRegion(
+                        mTaskbarDragLayer, insetsInfo.touchableRegion);
+            }
+
+            if (mTaskbarDragLayer.getAlpha() < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) {
+                // Let touches pass through us.
+                insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
+            } else if (mControllers.navbarButtonsViewController.isImeVisible()) {
+                insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME);
+            } else if (!mControllers.uiController.isTaskbarTouchable()) {
+                // Let touches pass through us.
+                insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
+            } else if (mControllers.taskbarViewController.areIconsVisible()) {
+                // Buttons are visible, take over the full taskbar area
+                insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME);
+            } else {
+                insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
+            }
+        }
+
+        /**
+         * Called to update the {@link InsetsInfo#contentInsets}.
+         */
+        public void updateContentInsets(Rect outContentInsets) {
+            mControllers.uiController.updateContentInsets(outContentInsets);
+        }
+
+        /**
+         * Called when a child is removed from TaskbarDragLayer.
+         */
+        public void onDragLayerViewRemoved() {
+            if (AbstractFloatingView.getOpenView(mActivity, TYPE_ALL) == null) {
+                mActivity.setTaskbarWindowFullscreen(false);
+            }
+        }
+
+        /**
+         * Returns how tall the background should be drawn at the bottom of the screen.
+         */
+        public int getTaskbarBackgroundHeight() {
+            return mActivity.getDeviceProfile().taskbarSize;
+        }
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragView.java
new file mode 100644
index 0000000..cf28eff
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragView.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import android.graphics.drawable.Drawable;
+
+import com.android.launcher3.R;
+import com.android.launcher3.dragndrop.DragView;
+
+/**
+ * A DragView drawn/used by the Taskbar. Note that this is only for the internal drag-and-drop,
+ * while the pre-drag is still in progress (i.e. when the long press popup is still open). After
+ * that ends, we switch to a system drag and drop view instead.
+ */
+public class TaskbarDragView extends DragView<TaskbarActivityContext> {
+    public TaskbarDragView(TaskbarActivityContext launcher, Drawable drawable, int registrationX,
+            int registrationY, float initialScale, float scaleOnDrop, float finalScaleDps) {
+        super(launcher, drawable, registrationX, registrationY, initialScale, scaleOnDrop,
+                finalScaleDps);
+    }
+
+    @Override
+    public void animateTo(int toTouchX, int toTouchY, Runnable onCompleteRunnable, int duration) {
+        Runnable onAnimationEnd = () -> {
+            if (onCompleteRunnable != null) {
+                onCompleteRunnable.run();
+            }
+            mActivity.getDragLayer().removeView(this);
+        };
+
+        duration = Math.max(duration,
+                getResources().getInteger(R.integer.config_dropAnimMinDuration));
+
+        animate()
+                .translationX(toTouchX - mRegistrationX)
+                .translationY(toTouchY - mRegistrationY)
+                .scaleX(mScaleOnDrop)
+                .scaleY(mScaleOnDrop)
+                .withEndAction(onAnimationEnd)
+                .setDuration(duration)
+                .start();
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java
deleted file mode 100644
index 683a5b9..0000000
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.taskbar;
-
-import static android.view.View.GONE;
-import static android.view.View.VISIBLE;
-
-import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_FRAME;
-import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION;
-
-import android.graphics.Rect;
-import android.inputmethodservice.InputMethodService;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnLongClickListener;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.R;
-import com.android.launcher3.anim.AlphaUpdateListener;
-import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo;
-
-/**
- * Controller for taskbar icon UI
- */
-public class TaskbarIconController {
-
-    private final Rect mTempRect = new Rect();
-
-    private final TaskbarActivityContext mActivity;
-    private final TaskbarDragLayer mDragLayer;
-
-    private final TaskbarView mTaskbarView;
-    private final ImeBarView mImeBarView;
-
-    @NonNull
-    private TaskbarUIController mUIController = TaskbarUIController.DEFAULT;
-
-    TaskbarIconController(TaskbarActivityContext activity, TaskbarDragLayer dragLayer) {
-        mActivity = activity;
-        mDragLayer = dragLayer;
-        mTaskbarView = mDragLayer.findViewById(R.id.taskbar_view);
-        mImeBarView = mDragLayer.findViewById(R.id.ime_bar_view);
-    }
-
-    public void init(OnClickListener clickListener, OnLongClickListener longClickListener) {
-        mDragLayer.addOnLayoutChangeListener((v, a, b, c, d, e, f, g, h) ->
-                mUIController.alignRealHotseatWithTaskbar());
-
-        ButtonProvider buttonProvider = new ButtonProvider(mActivity);
-        mImeBarView.init(buttonProvider);
-        mTaskbarView.construct(clickListener, longClickListener, buttonProvider);
-        mTaskbarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize;
-
-        mDragLayer.init(new Callbacks(), mTaskbarView);
-    }
-
-    public void onDestroy() {
-        mDragLayer.onDestroy();
-    }
-
-    public void setUIController(@NonNull TaskbarUIController uiController) {
-        mUIController = uiController;
-    }
-
-    /**
-     * When in 3 button nav, the above doesn't get called since we prevent sysui nav bar from
-     * instantiating at all, which is what's responsible for sending sysui state flags over.
-     *
-     * @param vis IME visibility flag
-     */
-    public void updateImeStatus(int displayId, int vis, boolean showImeSwitcher) {
-        if (displayId != mActivity.getDisplayId() || !mActivity.canShowNavButtons()) {
-            return;
-        }
-
-        mImeBarView.setImeSwitcherVisibility(showImeSwitcher);
-        setImeIsVisible((vis & InputMethodService.IME_VISIBLE) != 0);
-    }
-
-    /**
-     * Should be called when the IME visibility changes, so we can hide/show Taskbar accordingly.
-     */
-    public void setImeIsVisible(boolean isImeVisible) {
-        mTaskbarView.setTouchesEnabled(!isImeVisible);
-        mUIController.onImeVisible(mDragLayer, isImeVisible);
-    }
-
-    /**
-     * Callbacks for {@link TaskbarDragLayer} to interact with the icon controller
-     */
-    public class Callbacks {
-
-        /**
-         * Called to update the touchable insets
-         */
-        public void updateInsetsTouchability(InsetsInfo insetsInfo) {
-            insetsInfo.touchableRegion.setEmpty();
-            if (mDragLayer.getAlpha() < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) {
-                // Let touches pass through us.
-                insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
-            } else if (mImeBarView.getVisibility() == VISIBLE) {
-                insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME);
-            } else if (!mUIController.isTaskbarTouchable()) {
-                // Let touches pass through us.
-                insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
-            } else if (mTaskbarView.areIconsVisible()) {
-                // Buttons are visible, take over the full taskbar area
-                insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME);
-            } else {
-                if (mTaskbarView.mSystemButtonContainer.getVisibility() == VISIBLE) {
-                    mDragLayer.getDescendantRectRelativeToSelf(
-                            mTaskbarView.mSystemButtonContainer, mTempRect);
-                    insetsInfo.touchableRegion.set(mTempRect);
-                }
-                insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
-            }
-
-            // TaskbarContainerView provides insets to other apps based on contentInsets. These
-            // insets should stay consistent even if we expand TaskbarContainerView's bounds, e.g.
-            // to show a floating view like Folder. Thus, we set the contentInsets to be where
-            // mTaskbarView is, since its position never changes and insets rather than overlays.
-            insetsInfo.contentInsets.left = mTaskbarView.getLeft();
-            insetsInfo.contentInsets.top = mTaskbarView.getTop();
-            insetsInfo.contentInsets.right = mDragLayer.getWidth() - mTaskbarView.getRight();
-            insetsInfo.contentInsets.bottom = mDragLayer.getHeight() - mTaskbarView.getBottom();
-        }
-
-        public void onDragLayerViewRemoved() {
-            int count = mDragLayer.getChildCount();
-            // Ensure no other children present (like Folders, etc)
-            for (int i = 0; i < count; i++) {
-                View v = mDragLayer.getChildAt(i);
-                if (!((v instanceof TaskbarView) || (v instanceof ImeBarView))) {
-                    return;
-                }
-            }
-            mActivity.setTaskbarWindowFullscreen(false);
-        }
-
-        public void updateImeBarVisibilityAlpha(float alpha) {
-            if (!mActivity.canShowNavButtons()) {
-                // TODO Remove sysui IME bar for gesture nav as well
-                return;
-            }
-            mImeBarView.setAlpha(alpha);
-            mImeBarView.setVisibility(alpha == 0 ? GONE : VISIBLE);
-        }
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java
new file mode 100644
index 0000000..2936bd2
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java
@@ -0,0 +1,104 @@
+package com.android.launcher3.taskbar;
+
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
+
+import android.app.KeyguardManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.view.View;
+
+/**
+ * Controller for managing keyguard state for taskbar
+ */
+public class TaskbarKeyguardController {
+
+    private static final int KEYGUARD_SYSUI_FLAGS = SYSUI_STATE_BOUNCER_SHOWING |
+            SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING | SYSUI_STATE_DEVICE_DOZING;
+
+    private final TaskbarActivityContext mContext;
+    private int mDisabledNavIcons;
+    private int mKeyguardSysuiFlags;
+    private boolean mBouncerShowing;
+    private NavbarButtonsViewController mNavbarButtonsViewController;
+    private final KeyguardManager mKeyguardManager;
+    private boolean mIsScreenOff;
+
+    private final BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mIsScreenOff = true;
+        }
+    };
+
+    public TaskbarKeyguardController(TaskbarActivityContext context) {
+        mContext = context;
+        mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
+    }
+
+    public void init(NavbarButtonsViewController navbarButtonUIController) {
+        mNavbarButtonsViewController = navbarButtonUIController;
+        mContext.registerReceiver(mScreenOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
+    }
+
+    public void updateStateForSysuiFlags(int systemUiStateFlags) {
+        boolean bouncerShowing = (systemUiStateFlags & SYSUI_STATE_BOUNCER_SHOWING) != 0;
+        boolean keyguardShowing = (systemUiStateFlags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING)
+                != 0;
+        boolean dozing = (systemUiStateFlags & SYSUI_STATE_DEVICE_DOZING) != 0;
+
+        int interestingKeyguardFlags = systemUiStateFlags & KEYGUARD_SYSUI_FLAGS;
+        if (interestingKeyguardFlags == mKeyguardSysuiFlags) {
+            return;
+        }
+        mKeyguardSysuiFlags = interestingKeyguardFlags;
+
+        mBouncerShowing = bouncerShowing;
+        if (!mContext.isThreeButtonNav()) {
+            // For gesture nav we don't need to deal with bouncer or showing taskbar when locked
+            return;
+        }
+
+        mNavbarButtonsViewController.setKeyguardVisible(keyguardShowing || dozing);
+        updateIconsForBouncer();
+    }
+
+    public boolean isScreenOff() {
+        return mIsScreenOff;
+    }
+
+    public void setScreenOn() {
+        mIsScreenOff = false;
+    }
+
+    public void disableNavbarElements(int state1, int state2) {
+        if (mDisabledNavIcons == state1) {
+            // no change
+            return;
+        }
+        mDisabledNavIcons = state1;
+        updateIconsForBouncer();
+    }
+
+    /**
+     * Hides/shows taskbar when keyguard is up
+     */
+    private void updateIconsForBouncer() {
+        boolean disableBack = (mDisabledNavIcons & View.STATUS_BAR_DISABLE_BACK) != 0;
+        boolean disableRecent = (mDisabledNavIcons & View.STATUS_BAR_DISABLE_RECENT) != 0;
+        boolean disableHome = (mDisabledNavIcons & View.STATUS_BAR_DISABLE_HOME) != 0;
+        boolean onlyBackEnabled = !disableBack && disableRecent && disableHome;
+
+        boolean showBackForBouncer = onlyBackEnabled &&
+                mKeyguardManager.isDeviceSecure() &&
+                mBouncerShowing;
+        mNavbarButtonsViewController.setBackForBouncer(showBackForBouncer);
+    }
+
+    public void onDestroy() {
+        mContext.unregisterReceiver(mScreenOffReceiver);
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index d026bfb..788a36b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -21,14 +21,12 @@
 import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
 import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
 import static com.android.launcher3.util.DisplayController.CHANGE_SUPPORTED_BOUNDS;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
 
 import android.content.Context;
 import android.hardware.display.DisplayManager;
-import android.inputmethodservice.InputMethodService;
 import android.view.Display;
 
-import androidx.annotation.Nullable;
+import androidx.annotation.NonNull;
 
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.DeviceProfile;
@@ -41,7 +39,7 @@
 import com.android.quickstep.TouchInteractionService;
 
 /**
- * Class to manager taskbar lifecycle
+ * Class to manage taskbar lifecycle
  */
 public class TaskbarManager implements DisplayController.DisplayInfoChangeListener,
         SysUINavigationMode.NavigationModeChangeListener {
@@ -53,6 +51,11 @@
 
     private TaskbarActivityContext mTaskbarActivityContext;
     private BaseQuickstepLauncher mLauncher;
+    /**
+     * Cache a copy here so we can initialize state whenever taskbar is recreated, since
+     * this class does not get re-initialized w/ new taskbars.
+     */
+    private int mSysuiStateFlags;
 
     private static final int CHANGE_FLAGS =
             CHANGE_ACTIVE_SCREEN | CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS;
@@ -100,14 +103,25 @@
     }
 
     /**
-     * Sets or clears a launcher to act as taskbar callback
+     * Sets a launcher to act as taskbar callback
      */
-    public void setLauncher(@Nullable BaseQuickstepLauncher launcher) {
+    public void setLauncher(@NonNull BaseQuickstepLauncher launcher) {
         mLauncher = launcher;
         if (mTaskbarActivityContext != null) {
-            mTaskbarActivityContext.setUIController(mLauncher == null
-                    ? TaskbarUIController.DEFAULT
-                    : new LauncherTaskbarUIController(launcher, mTaskbarActivityContext));
+            mTaskbarActivityContext.setUIController(
+                    new LauncherTaskbarUIController(launcher, mTaskbarActivityContext));
+        }
+    }
+
+    /**
+     * Clears a previously set Launcher
+     */
+    public void clearLauncher(@NonNull BaseQuickstepLauncher launcher) {
+        if (mLauncher == launcher) {
+            mLauncher = null;
+            if (mTaskbarActivityContext != null) {
+                mTaskbarActivityContext.setUIController(TaskbarUIController.DEFAULT);
+            }
         }
     }
 
@@ -130,31 +144,35 @@
             mTaskbarActivityContext.setUIController(
                     new LauncherTaskbarUIController(mLauncher, mTaskbarActivityContext));
         }
+        onSysuiFlagsChangedInternal(mSysuiStateFlags, true /* forceUpdate */);
     }
 
-    /**
-     * See {@link com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags}
-     * @param systemUiStateFlags The latest SystemUiStateFlags
-     */
     public void onSystemUiFlagsChanged(int systemUiStateFlags) {
-        boolean isImeVisible = (systemUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0;
+        onSysuiFlagsChangedInternal(systemUiStateFlags, false /* forceUpdate */);
+    }
+
+    private void onSysuiFlagsChangedInternal(int systemUiStateFlags, boolean forceUpdate) {
+        mSysuiStateFlags = systemUiStateFlags;
         if (mTaskbarActivityContext != null) {
-            mTaskbarActivityContext.setImeIsVisible(isImeVisible);
+            mTaskbarActivityContext.updateSysuiStateFlags(systemUiStateFlags, forceUpdate);
         }
     }
 
-    /**
-     * When in 3 button nav, the above doesn't get called since we prevent sysui nav bar from
-     * instantiating at all, which is what's responsible for sending sysui state flags over.
-     *
-     * @param vis IME visibility flag
-     * @param backDisposition Used to determine back button behavior for software keyboard
-     *                        See BACK_DISPOSITION_* constants in {@link InputMethodService}
-     */
-    public void updateImeStatus(int displayId, int vis, int backDisposition,
-            boolean showImeSwitcher) {
+    public void onRotationProposal(int rotation, boolean isValid) {
         if (mTaskbarActivityContext != null) {
-            mTaskbarActivityContext.updateImeStatus(displayId, vis, showImeSwitcher);
+            mTaskbarActivityContext.onRotationProposal(rotation, isValid);
+        }
+    }
+
+    public void disableNavBarElements(int displayId, int state1, int state2, boolean animate) {
+        if (mTaskbarActivityContext != null) {
+            mTaskbarActivityContext.disableNavBarElements(displayId, state1, state2, animate);
+        }
+    }
+
+    public void onSystemBarAttributesChanged(int displayId, int behavior) {
+        if (mTaskbarActivityContext != null) {
+            mTaskbarActivityContext.onSystemBarAttributesChanged(displayId, behavior);
         }
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
index 3b5afad..dd7c403 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -23,6 +23,8 @@
 
 import androidx.annotation.IntDef;
 
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.quickstep.OverviewCommandHelper;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TouchInteractionService;
@@ -44,7 +46,9 @@
             BUTTON_BACK,
             BUTTON_HOME,
             BUTTON_RECENTS,
-            BUTTON_IME_SWITCH
+            BUTTON_IME_SWITCH,
+            BUTTON_A11Y,
+            BUTTON_A11Y_LONG_CLICK
     })
 
     public @interface TaskbarButton {}
@@ -53,6 +57,8 @@
     static final int BUTTON_HOME = BUTTON_BACK << 1;
     static final int BUTTON_RECENTS = BUTTON_HOME << 1;
     static final int BUTTON_IME_SWITCH = BUTTON_RECENTS << 1;
+    static final int BUTTON_A11Y = BUTTON_IME_SWITCH << 1;
+    static final int BUTTON_A11Y_LONG_CLICK = BUTTON_A11Y << 1;
 
     private final TouchInteractionService mService;
 
@@ -74,6 +80,12 @@
             case BUTTON_IME_SWITCH:
                 showIMESwitcher();
                 break;
+            case BUTTON_A11Y:
+                notifyImeClick(false /* longClick */);
+                break;
+            case BUTTON_A11Y_LONG_CLICK:
+                notifyImeClick(true /* longClick */);
+                break;
         }
     }
 
@@ -84,8 +96,8 @@
     }
 
     private void navigateToOverview() {
-        mService.getOverviewCommandHelper()
-                .addCommand(OverviewCommandHelper.TYPE_SHOW);
+        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle");
+        mService.getOverviewCommandHelper().addCommand(OverviewCommandHelper.TYPE_TOGGLE);
     }
 
     private void executeBack() {
@@ -97,4 +109,13 @@
                 .showInputMethodPickerFromSystem(true /* showAuxiliarySubtypes */,
                         DEFAULT_DISPLAY);
     }
+
+    private void notifyImeClick(boolean longClick) {
+        SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.getNoCreate();
+        if (longClick) {
+            systemUiProxy.notifyAccessibilityButtonLongClicked();
+        } else {
+            systemUiProxy.notifyAccessibilityButtonClicked(mService.getDisplayId());
+        }
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
new file mode 100644
index 0000000..57600d7
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import static android.view.HapticFeedbackConstants.LONG_PRESS;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.annotation.Nullable;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
+import com.android.quickstep.AnimatedFloat;
+
+/**
+ * Coordinates between controllers such as TaskbarViewController and StashedHandleViewController to
+ * create a cohesive animation between stashed/unstashed states.
+ */
+public class TaskbarStashController {
+
+    /**
+     * How long to stash/unstash when manually invoked via long press.
+     */
+    private static final long TASKBAR_STASH_DURATION = 300;
+
+    /**
+     * The scale TaskbarView animates to when being stashed.
+     */
+    private static final float STASHED_TASKBAR_SCALE = 0.5f;
+
+    /**
+     * The SharedPreferences key for whether user has manually stashed the taskbar.
+     */
+    private static final String SHARED_PREFS_STASHED_KEY = "taskbar_is_stashed";
+
+    /**
+     * Whether taskbar should be stashed out of the box.
+     */
+    private static final boolean DEFAULT_STASHED_PREF = false;
+
+    private final TaskbarActivityContext mActivity;
+    private final SharedPreferences mPrefs;
+    private final int mStashedHeight;
+    private final int mUnstashedHeight;
+
+    // Initialized in init.
+    private TaskbarControllers mControllers;
+    // Taskbar background properties.
+    private AnimatedFloat mTaskbarBackgroundOffset;
+    // TaskbarView icon properties.
+    private AlphaProperty mIconAlphaForStash;
+    private AnimatedFloat mIconScaleForStash;
+    private AnimatedFloat mIconTranslationYForStash;
+    // Stashed handle properties.
+    private AnimatedFloat mTaskbarStashedHandleAlpha;
+
+    /** Whether the user has manually invoked taskbar stashing, which we persist. */
+    private boolean mIsStashedInApp;
+    /** Whether we are currently visually stashed (might change based on launcher state). */
+    private boolean mIsStashed = false;
+
+    private @Nullable AnimatorSet mAnimator;
+
+    public TaskbarStashController(TaskbarActivityContext activity) {
+        mActivity = activity;
+        mPrefs = Utilities.getPrefs(mActivity);
+        final Resources resources = mActivity.getResources();
+        mStashedHeight = resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size);
+        mUnstashedHeight = mActivity.getDeviceProfile().taskbarSize;
+    }
+
+    public void init(TaskbarControllers controllers) {
+        mControllers = controllers;
+
+        TaskbarDragLayerController dragLayerController = controllers.taskbarDragLayerController;
+        mTaskbarBackgroundOffset = dragLayerController.getTaskbarBackgroundOffset();
+
+        TaskbarViewController taskbarViewController = controllers.taskbarViewController;
+        mIconAlphaForStash = taskbarViewController.getTaskbarIconAlpha().getProperty(
+                TaskbarViewController.ALPHA_INDEX_STASH);
+        mIconScaleForStash = taskbarViewController.getTaskbarIconScaleForStash();
+        mIconTranslationYForStash = taskbarViewController.getTaskbarIconTranslationYForStash();
+
+        StashedHandleViewController stashedHandleController =
+                controllers.stashedHandleViewController;
+        mTaskbarStashedHandleAlpha = stashedHandleController.getStashedHandleAlpha();
+
+        mIsStashedInApp = supportsStashing()
+                && mPrefs.getBoolean(SHARED_PREFS_STASHED_KEY, DEFAULT_STASHED_PREF);
+    }
+
+    /**
+     * Returns whether the user can manually stash the taskbar based on the current device state.
+     */
+    private boolean supportsStashing() {
+        return !mActivity.isThreeButtonNav();
+    }
+
+    /**
+     * Returns whether the taskbar is currently visually stashed.
+     */
+    public boolean isStashed() {
+        return mIsStashed;
+    }
+
+    /**
+     * Returns whether the user has manually stashed the taskbar in apps.
+     */
+    public boolean isStashedInApp() {
+        return mIsStashedInApp;
+    }
+
+    public int getContentHeight() {
+        return isStashed() ? mStashedHeight : mUnstashedHeight;
+    }
+
+    public int getStashedHeight() {
+        return mStashedHeight;
+    }
+
+    /**
+     * Should be called when long pressing the nav region when taskbar is present.
+     * @return Whether taskbar was stashed and now is unstashed.
+     */
+    public boolean onLongPressToUnstashTaskbar() {
+        if (!isStashed()) {
+            // We only listen for long press on the nav region to unstash the taskbar. To stash the
+            // taskbar, we use an OnLongClickListener on TaskbarView instead.
+            return false;
+        }
+        if (updateAndAnimateIsStashedInApp(false)) {
+            mControllers.taskbarActivityContext.getDragLayer().performHapticFeedback(LONG_PRESS);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Updates whether we should stash the taskbar when in apps, and animates to the changed state.
+     * @return Whether we started an animation to either be newly stashed or unstashed.
+     */
+    public boolean updateAndAnimateIsStashedInApp(boolean isStashedInApp) {
+        if (!supportsStashing()) {
+            return false;
+        }
+        if (mIsStashedInApp != isStashedInApp) {
+            boolean wasStashed = mIsStashedInApp;
+            mIsStashedInApp = isStashedInApp;
+            mPrefs.edit().putBoolean(SHARED_PREFS_STASHED_KEY, mIsStashedInApp).apply();
+            boolean isStashed = mIsStashedInApp;
+            if (wasStashed != isStashed) {
+                createAnimToIsStashed(isStashed, TASKBAR_STASH_DURATION).start();
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Starts an animation to the new stashed state with a default duration.
+     */
+    public void animateToIsStashed(boolean isStashed) {
+        animateToIsStashed(isStashed, TASKBAR_STASH_DURATION);
+    }
+
+    /**
+     * Starts an animation to the new stashed state with the specified duration.
+     */
+    public void animateToIsStashed(boolean isStashed, long duration) {
+        createAnimToIsStashed(isStashed, duration).start();
+    }
+
+    private Animator createAnimToIsStashed(boolean isStashed, long duration) {
+        AnimatorSet fullLengthAnimatorSet = new AnimatorSet();
+        // Not exactly half and may overlap. See [first|second]HalfDurationScale below.
+        AnimatorSet firstHalfAnimatorSet = new AnimatorSet();
+        AnimatorSet secondHalfAnimatorSet = new AnimatorSet();
+
+        final float firstHalfDurationScale;
+        final float secondHalfDurationScale;
+
+        if (isStashed) {
+            firstHalfDurationScale = 0.75f;
+            secondHalfDurationScale = 0.5f;
+            final float stashTranslation = (mUnstashedHeight - mStashedHeight) / 2f;
+
+            fullLengthAnimatorSet.playTogether(
+                    mTaskbarBackgroundOffset.animateToValue(1),
+                    mIconTranslationYForStash.animateToValue(stashTranslation)
+            );
+            firstHalfAnimatorSet.playTogether(
+                    mIconAlphaForStash.animateToValue(0),
+                    mIconScaleForStash.animateToValue(STASHED_TASKBAR_SCALE)
+            );
+            secondHalfAnimatorSet.playTogether(
+                    mTaskbarStashedHandleAlpha.animateToValue(1)
+            );
+        } else  {
+            firstHalfDurationScale = 0.5f;
+            secondHalfDurationScale = 0.75f;
+
+            fullLengthAnimatorSet.playTogether(
+                    mTaskbarBackgroundOffset.animateToValue(0),
+                    mIconScaleForStash.animateToValue(1),
+                    mIconTranslationYForStash.animateToValue(0)
+            );
+            firstHalfAnimatorSet.playTogether(
+                    mTaskbarStashedHandleAlpha.animateToValue(0)
+            );
+            secondHalfAnimatorSet.playTogether(
+                    mIconAlphaForStash.animateToValue(1)
+            );
+        }
+
+        Animator stashedHandleRevealAnim = mControllers.stashedHandleViewController
+                .createRevealAnimToIsStashed(isStashed);
+        if (stashedHandleRevealAnim != null) {
+            fullLengthAnimatorSet.play(stashedHandleRevealAnim);
+        }
+
+        fullLengthAnimatorSet.setDuration(duration);
+        firstHalfAnimatorSet.setDuration((long) (duration * firstHalfDurationScale));
+        secondHalfAnimatorSet.setDuration((long) (duration * secondHalfDurationScale));
+        secondHalfAnimatorSet.setStartDelay((long) (duration * (1 - secondHalfDurationScale)));
+
+        if (mAnimator != null) {
+            mAnimator.cancel();
+        }
+        mAnimator = new AnimatorSet();
+        mAnimator.playTogether(fullLengthAnimatorSet, firstHalfAnimatorSet,
+                secondHalfAnimatorSet);
+        mAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                mIsStashed = isStashed;
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mAnimator = null;
+            }
+        });
+        return mAnimator;
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java
index a701aae..edd2a22 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java
@@ -16,11 +16,8 @@
 package com.android.launcher3.taskbar;
 
 import static com.android.launcher3.LauncherState.TASKBAR;
-import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 
-import androidx.annotation.Nullable;
-
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.anim.PendingAnimation;
@@ -28,29 +25,26 @@
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.quickstep.AnimatedFloat;
+import com.android.quickstep.SystemUiProxy;
 
 /**
- * StateHandler to animate Taskbar according to Launcher's state machine. Does nothing if Taskbar
- * isn't present (i.e. {@link #setAnimationController} is never called).
+ * StateHandler to animate Taskbar according to Launcher's state machine.
  */
 public class TaskbarStateHandler implements StateManager.StateHandler<LauncherState> {
 
     private final BaseQuickstepLauncher mLauncher;
 
-    // Contains Taskbar-related methods and fields we should aniamte. If null, don't do anything.
-    private @Nullable TaskbarAnimationController mAnimationController = null;
+    private AnimatedFloat mNavbarButtonAlpha = new AnimatedFloat(this::updateNavbarButtonAlpha);
 
     public TaskbarStateHandler(BaseQuickstepLauncher launcher) {
         mLauncher = launcher;
     }
 
-    public void setAnimationController(TaskbarAnimationController callbacks) {
-        mAnimationController = callbacks;
-    }
-
     @Override
     public void setState(LauncherState state) {
         setState(state, PropertySetter.NO_ANIM_PROPERTY_SETTER);
+        // Force update the alpha in case it was not initialized properly
+        updateNavbarButtonAlpha();
     }
 
     @Override
@@ -59,17 +53,19 @@
         setState(toState, animation);
     }
 
-    private void setState(LauncherState toState, PropertySetter setter) {
-        if (mAnimationController == null) {
-            return;
-        }
-
+    /**
+     * Sets the provided state
+     */
+    public void setState(LauncherState toState, PropertySetter setter) {
         boolean isTaskbarVisible = (toState.getVisibleElements(mLauncher) & TASKBAR) != 0;
-        setter.setFloat(mAnimationController.getTaskbarVisibilityForLauncherState(),
-                AnimatedFloat.VALUE, isTaskbarVisible ? 1f : 0f, LINEAR);
-        setter.setFloat(mAnimationController.getTaskbarScaleForLauncherState(),
-                AnimatedFloat.VALUE, toState.getTaskbarScale(mLauncher), LINEAR);
-        setter.setFloat(mAnimationController.getTaskbarTranslationYForLauncherState(),
-                AnimatedFloat.VALUE, toState.getTaskbarTranslationY(mLauncher), ACCEL_DEACCEL);
+        // Make the nav bar visible in states that taskbar isn't visible.
+        // TODO: We should draw our own handle instead of showing the nav bar.
+        float navbarButtonAlpha = isTaskbarVisible ? 0f : 1f;
+        setter.setFloat(mNavbarButtonAlpha, AnimatedFloat.VALUE, navbarButtonAlpha, LINEAR);
+    }
+
+
+    private void updateNavbarButtonAlpha() {
+        SystemUiProxy.INSTANCE.get(mLauncher).setNavBarButtonAlpha(mNavbarButtonAlpha.value, false);
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 50adead..6d0e3c6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.taskbar;
 
+import android.graphics.Rect;
+
 /**
  * Base class for providing different taskbar UI
  */
@@ -22,12 +24,7 @@
 
     public static final TaskbarUIController DEFAULT = new TaskbarUIController();
 
-    /**
-     * Pads the Hotseat to line up exactly with Taskbar's copy of the Hotseat.
-     */
-    public void alignRealHotseatWithTaskbar() { }
-
-    protected void onCreate() { }
+    protected void init(TaskbarControllers taskbarControllers) { }
 
     protected void onDestroy() { }
 
@@ -35,7 +32,9 @@
         return true;
     }
 
-    protected void onImeVisible(TaskbarDragLayer container, boolean isVisible) {
-        container.updateImeBarVisibilityAlpha(isVisible ? 1 : 0);
+    protected void updateContentInsets(Rect outContentInsets) { }
+
+    protected boolean onLongPressToUnstashTaskbar() {
+        return false;
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index c6573a6..820d40a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -15,76 +15,54 @@
  */
 package com.android.launcher3.taskbar;
 
-import static android.view.View.MeasureSpec.EXACTLY;
-import static android.view.View.MeasureSpec.makeMeasureSpec;
-
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.util.AttributeSet;
-import android.view.DragEvent;
-import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewConfiguration;
-import android.widget.LinearLayout;
+import android.widget.FrameLayout;
 
 import androidx.annotation.LayoutRes;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.uioverrides.ApiWrapper;
 import com.android.launcher3.views.ActivityContext;
 
 /**
  * Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps.
  */
-public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconParent, Insettable {
+public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconParent, Insettable {
 
-    private final int mIconTouchSize;
-    private final boolean mIsRtl;
-    private final int mTouchSlop;
-    private final RectF mTempDelegateBounds = new RectF();
-    private final RectF mDelegateSlopBounds = new RectF();
     private final int[] mTempOutLocation = new int[2];
 
+    private final Rect mIconLayoutBounds = new Rect();
+    private final int mIconTouchSize;
     private final int mItemMarginLeftRight;
+    private final int mItemPadding;
 
     private final TaskbarActivityContext mActivityContext;
 
-    // Initialized in TaskbarController constructor.
+    // Initialized in init.
+    private TaskbarViewController.TaskbarViewCallbacks mControllerCallbacks;
     private View.OnClickListener mIconClickListener;
     private View.OnLongClickListener mIconLongClickListener;
 
-    LinearLayout mSystemButtonContainer;
-    LinearLayout mHotseatIconsContainer;
-
-    // Delegate touches to the closest view if within mIconTouchSize.
-    private boolean mDelegateTargeted;
-    private View mDelegateView;
     // Prevents dispatching touches to children if true
     private boolean mTouchEnabled = true;
 
-    private boolean mIsDraggingItem;
     // Only non-null when the corresponding Folder is open.
     private @Nullable FolderIcon mLeaveBehindFolderIcon;
 
-    /** Provider of buttons added to taskbar in 3 button nav */
-    private ButtonProvider mButtonProvider;
-
-    private boolean mDisableRelayout;
-    private boolean mAreHolesAllowed;
-
     public TaskbarView(@NonNull Context context) {
         this(context, null);
     }
@@ -105,85 +83,76 @@
 
         Resources resources = getResources();
         mIconTouchSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_touch_size);
-        mItemMarginLeftRight = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
 
-        mIsRtl = Utilities.isRtl(resources);
-        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+        int actualMargin = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
+        int actualIconSize = mActivityContext.getDeviceProfile().iconSizePx;
+
+        // We layout the icons to be of mIconTouchSize in width and height
+        mItemMarginLeftRight = actualMargin - (mIconTouchSize - actualIconSize) / 2;
+        mItemPadding = (mIconTouchSize - actualIconSize) / 2;
     }
 
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mSystemButtonContainer = findViewById(R.id.system_button_layout);
-        mHotseatIconsContainer = findViewById(R.id.hotseat_icons_layout);
+    protected void init(TaskbarViewController.TaskbarViewCallbacks callbacks) {
+        mControllerCallbacks = callbacks;
+        mIconClickListener = mControllerCallbacks.getIconOnClickListener();
+        mIconLongClickListener = mControllerCallbacks.getIconOnLongClickListener();
+
+        setOnLongClickListener(mControllerCallbacks.getBackgroundOnLongClickListener());
     }
 
-    protected void construct(OnClickListener clickListener, OnLongClickListener longClickListener,
-                ButtonProvider buttonProvider) {
-        mIconClickListener = clickListener;
-        mIconLongClickListener = longClickListener;
-        mButtonProvider = buttonProvider;
-
-        if (mActivityContext.canShowNavButtons()) {
-            createNavButtons();
-        } else {
-            mSystemButtonContainer.setVisibility(GONE);
+    private void removeAndRecycle(View view) {
+        removeView(view);
+        view.setOnClickListener(null);
+        view.setOnLongClickListener(null);
+        if (!(view.getTag() instanceof FolderInfo)) {
+            mActivityContext.getViewCache().recycleView(view.getSourceLayoutResId(), view);
         }
-
-        int numHotseatIcons = mActivityContext.getDeviceProfile().numShownHotseatIcons;
-        updateHotseatItems(new ItemInfo[numHotseatIcons]);
-    }
-
-    /**
-     * Enables/disables empty icons in taskbar so that the layout matches with Launcher
-     */
-    public void setHolesAllowedInLayout(boolean areHolesAllowed) {
-        if (mAreHolesAllowed != areHolesAllowed) {
-            mAreHolesAllowed = areHolesAllowed;
-            updateHotseatItemsVisibility();
-            // TODO: Add animation
-        }
-    }
-
-    private void setHolesAllowedInLayoutNoAnimation(boolean areHolesAllowed) {
-        if (mAreHolesAllowed != areHolesAllowed) {
-            mAreHolesAllowed = areHolesAllowed;
-            updateHotseatItemsVisibility();
-            onMeasure(makeMeasureSpec(getMeasuredWidth(), EXACTLY),
-                    makeMeasureSpec(getMeasuredHeight(), EXACTLY));
-            onLayout(false, getLeft(), getTop(), getRight(), getBottom());
-        }
+        view.setTag(null);
     }
 
     /**
      * Inflates/binds the Hotseat views to show in the Taskbar given their ItemInfos.
      */
     protected void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
+        int nextViewIndex = 0;
+
         for (int i = 0; i < hotseatItemInfos.length; i++) {
-            ItemInfo hotseatItemInfo = hotseatItemInfos[
-                    !mIsRtl ? i : hotseatItemInfos.length - i - 1];
-            View hotseatView = mHotseatIconsContainer.getChildAt(i);
+            ItemInfo hotseatItemInfo = hotseatItemInfos[i];
+            if (hotseatItemInfo == null) {
+                continue;
+            }
 
             // Replace any Hotseat views with the appropriate type if it's not already that type.
             final int expectedLayoutResId;
             boolean isFolder = false;
-            boolean needsReinflate = false;
-            if (hotseatItemInfo != null && hotseatItemInfo.isPredictedItem()) {
+            if (hotseatItemInfo.isPredictedItem()) {
                 expectedLayoutResId = R.layout.taskbar_predicted_app_icon;
             } else if (hotseatItemInfo instanceof FolderInfo) {
                 expectedLayoutResId = R.layout.folder_icon;
                 isFolder = true;
-                // Unlike for BubbleTextView, we can't reapply a new FolderInfo after inflation, so
-                // if the info changes we need to reinflate. This should only happen if a new folder
-                // is dragged to the position that another folder previously existed.
-                needsReinflate = hotseatView != null && hotseatView.getTag() != hotseatItemInfo;
             } else {
                 expectedLayoutResId = R.layout.taskbar_app_icon;
             }
-            if (hotseatView == null
-                    || hotseatView.getSourceLayoutResId() != expectedLayoutResId
-                    || needsReinflate) {
-                mHotseatIconsContainer.removeView(hotseatView);
+
+            View hotseatView = null;
+            while (nextViewIndex < getChildCount()) {
+                hotseatView = getChildAt(nextViewIndex);
+
+                // see if the view can be reused
+                if ((hotseatView.getSourceLayoutResId() != expectedLayoutResId)
+                        || (isFolder && (hotseatView.getTag() != hotseatItemInfo))) {
+                    // Unlike for BubbleTextView, we can't reapply a new FolderInfo after inflation,
+                    // so if the info changes we need to reinflate. This should only happen if a new
+                    // folder is dragged to the position that another folder previously existed.
+                    removeAndRecycle(hotseatView);
+                    hotseatView = null;
+                } else {
+                    // View found
+                    break;
+                }
+            }
+
+            if (hotseatView == null) {
                 if (isFolder) {
                     FolderInfo folderInfo = (FolderInfo) hotseatItemInfo;
                     FolderIcon folderIcon = FolderIcon.inflateFolderAndIcon(expectedLayoutResId,
@@ -193,10 +162,9 @@
                 } else {
                     hotseatView = inflate(expectedLayoutResId);
                 }
-                int iconSize = mActivityContext.getDeviceProfile().iconSizePx;
-                LayoutParams lp = new LayoutParams(iconSize, iconSize);
-                lp.setMargins(mItemMarginLeftRight, 0, mItemMarginLeftRight, 0);
-                mHotseatIconsContainer.addView(hotseatView, i, lp);
+                LayoutParams lp = new LayoutParams(mIconTouchSize, mIconTouchSize);
+                hotseatView.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
+                addView(hotseatView, nextViewIndex, lp);
             }
 
             // Apply the Hotseat ItemInfos, or hide the view if there is none for a given index.
@@ -204,29 +172,46 @@
                     && hotseatItemInfo instanceof WorkspaceItemInfo) {
                 ((BubbleTextView) hotseatView).applyFromWorkspaceItem(
                         (WorkspaceItemInfo) hotseatItemInfo);
-                hotseatView.setOnClickListener(mIconClickListener);
-                hotseatView.setOnLongClickListener(mIconLongClickListener);
-            } else if (isFolder) {
-                hotseatView.setOnClickListener(mIconClickListener);
-                hotseatView.setOnLongClickListener(mIconLongClickListener);
-            } else {
-                hotseatView.setOnClickListener(null);
-                hotseatView.setOnLongClickListener(null);
-                hotseatView.setTag(null);
             }
-            updateHotseatItemVisibility(hotseatView);
+            setClickAndLongClickListenersForIcon(hotseatView);
+            nextViewIndex++;
+        }
+        // Remove remaining views
+        while (nextViewIndex < getChildCount()) {
+            removeAndRecycle(getChildAt(nextViewIndex));
         }
     }
 
-    protected void updateHotseatItemsVisibility() {
-        for (int i = mHotseatIconsContainer.getChildCount() - 1; i >= 0; i--) {
-            updateHotseatItemVisibility(mHotseatIconsContainer.getChildAt(i));
-        }
+    /**
+     * Sets OnClickListener and OnLongClickListener for the given view.
+     */
+    public void setClickAndLongClickListenersForIcon(View icon) {
+        icon.setOnClickListener(mIconClickListener);
+        icon.setOnLongClickListener(mIconLongClickListener);
     }
 
-    private void updateHotseatItemVisibility(View hotseatView) {
-        hotseatView.setVisibility(
-                hotseatView.getTag() != null ? VISIBLE : (mAreHolesAllowed ? INVISIBLE : GONE));
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        int count = getChildCount();
+        int spaceNeeded = count * (mItemMarginLeftRight * 2 + mIconTouchSize);
+        int iconStart = (right - left - spaceNeeded) / 2;
+        int startOffset = ApiWrapper.getHotseatStartOffset(getContext());
+        if (startOffset > iconStart) {
+            int diff = startOffset - iconStart;
+            iconStart = isLayoutRtl() ? (iconStart - diff) : iconStart + diff;
+        }
+        // Layout the children
+        mIconLayoutBounds.left = iconStart;
+        mIconLayoutBounds.top = (bottom - top - mIconTouchSize) / 2;
+        mIconLayoutBounds.bottom = mIconLayoutBounds.top + mIconTouchSize;
+        for (int i = 0; i < count; i++) {
+            View child = getChildAt(i);
+            iconStart += mItemMarginLeftRight;
+            int iconEnd = iconStart + mIconTouchSize;
+            child.layout(iconStart, mIconLayoutBounds.top, iconEnd, mIconLayoutBounds.bottom);
+            iconStart = iconEnd + mItemMarginLeftRight;
+        }
+        mIconLayoutBounds.right = iconStart;
     }
 
     @Override
@@ -237,160 +222,23 @@
         return super.dispatchTouchEvent(ev);
     }
 
-    @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        boolean handled = delegateTouchIfNecessary(event);
-        return super.onTouchEvent(event) || handled;
-    }
-
     public void setTouchesEnabled(boolean touchEnabled) {
         this.mTouchEnabled = touchEnabled;
     }
 
     /**
-     * User touched the Taskbar background. Determine whether the touch is close enough to a view
-     * that we should forward the touches to it.
-     * @return Whether a delegate view was chosen and it handled the touch event.
-     */
-    private boolean delegateTouchIfNecessary(MotionEvent event) {
-        final float x = event.getX();
-        final float y = event.getY();
-        if (mDelegateView == null && event.getAction() == MotionEvent.ACTION_DOWN) {
-            View delegateView = findDelegateView(x, y);
-            if (delegateView != null) {
-                mDelegateTargeted = true;
-                mDelegateView = delegateView;
-                mDelegateSlopBounds.set(mTempDelegateBounds);
-                mDelegateSlopBounds.inset(-mTouchSlop, -mTouchSlop);
-            }
-        }
-
-        boolean sendToDelegate = mDelegateTargeted;
-        boolean inBounds = true;
-        switch (event.getAction()) {
-            case MotionEvent.ACTION_MOVE:
-                inBounds = mDelegateSlopBounds.contains(x, y);
-                break;
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_CANCEL:
-                mDelegateTargeted = false;
-                break;
-        }
-
-        boolean handled = false;
-        if (sendToDelegate) {
-            if (inBounds) {
-                // Offset event coordinates to be inside the target view
-                event.setLocation(mDelegateView.getWidth() / 2f, mDelegateView.getHeight() / 2f);
-            } else {
-                // Offset event coordinates to be outside the target view (in case it does
-                // something like tracking pressed state)
-                event.setLocation(-mTouchSlop * 2, -mTouchSlop * 2);
-            }
-            handled = mDelegateView.dispatchTouchEvent(event);
-            // Cleanup if this was the last event to send to the delegate.
-            if (!mDelegateTargeted) {
-                mDelegateView = null;
-            }
-        }
-        return handled;
-    }
-
-    /**
-     * Return an item whose touch bounds contain the given coordinates,
-     * or null if no such item exists.
-     *
-     * Also sets {@link #mTempDelegateBounds} to be the touch bounds of the chosen delegate view.
-     */
-    private @Nullable View findDelegateView(float x, float y) {
-        for (int i = 0; i < getChildCount(); i++) {
-            View child = getChildAt(i);
-            if (!child.isShown() || !child.isClickable()) {
-                continue;
-            }
-            int childCenterX = child.getLeft() + child.getWidth() / 2;
-            int childCenterY = child.getTop() + child.getHeight() / 2;
-            mTempDelegateBounds.set(
-                    childCenterX - mIconTouchSize / 2f,
-                    childCenterY - mIconTouchSize / 2f,
-                    childCenterX + mIconTouchSize / 2f,
-                    childCenterY + mIconTouchSize / 2f);
-            if (mTempDelegateBounds.contains(x, y)) {
-                return child;
-            }
-        }
-        return null;
-    }
-
-    /**
      * Returns whether the given MotionEvent, *in screen coorindates*, is within any Taskbar item's
      * touch bounds.
      */
     public boolean isEventOverAnyItem(MotionEvent ev) {
         getLocationOnScreen(mTempOutLocation);
-        float xInOurCoordinates = ev.getX() - mTempOutLocation[0];
-        float yInOurCoorindates = ev.getY() - mTempOutLocation[1];
-        return findDelegateView(xInOurCoordinates, yInOurCoorindates) != null;
+        int xInOurCoordinates = (int) ev.getX() - mTempOutLocation[0];
+        int yInOurCoorindates = (int) ev.getY() - mTempOutLocation[1];
+        return isShown() && mIconLayoutBounds.contains(xInOurCoordinates, yInOurCoorindates);
     }
 
-    /**
-     * Add back/home/recents buttons into a single ViewGroup that will be inserted at
-     * {@param navButtonStartIndex}
-     */
-    private void createNavButtons() {
-        LinearLayout.LayoutParams buttonParams = new LinearLayout.LayoutParams(
-                mActivityContext.getDeviceProfile().iconSizePx,
-                mActivityContext.getDeviceProfile().iconSizePx
-        );
-        buttonParams.gravity = Gravity.CENTER;
-
-        mSystemButtonContainer.addView(mButtonProvider.getBack(), buttonParams);
-        mSystemButtonContainer.addView(mButtonProvider.getHome(), buttonParams);
-        mSystemButtonContainer.addView(mButtonProvider.getRecents(), buttonParams);
-    }
-
-    @Override
-    public boolean onDragEvent(DragEvent event) {
-        switch (event.getAction()) {
-            case DragEvent.ACTION_DRAG_STARTED:
-                mIsDraggingItem = true;
-                AbstractFloatingView.closeAllOpenViews(mActivityContext);
-                return true;
-            case DragEvent.ACTION_DRAG_ENDED:
-                mIsDraggingItem = false;
-                break;
-        }
-        return super.onDragEvent(event);
-    }
-
-    public boolean isDraggingItem() {
-        return mIsDraggingItem;
-    }
-
-    /**
-     * @return The bounding box of where the hotseat elements are relative to this TaskbarView.
-     */
-    protected RectF getHotseatBounds() {
-        RectF result;
-        mDisableRelayout = true;
-        boolean wereHolesAllowed = mAreHolesAllowed;
-        setHolesAllowedInLayoutNoAnimation(true);
-        result = new RectF(
-                mHotseatIconsContainer.getLeft(),
-                mHotseatIconsContainer.getTop(),
-                mHotseatIconsContainer.getRight(),
-                mHotseatIconsContainer.getBottom());
-        setHolesAllowedInLayoutNoAnimation(wereHolesAllowed);
-        mDisableRelayout = false;
-
-        return result;
-    }
-
-    @Override
-    public void requestLayout() {
-        if (!mDisableRelayout) {
-            super.requestLayout();
-        }
+    public Rect getIconLayoutBounds() {
+        return mIconLayoutBounds;
     }
 
     // FolderIconParent implemented methods.
@@ -421,7 +269,7 @@
     }
 
     private View inflate(@LayoutRes int layoutResId) {
-        return mActivityContext.getLayoutInflater().inflate(layoutResId, this, false);
+        return mActivityContext.getViewCache().getView(layoutResId, mActivityContext, this);
     }
 
     @Override
@@ -429,11 +277,8 @@
         // Ignore, we just implement Insettable to draw behind system insets.
     }
 
-    public void setIconsVisibility(boolean isVisible) {
-        mHotseatIconsContainer.setVisibility(isVisible ? VISIBLE : INVISIBLE);
-    }
-
     public boolean areIconsVisible() {
-        return mHotseatIconsContainer.getVisibility() == VISIBLE;
+        // Consider the overall visibility
+        return getVisibility() == VISIBLE;
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
new file mode 100644
index 0000000..50c26b3
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.quickstep.AnimatedFloat.VALUE;
+
+import android.graphics.Rect;
+import android.view.View;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.util.MultiValueAlpha;
+import com.android.quickstep.AnimatedFloat;
+
+/**
+ * Handles properties/data collection, then passes the results to TaskbarView to render.
+ */
+public class TaskbarViewController {
+    private static final Runnable NO_OP = () -> { };
+
+    public static final int ALPHA_INDEX_HOME = 0;
+    public static final int ALPHA_INDEX_IME = 1;
+    public static final int ALPHA_INDEX_KEYGUARD = 2;
+    public static final int ALPHA_INDEX_STASH = 3;
+
+    private final TaskbarActivityContext mActivity;
+    private final TaskbarView mTaskbarView;
+    private final MultiValueAlpha mTaskbarIconAlpha;
+    private final AnimatedFloat mTaskbarIconScaleForStash = new AnimatedFloat(this::updateScale);
+    private final AnimatedFloat mTaskbarIconTranslationYForHome = new AnimatedFloat(
+            this::updateTranslationY);
+    private final AnimatedFloat mTaskbarIconTranslationYForStash = new AnimatedFloat(
+            this::updateTranslationY);
+
+    // Initialized in init.
+    private TaskbarControllers mControllers;
+
+    // Animation to align icons with Launcher, created lazily. This allows the controller to be
+    // active only during the animation and does not need to worry about layout changes.
+    private AnimatorPlaybackController mIconAlignControllerLazy = null;
+    private Runnable mOnControllerPreCreateCallback = NO_OP;
+
+    public TaskbarViewController(TaskbarActivityContext activity, TaskbarView taskbarView) {
+        mActivity = activity;
+        mTaskbarView = taskbarView;
+        mTaskbarIconAlpha = new MultiValueAlpha(mTaskbarView, 4);
+        mTaskbarIconAlpha.setUpdateVisibility(true);
+    }
+
+    public void init(TaskbarControllers controllers) {
+        mControllers = controllers;
+        mTaskbarView.init(new TaskbarViewCallbacks());
+        mTaskbarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize;
+
+        mTaskbarIconScaleForStash.updateValue(1f);
+    }
+
+    public boolean areIconsVisible() {
+        return mTaskbarView.areIconsVisible();
+    }
+
+    public MultiValueAlpha getTaskbarIconAlpha() {
+        return mTaskbarIconAlpha;
+    }
+
+    /**
+     * Should be called when the IME visibility changes, so we can make Taskbar not steal touches.
+     */
+    public void setImeIsVisible(boolean isImeVisible) {
+        mTaskbarView.setTouchesEnabled(!isImeVisible);
+    }
+
+    /**
+     * Sets OnClickListener and OnLongClickListener for the given view.
+     */
+    public void setClickAndLongClickListenersForIcon(View icon) {
+        mTaskbarView.setClickAndLongClickListenersForIcon(icon);
+    }
+
+    public Rect getIconLayoutBounds() {
+        return mTaskbarView.getIconLayoutBounds();
+    }
+
+    public AnimatedFloat getTaskbarIconScaleForStash() {
+        return mTaskbarIconScaleForStash;
+    }
+
+    public AnimatedFloat getTaskbarIconTranslationYForStash() {
+        return mTaskbarIconTranslationYForStash;
+    }
+
+    /**
+     * Applies scale properties for the entire TaskbarView (rather than individual icons).
+     */
+    private void updateScale() {
+        float scale = mTaskbarIconScaleForStash.value;
+        mTaskbarView.setScaleX(scale);
+        mTaskbarView.setScaleY(scale);
+    }
+
+    private void updateTranslationY() {
+        mTaskbarView.setTranslationY(mTaskbarIconTranslationYForHome.value
+                + mTaskbarIconTranslationYForStash.value);
+    }
+
+    /**
+     * Sets the taskbar icon alignment relative to Launcher hotseat icons
+     * @param alignmentRatio [0, 1]
+     *                       0 => not aligned
+     *                       1 => fully aligned
+     */
+    public void setLauncherIconAlignment(float alignmentRatio, DeviceProfile launcherDp) {
+        if (mIconAlignControllerLazy == null) {
+            mIconAlignControllerLazy = createIconAlignmentController(launcherDp);
+        }
+        mIconAlignControllerLazy.setPlayFraction(alignmentRatio);
+        if (alignmentRatio <= 0 || alignmentRatio >= 1) {
+            // Cleanup lazy controller so that it is created again in next animation
+            mIconAlignControllerLazy = null;
+        }
+    }
+
+    /**
+     * Creates an animation for aligning the taskbar icons with the provided Launcher device profile
+     */
+    private AnimatorPlaybackController createIconAlignmentController(DeviceProfile launcherDp) {
+        mOnControllerPreCreateCallback.run();
+        PendingAnimation setter = new PendingAnimation(100);
+        Rect hotseatPadding = launcherDp.getHotseatLayoutPadding(mActivity);
+        float scaleUp = ((float) launcherDp.iconSizePx) / mActivity.getDeviceProfile().iconSizePx;
+        int hotseatCellSize =
+                (launcherDp.availableWidthPx - hotseatPadding.left - hotseatPadding.right)
+                        / launcherDp.numShownHotseatIcons;
+
+        int offsetY = launcherDp.getTaskbarOffsetY();
+        setter.setFloat(mTaskbarIconTranslationYForHome, VALUE, -offsetY, LINEAR);
+
+        int collapsedHeight = mActivity.getDeviceProfile().taskbarSize;
+        int expandedHeight = collapsedHeight + offsetY;
+        setter.addOnFrameListener(anim -> mActivity.setTaskbarWindowHeight(
+                anim.getAnimatedFraction() > 0 ? expandedHeight : collapsedHeight));
+
+        int count = mTaskbarView.getChildCount();
+        for (int i = 0; i < count; i++) {
+            View child = mTaskbarView.getChildAt(i);
+            ItemInfo info = (ItemInfo) child.getTag();
+            setter.setFloat(child, SCALE_PROPERTY, scaleUp, LINEAR);
+
+            float childCenter = (child.getLeft() + child.getRight()) / 2;
+            float hotseatIconCenter = hotseatPadding.left + hotseatCellSize * info.screenId
+                    + hotseatCellSize / 2;
+            setter.setFloat(child, VIEW_TRANSLATE_X, hotseatIconCenter - childCenter, LINEAR);
+        }
+
+        AnimatorPlaybackController controller = setter.createPlaybackController();
+        mOnControllerPreCreateCallback = () -> controller.setPlayFraction(0);
+        return controller;
+    }
+
+    /**
+     * Callbacks for {@link TaskbarView} to interact with its controller.
+     */
+    public class TaskbarViewCallbacks {
+        public View.OnClickListener getIconOnClickListener() {
+            return mActivity::onTaskbarIconClicked;
+        }
+
+        public View.OnLongClickListener getIconOnLongClickListener() {
+            return mControllers.taskbarDragController::startDragOnLongClick;
+        }
+
+        public View.OnLongClickListener getBackgroundOnLongClickListener() {
+            return view -> mControllers.taskbarStashController.updateAndAnimateIsStashedInApp(true);
+        }
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButton.java b/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButton.java
new file mode 100644
index 0000000..4093097
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButton.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.contextual;
+
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.view.View;
+
+/**
+ * Interface of a rotation button that interacts {@link RotationButtonController}.
+ * This interface exists because of the two different styles of rotation button in Sysui,
+ * one in contextual for 3 button nav and a floating rotation button for gestural.
+ * Keeping the interface for eventual migration of floating button, so some methods are
+ * pass through to "super" while others are trivially implemented.
+ *
+ * Changes:
+ *  * Directly use AnimatedVectorDrawable instead of KeyButtonDrawable
+ */
+public interface RotationButton {
+    default void setRotationButtonController(RotationButtonController rotationButtonController) { }
+
+    default View getCurrentView() {
+        return null;
+    }
+    default void show() { }
+    default void hide() { }
+    default boolean isVisible() {
+        return false;
+    }
+
+    default void updateIcon(int lightIconColor, int darkIconColor) { }
+    default void setOnClickListener(View.OnClickListener onClickListener) { }
+    default void setOnHoverListener(View.OnHoverListener onHoverListener) { }
+    default AnimatedVectorDrawable getImageDrawable() {
+        return null;
+    }
+    default void setDarkIntensity(float darkIntensity) { }
+    default boolean acceptRotationProposal() {
+        return getCurrentView() != null;
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButtonController.java b/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButtonController.java
new file mode 100644
index 0000000..99dc282
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButtonController.java
@@ -0,0 +1,512 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.contextual;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.internal.view.RotationPolicy.NATURAL_ROTATION;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
+import android.annotation.SuppressLint;
+import android.app.StatusBarManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.IRotationWatcher;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.View;
+import android.view.WindowInsetsController;
+import android.view.WindowManagerGlobal;
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.logging.UiEventLoggerImpl;
+import com.android.internal.view.RotationPolicy;
+import com.android.launcher3.R;
+import com.android.launcher3.util.DisplayController;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.utilities.ViewRippler;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
+
+import java.util.Optional;
+
+/**
+ * Copied over from the SysUI equivalent class. Known issues/things not ported over
+ *  * When rotation button visible and in auto-hide mode, we ask auto-hide controller to
+ *    keep the navbar around longer. Will need to implement if we use auto-hide on taskbar
+ *
+ * Contains logic that deals with showing a rotate suggestion button with animation.
+ */
+public class RotationButtonController {
+
+    private static final String TAG = "StatusBar/RotationButtonController";
+    private static final int BUTTON_FADE_IN_OUT_DURATION_MS = 100;
+    private static final int NAVBAR_HIDDEN_PENDING_ICON_TIMEOUT_MS = 20000;
+
+    private static final int NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION = 3;
+
+    private final Context mContext;
+    private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
+    private final UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
+    private final ViewRippler mViewRippler = new ViewRippler();
+    private final DisplayController mDisplayController;
+    private RotationButton mRotationButton;
+
+    private int mLastRotationSuggestion;
+    private boolean mPendingRotationSuggestion;
+    private boolean mHoveringRotationSuggestion;
+    private final AccessibilityManager mAccessibilityManager;
+    private final TaskStackListenerImpl mTaskStackListener;
+    private boolean mListenersRegistered = false;
+    private boolean mIsTaskbarShowing;
+    @SuppressLint("InlinedApi")
+    private @WindowInsetsController.Behavior
+    int mBehavior = WindowInsetsController.BEHAVIOR_DEFAULT;
+    private boolean mSkipOverrideUserLockPrefsOnce;
+    private final int mLightIconColor;
+    private final int mDarkIconColor;
+    private int mIconResId = R.drawable.ic_sysbar_rotate_button_ccw_start_90;
+
+    private final Runnable mRemoveRotationProposal =
+            () -> setRotateSuggestionButtonState(false /* visible */);
+    private final Runnable mCancelPendingRotationProposal =
+            () -> mPendingRotationSuggestion = false;
+    private Animator mRotateHideAnimator;
+
+
+    private final IRotationWatcher.Stub mRotationWatcher = new IRotationWatcher.Stub() {
+        @Override
+        public void onRotationChanged(final int rotation) {
+            // We need this to be scheduled as early as possible to beat the redrawing of
+            // window in response to the orientation change.
+            mMainThreadHandler.postAtFrontOfQueue(() -> {
+                // If the screen rotation changes while locked, potentially update lock to flow with
+                // new screen rotation and hide any showing suggestions.
+                if (isRotationLocked()) {
+                    if (shouldOverrideUserLockPrefs(rotation)) {
+                        setRotationLockedAtAngle(rotation);
+                    }
+                    setRotateSuggestionButtonState(false /* visible */, true /* forced */);
+                }
+            });
+        }
+    };
+
+    /**
+     * Determines if rotation suggestions disabled2 flag exists in flag
+     * @param disable2Flags see if rotation suggestion flag exists in this flag
+     * @return whether flag exists
+     */
+    static boolean hasDisable2RotateSuggestionFlag(int disable2Flags) {
+        return (disable2Flags & StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS) != 0;
+    }
+
+    public RotationButtonController(Context context, @ColorInt int lightIconColor,
+            @ColorInt int darkIconColor) {
+        mContext = context;
+        mLightIconColor = lightIconColor;
+        mDarkIconColor = darkIconColor;
+
+        mAccessibilityManager = AccessibilityManager.getInstance(context);
+        mTaskStackListener = new TaskStackListenerImpl();
+        mDisplayController = DisplayController.INSTANCE.get(context);
+    }
+
+    public void setRotationButton(RotationButton rotationButton) {
+        mRotationButton = rotationButton;
+        mRotationButton.setRotationButtonController(this);
+        mRotationButton.setOnClickListener(this::onRotateSuggestionClick);
+        mRotationButton.setOnHoverListener(this::onRotateSuggestionHover);
+    }
+
+    public void init() {
+        registerListeners();
+        if (mDisplayController.getInfo().id != DEFAULT_DISPLAY) {
+            // Currently there is no accelerometer sensor on non-default display, disable fixed
+            // rotation for non-default display
+            onDisable2FlagChanged(StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS);
+        }
+    }
+
+    public void onDestroy() {
+        unregisterListeners();
+    }
+
+    private void registerListeners() {
+        if (mListenersRegistered) {
+            return;
+        }
+
+        mListenersRegistered = true;
+        try {
+            WindowManagerGlobal.getWindowManagerService()
+                    .watchRotation(mRotationWatcher, mDisplayController.getInfo().id);
+        } catch (IllegalArgumentException e) {
+            mListenersRegistered = false;
+            Log.w(TAG, "RegisterListeners for the display failed");
+        } catch (RemoteException e) {
+            Log.e(TAG, "RegisterListeners caught a RemoteException", e);
+            return;
+        }
+
+        TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
+    }
+
+    void unregisterListeners() {
+        if (!mListenersRegistered) {
+            return;
+        }
+
+        mListenersRegistered = false;
+        try {
+            WindowManagerGlobal.getWindowManagerService().removeRotationWatcher(mRotationWatcher);
+        } catch (RemoteException e) {
+            Log.e(TAG, "UnregisterListeners caught a RemoteException", e);
+            return;
+        }
+
+        TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
+    }
+
+    void setRotationLockedAtAngle(int rotationSuggestion) {
+        RotationPolicy.setRotationLockAtAngle(mContext, true, rotationSuggestion);
+    }
+
+    public boolean isRotationLocked() {
+        return RotationPolicy.isRotationLocked(mContext);
+    }
+
+    public void setRotateSuggestionButtonState(boolean visible) {
+        setRotateSuggestionButtonState(visible, false /* force */);
+    }
+
+    void setRotateSuggestionButtonState(final boolean visible, final boolean force) {
+        // At any point the button can become invisible because an a11y service became active.
+        // Similarly, a call to make the button visible may be rejected because an a11y service is
+        // active. Must account for this.
+        // Rerun a show animation to indicate change but don't rerun a hide animation
+        if (!visible && !mRotationButton.isVisible()) return;
+
+        final View view = mRotationButton.getCurrentView();
+        if (view == null) return;
+
+        final AnimatedVectorDrawable currentDrawable = mRotationButton.getImageDrawable();
+        if (currentDrawable == null) return;
+
+        // Clear any pending suggestion flag as it has either been nullified or is being shown
+        mPendingRotationSuggestion = false;
+        mMainThreadHandler.removeCallbacks(mCancelPendingRotationProposal);
+
+        // Handle the visibility change and animation
+        if (visible) { // Appear and change (cannot force)
+            // Stop and clear any currently running hide animations
+            if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
+                mRotateHideAnimator.cancel();
+            }
+            mRotateHideAnimator = null;
+
+            // Reset the alpha if any has changed due to hide animation
+            view.setAlpha(1f);
+
+            // Run the rotate icon's animation if it has one
+            currentDrawable.reset();
+            currentDrawable.start();
+
+            // TODO(b/187754252): No idea why this doesn't work. If we remove the "false"
+            //  we see the animation show the pressed state... but it only shows the first time.
+            if (!isRotateSuggestionIntroduced()) mViewRippler.start(view);
+
+            // Set visibility unless a11y service is active.
+            mRotationButton.show();
+        } else { // Hide
+            mViewRippler.stop(); // Prevent any pending ripples, force hide or not
+
+            if (force) {
+                // If a hide animator is running stop it and make invisible
+                if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
+                    mRotateHideAnimator.pause();
+                }
+                mRotationButton.hide();
+                return;
+            }
+
+            // Don't start any new hide animations if one is running
+            if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return;
+
+            ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, "alpha", 0f);
+            fadeOut.setDuration(BUTTON_FADE_IN_OUT_DURATION_MS);
+            fadeOut.setInterpolator(LINEAR);
+            fadeOut.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mRotationButton.hide();
+                }
+            });
+
+            mRotateHideAnimator = fadeOut;
+            fadeOut.start();
+        }
+    }
+
+    void setDarkIntensity(float darkIntensity) {
+        mRotationButton.setDarkIntensity(darkIntensity);
+    }
+
+    public void onRotationProposal(int rotation, boolean isValid) {
+        int windowRotation = mDisplayController.getInfo().rotation;
+
+        if (!mRotationButton.acceptRotationProposal()) {
+            return;
+        }
+
+        // This method will be called on rotation suggestion changes even if the proposed rotation
+        // is not valid for the top app. Use invalid rotation choices as a signal to remove the
+        // rotate button if shown.
+        if (!isValid) {
+            setRotateSuggestionButtonState(false /* visible */);
+            return;
+        }
+
+        // If window rotation matches suggested rotation, remove any current suggestions
+        if (rotation == windowRotation) {
+            mMainThreadHandler.removeCallbacks(mRemoveRotationProposal);
+            setRotateSuggestionButtonState(false /* visible */);
+            return;
+        }
+
+        // Prepare to show the navbar icon by updating the icon style to change anim params
+        mLastRotationSuggestion = rotation; // Remember rotation for click
+        final boolean rotationCCW = Utilities.isRotationAnimationCCW(windowRotation, rotation);
+        if (windowRotation == Surface.ROTATION_0 || windowRotation == Surface.ROTATION_180) {
+            mIconResId = rotationCCW
+                    ? R.drawable.ic_sysbar_rotate_button_ccw_start_90
+                    : R.drawable.ic_sysbar_rotate_button_cw_start_90;
+        } else { // 90 or 270
+            mIconResId = rotationCCW
+                    ? R.drawable.ic_sysbar_rotate_button_ccw_start_0
+                    : R.drawable.ic_sysbar_rotate_button_ccw_start_0;
+        }
+        mRotationButton.updateIcon(mLightIconColor, mDarkIconColor);
+
+        if (canShowRotationButton()) {
+            // The navbar is visible / it's in visual immersive mode, so show the icon right away
+            showAndLogRotationSuggestion();
+        } else {
+            // If the navbar isn't shown, flag the rotate icon to be shown should the navbar become
+            // visible given some time limit.
+            mPendingRotationSuggestion = true;
+            mMainThreadHandler.removeCallbacks(mCancelPendingRotationProposal);
+            mMainThreadHandler.postDelayed(mCancelPendingRotationProposal,
+                    NAVBAR_HIDDEN_PENDING_ICON_TIMEOUT_MS);
+        }
+    }
+
+    public void onDisable2FlagChanged(int state2) {
+        final boolean rotateSuggestionsDisabled = hasDisable2RotateSuggestionFlag(state2);
+        if (rotateSuggestionsDisabled) onRotationSuggestionsDisabled();
+    }
+
+    public void onBehaviorChanged(int displayId, @WindowInsetsController.Behavior int behavior) {
+        if (mDisplayController.getInfo().id != displayId) {
+            return;
+        }
+
+        if (mBehavior != behavior) {
+            mBehavior = behavior;
+            showPendingRotationButtonIfNeeded();
+        }
+    }
+
+    public void onTaskBarVisibilityChange(boolean showing) {
+        if (mIsTaskbarShowing != showing) {
+            mIsTaskbarShowing = showing;
+            showPendingRotationButtonIfNeeded();
+        }
+    }
+
+    private void showPendingRotationButtonIfNeeded() {
+        if (canShowRotationButton() && mPendingRotationSuggestion) {
+            showAndLogRotationSuggestion();
+        }
+    }
+
+    /** Return true when either the task bar is visible or it's in visual immersive mode. */
+    @SuppressLint("InlinedApi")
+    private boolean canShowRotationButton() {
+        return mIsTaskbarShowing || mBehavior == WindowInsetsController.BEHAVIOR_DEFAULT;
+    }
+
+    public @DrawableRes
+    int getIconResId() {
+        return mIconResId;
+    }
+
+    public @ColorInt int getLightIconColor() {
+        return mLightIconColor;
+    }
+
+    public @ColorInt int getDarkIconColor() {
+        return mDarkIconColor;
+    }
+
+    private void onRotateSuggestionClick(View v) {
+        mUiEventLogger.log(RotationButtonEvent.ROTATION_SUGGESTION_ACCEPTED);
+        incrementNumAcceptedRotationSuggestionsIfNeeded();
+        setRotationLockedAtAngle(mLastRotationSuggestion);
+    }
+
+    private boolean onRotateSuggestionHover(View v, MotionEvent event) {
+        final int action = event.getActionMasked();
+        mHoveringRotationSuggestion = (action == MotionEvent.ACTION_HOVER_ENTER)
+                || (action == MotionEvent.ACTION_HOVER_MOVE);
+        rescheduleRotationTimeout(true /* reasonHover */);
+        return false; // Must return false so a11y hover events are dispatched correctly.
+    }
+
+    private void onRotationSuggestionsDisabled() {
+        // Immediately hide the rotate button and clear any planned removal
+        setRotateSuggestionButtonState(false /* visible */, true /* force */);
+        mMainThreadHandler.removeCallbacks(mRemoveRotationProposal);
+    }
+
+    private void showAndLogRotationSuggestion() {
+        setRotateSuggestionButtonState(true /* visible */);
+        rescheduleRotationTimeout(false /* reasonHover */);
+        mUiEventLogger.log(RotationButtonEvent.ROTATION_SUGGESTION_SHOWN);
+    }
+
+    /**
+     * Makes {@link #shouldOverrideUserLockPrefs} always return {@code false} once. It is used to
+     * avoid losing original user rotation when display rotation is changed by entering the fixed
+     * orientation overview.
+     */
+    void setSkipOverrideUserLockPrefsOnce() {
+        mSkipOverrideUserLockPrefsOnce = true;
+    }
+
+    private boolean shouldOverrideUserLockPrefs(final int rotation) {
+        if (mSkipOverrideUserLockPrefsOnce) {
+            mSkipOverrideUserLockPrefsOnce = false;
+            return false;
+        }
+        // Only override user prefs when returning to the natural rotation (normally portrait).
+        // Don't let apps that force landscape or 180 alter user lock.
+        return rotation == NATURAL_ROTATION;
+    }
+
+    private void rescheduleRotationTimeout(final boolean reasonHover) {
+        // May be called due to a new rotation proposal or a change in hover state
+        if (reasonHover) {
+            // Don't reschedule if a hide animator is running
+            if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return;
+            // Don't reschedule if not visible
+            if (!mRotationButton.isVisible()) return;
+        }
+
+        // Stop any pending removal
+        mMainThreadHandler.removeCallbacks(mRemoveRotationProposal);
+        // Schedule timeout
+        mMainThreadHandler.postDelayed(mRemoveRotationProposal,
+                computeRotationProposalTimeout());
+    }
+
+    private int computeRotationProposalTimeout() {
+        return mAccessibilityManager.getRecommendedTimeoutMillis(
+                mHoveringRotationSuggestion ? 16000 : 5000,
+                AccessibilityManager.FLAG_CONTENT_CONTROLS);
+    }
+
+    private boolean isRotateSuggestionIntroduced() {
+        ContentResolver cr = mContext.getContentResolver();
+        return Settings.Secure.getInt(cr, Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED, 0)
+                >= NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION;
+    }
+
+    private void incrementNumAcceptedRotationSuggestionsIfNeeded() {
+        // Get the number of accepted suggestions
+        ContentResolver cr = mContext.getContentResolver();
+        final int numSuggestions = Settings.Secure.getInt(cr,
+                Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED, 0);
+
+        // Increment the number of accepted suggestions only if it would change intro mode
+        if (numSuggestions < NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION) {
+            Settings.Secure.putInt(cr, Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED,
+                    numSuggestions + 1);
+        }
+    }
+
+    private class TaskStackListenerImpl extends TaskStackChangeListener {
+        // Invalidate any rotation suggestion on task change or activity orientation change
+        // Note: all callbacks happen on main thread
+
+        @Override
+        public void onTaskStackChanged() {
+            setRotateSuggestionButtonState(false /* visible */);
+        }
+
+        @Override
+        public void onTaskRemoved(int taskId) {
+            setRotateSuggestionButtonState(false /* visible */);
+        }
+
+        @Override
+        public void onTaskMovedToFront(int taskId) {
+            setRotateSuggestionButtonState(false /* visible */);
+        }
+
+        @Override
+        public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) {
+            // Only hide the icon if the top task changes its requestedOrientation
+            // Launcher can alter its requestedOrientation while it's not on top, don't hide on this
+            Optional.ofNullable(ActivityManagerWrapper.getInstance())
+                    .map(ActivityManagerWrapper::getRunningTask)
+                    .ifPresent(a -> {
+                        if (a.id == taskId) setRotateSuggestionButtonState(false /* visible */);
+                    });
+        }
+    }
+
+    enum RotationButtonEvent implements UiEventLogger.UiEventEnum {
+        @UiEvent(doc = "The rotation button was shown")
+        ROTATION_SUGGESTION_SHOWN(206),
+        @UiEvent(doc = "The rotation button was clicked")
+        ROTATION_SUGGESTION_ACCEPTED(207);
+
+        private final int mId;
+        RotationButtonEvent(int id) {
+            mId = id;
+        }
+        @Override public int getId() {
+            return mId;
+        }
+    }
+}
+
diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
index 76a5782..a595f54 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -17,10 +17,15 @@
 package com.android.launcher3.uioverrides;
 
 import android.app.Person;
+import android.content.Context;
 import android.content.pm.ShortcutInfo;
+import android.content.res.Resources;
 import android.view.Display;
 
+import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.SysUINavigationMode.Mode;
 
 public class ApiWrapper {
 
@@ -37,4 +42,18 @@
     public static boolean isInternalDisplay(Display display) {
         return display.getType() == Display.TYPE_INTERNAL;
     }
+
+    /**
+     * Returns the minimum space that should be left empty at the start of hotseat
+     */
+    public static int getHotseatStartOffset(Context context) {
+        if (SysUINavigationMode.INSTANCE.get(context).getMode() == Mode.THREE_BUTTONS) {
+            Resources res = context.getResources();
+            return 2 * res.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_spacing)
+                    + 3 * res.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size);
+        } else {
+            return 0;
+        }
+
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 1d52315..d74b6c5 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -17,6 +17,8 @@
 package com.android.launcher3.uioverrides;
 
 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
+import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
+import static com.android.launcher3.anim.Interpolators.INSTANT;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL;
@@ -112,8 +114,9 @@
                 mRecentsView, getTaskModalnessProperty(),
                 toState.getOverviewModalness(),
                 config.getInterpolator(ANIM_OVERVIEW_MODAL, LINEAR));
-        setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS,
-                toState.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()) ? 1f : 0f, LINEAR);
+        boolean showAsGrid = toState.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile());
+        setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS, showAsGrid ? 1f : 0f,
+                showAsGrid ? INSTANT : FINAL_FRAME);
     }
 
     abstract FloatProperty getTaskModalnessProperty();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 996d36a..6cad3dd 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -106,8 +106,7 @@
         float clearAllButtonAlpha = state.areElementsVisible(mLauncher, CLEAR_ALL_BUTTON) ? 1 : 0;
         propertySetter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
                 clearAllButtonAlpha, LINEAR);
-        float overviewButtonAlpha = state.areElementsVisible(mLauncher, OVERVIEW_ACTIONS)
-                && mRecentsView.shouldShowOverviewActionsForState(state) ? 1 : 0;
+        float overviewButtonAlpha = state.areElementsVisible(mLauncher, OVERVIEW_ACTIONS) ? 1 : 0;
         propertySetter.setFloat(mLauncher.getActionsView().getVisibilityAlpha(),
                 MultiValueAlpha.VALUE, overviewButtonAlpha, config.getInterpolator(
                         ANIM_OVERVIEW_ACTIONS_FADE, LINEAR));
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 8c128c8..90e17c0 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -83,16 +83,6 @@
     }
 
     @Override
-    public float getTaskbarScale(Launcher launcher) {
-        return 1f;
-    }
-
-    @Override
-    public float getTaskbarTranslationY(Launcher launcher) {
-        return 0f;
-    }
-
-    @Override
     public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
         return new PageAlphaProvider(DEACCEL_2) {
             @Override
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 7e45369..096ac6c 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -743,6 +743,8 @@
             mRecentsAnimationStartCallbacks.clear();
         }
 
+        TaskViewUtils.setDividerBarShown(mRecentsAnimationTargets.nonApps, false);
+
         // Only add the callback to enable the input consumer after we actually have the controller
         mStateCallback.runOnceAtState(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
                 mRecentsAnimationController::enableInputConsumer);
@@ -757,6 +759,8 @@
         mActivityInitListener.unregister();
         mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
 
+        TaskViewUtils.setDividerBarShown(mRecentsAnimationTargets.nonApps, true);
+
         // Defer clearing the controller and the targets until after we've updated the state
         mRecentsAnimationController = null;
         mRecentsAnimationTargets = null;
@@ -885,6 +889,7 @@
                 break;
             case LAST_TASK:
                 mStateCallback.setState(STATE_RESUME_LAST_TASK);
+                TaskViewUtils.setDividerBarShown(mRecentsAnimationTargets.nonApps, true);
                 break;
         }
         ActiveGestureLog.INSTANCE.addLog("onSettledOnEndTarget " + endTarget);
@@ -1128,7 +1133,8 @@
                     mActivityRestartListener);
 
             mParallelRunningAnim = mActivityInterface.getParallelAnimationToLauncher(
-                    mGestureState.getEndTarget(), duration);
+                    mGestureState.getEndTarget(), duration,
+                    mTaskAnimationManager.getCurrentCallbacks());
             if (mParallelRunningAnim != null) {
                 mParallelRunningAnim.start();
             }
@@ -1725,6 +1731,9 @@
 
     @Override
     public void onRecentsAnimationFinished(RecentsAnimationController controller) {
+        if (!controller.getFinishTargetIsLauncher()) {
+            TaskViewUtils.setDividerBarShown(mRecentsAnimationTargets.nonApps, true);
+        }
         mRecentsAnimationController = null;
         mRecentsAnimationTargets = null;
         if (mRecentsView != null) {
diff --git a/quickstep/src/com/android/quickstep/AnimatedFloat.java b/quickstep/src/com/android/quickstep/AnimatedFloat.java
index f7e8781..95c8710 100644
--- a/quickstep/src/com/android/quickstep/AnimatedFloat.java
+++ b/quickstep/src/com/android/quickstep/AnimatedFloat.java
@@ -53,6 +53,16 @@
         mUpdateCallback = updateCallback;
     }
 
+    /**
+     * Returns an animation from the current value to the given value.
+     */
+    public ObjectAnimator animateToValue(float end) {
+        return animateToValue(value, end);
+    }
+
+    /**
+     * Returns an animation from the given start value to the given end value.
+     */
     public ObjectAnimator animateToValue(float start, float end) {
         cancelAnimation();
         mValueAnimator = ObjectAnimator.ofFloat(this, VALUE, start, end);
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 7ab371b..1412b1a 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -211,17 +211,18 @@
             Rect gridRect = new Rect();
             calculateGridSize(context, dp, gridRect);
 
-            int verticalMargin = res.getDimensionPixelSize(
-                    R.dimen.overview_grid_focus_vertical_margin);
-            float taskHeight = gridRect.height() - verticalMargin * 2;
+            int verticalMargin = Math.max(
+                    res.getDimensionPixelSize(R.dimen.overview_grid_focus_vertical_margin),
+                    res.getDimensionPixelSize(R.dimen.overview_actions_height));
+            float taskHeight =
+                    gridRect.height() - verticalMargin * 2 - dp.overviewTaskThumbnailTopMarginPx;
 
             PointF taskDimension = getTaskDimension(context, dp);
-            float scale = taskHeight / Math.max(taskDimension.x, taskDimension.y);
+            float scale = taskHeight / taskDimension.y;
             int outWidth = Math.round(scale * taskDimension.x);
             int outHeight = Math.round(scale * taskDimension.y);
 
-            int gravity = Gravity.CENTER_VERTICAL;
-            gravity |= orientedState.getRecentsRtlSetting(res) ? Gravity.RIGHT : Gravity.LEFT;
+            int gravity = Gravity.CENTER;
             Gravity.apply(gravity, outWidth, outHeight, gridRect, outRect);
         } else {
             int taskMargin = dp.overviewTaskMarginPx;
@@ -310,8 +311,7 @@
         float rowHeight = (gridRect.height() - rowSpacing) / 2f;
 
         PointF taskDimension = getTaskDimension(context, dp);
-        float scale = (rowHeight - dp.overviewTaskThumbnailTopMarginPx) / Math.max(
-                taskDimension.x, taskDimension.y);
+        float scale = (rowHeight - dp.overviewTaskThumbnailTopMarginPx) / taskDimension.y;
         int outWidth = Math.round(scale * taskDimension.x);
         int outHeight = Math.round(scale * taskDimension.y);
 
@@ -346,7 +346,8 @@
      * an optional additional animation with the same duration.
      */
     public @Nullable Animator getParallelAnimationToLauncher(
-            GestureState.GestureEndTarget endTarget, long duration) {
+            GestureState.GestureEndTarget endTarget, long duration,
+            RecentsAnimationCallbacks callbacks) {
         if (endTarget == RECENTS) {
             ACTIVITY_TYPE activity = getCreatedActivity();
             if (activity == null) {
@@ -363,6 +364,14 @@
     }
 
     /**
+     * Called when we detect a long press in the nav region before passing the gesture slop.
+     * @return Whether taskbar handled the long press, and thus should cancel the gesture.
+     */
+    public boolean onLongPressToUnstashTaskbar() {
+        return false;
+    }
+
+    /**
      * Returns the color of the scrim behind overview when at rest in this state.
      * Return {@link Color#TRANSPARENT} for no scrim.
      */
diff --git a/quickstep/src/com/android/quickstep/InputConsumer.java b/quickstep/src/com/android/quickstep/InputConsumer.java
index 0b2a057..3580ee5 100644
--- a/quickstep/src/com/android/quickstep/InputConsumer.java
+++ b/quickstep/src/com/android/quickstep/InputConsumer.java
@@ -39,6 +39,7 @@
     int TYPE_OVERSCROLL = 1 << 9;
     int TYPE_SYSUI_OVERLAY = 1 << 10;
     int TYPE_ONE_HANDED = 1 << 11;
+    int TYPE_TASKBAR_STASH = 1 << 12;
 
     String[] NAMES = new String[] {
            "TYPE_NO_OP",                    // 0
@@ -53,6 +54,7 @@
             "TYPE_OVERSCROLL",              // 9
             "TYPE_SYSUI_OVERLAY",           // 10
             "TYPE_ONE_HANDED",              // 11
+            "TYPE_TASKBAR_STASH",           // 12
     };
 
     InputConsumer NO_OP = () -> TYPE_NO_OP;
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index fb1391a..94a47e6 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -299,14 +299,15 @@
 
     @Override
     public @Nullable Animator getParallelAnimationToLauncher(GestureEndTarget endTarget,
-            long duration) {
+            long duration, RecentsAnimationCallbacks callbacks) {
         LauncherTaskbarUIController uiController = getTaskbarController();
-        Animator superAnimator = super.getParallelAnimationToLauncher(endTarget, duration);
-        if (uiController == null) {
+        Animator superAnimator = super.getParallelAnimationToLauncher(
+                endTarget, duration, callbacks);
+        if (uiController == null || callbacks == null) {
             return superAnimator;
         }
         LauncherState toState = stateFromGestureEndTarget(endTarget);
-        Animator taskbarAnimator = uiController.createAnimToLauncher(toState, duration);
+        Animator taskbarAnimator = uiController.createAnimToLauncher(toState, callbacks, duration);
         if (superAnimator == null) {
             return taskbarAnimator;
         } else {
@@ -317,6 +318,15 @@
     }
 
     @Override
+    public boolean onLongPressToUnstashTaskbar() {
+        LauncherTaskbarUIController taskbarController = getTaskbarController();
+        if (taskbarController == null) {
+            return super.onLongPressToUnstashTaskbar();
+        }
+        return taskbarController.onLongPressToUnstashTaskbar();
+    }
+
+    @Override
     protected int getOverviewScrimColorForState(BaseQuickstepLauncher launcher,
             LauncherState state) {
         return state.getWorkspaceScrimColor(launcher);
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index a21c714..239233b 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -19,6 +19,7 @@
 
 import android.graphics.Rect;
 import android.util.ArraySet;
+import android.view.RemoteAnimationTarget;
 
 import androidx.annotation.BinderThread;
 import androidx.annotation.UiThread;
@@ -39,6 +40,7 @@
         com.android.systemui.shared.system.RecentsAnimationListener {
 
     private final Set<RecentsAnimationListener> mListeners = new ArraySet<>();
+    private final SystemUiProxy mSystemUiProxy;
     private final boolean mAllowMinimizeSplitScreen;
 
     // TODO(141886704): Remove these references when they are no longer needed
@@ -46,7 +48,9 @@
 
     private boolean mCancelled;
 
-    public RecentsAnimationCallbacks(boolean allowMinimizeSplitScreen) {
+    public RecentsAnimationCallbacks(SystemUiProxy systemUiProxy,
+            boolean allowMinimizeSplitScreen) {
+        mSystemUiProxy = systemUiProxy;
         mAllowMinimizeSplitScreen = allowMinimizeSplitScreen;
     }
 
@@ -89,8 +93,11 @@
             RemoteAnimationTargetCompat[] appTargets,
             RemoteAnimationTargetCompat[] wallpaperTargets,
             Rect homeContentInsets, Rect minimizedHomeBounds) {
+        RemoteAnimationTarget[] nonAppTargets =
+                mSystemUiProxy.onGoingToRecentsLegacy(mCancelled);
         RecentsAnimationTargets targets = new RecentsAnimationTargets(appTargets,
-                wallpaperTargets, homeContentInsets, minimizedHomeBounds);
+                wallpaperTargets, RemoteAnimationTargetCompat.wrap(nonAppTargets),
+                homeContentInsets, minimizedHomeBounds);
         mController = new RecentsAnimationController(animationController,
                 mAllowMinimizeSplitScreen, this::onAnimationFinished);
 
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 53b6675..f343485 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -46,6 +46,8 @@
     private boolean mUseLauncherSysBarFlags = false;
     private boolean mSplitScreenMinimized = false;
     private boolean mFinishRequested = false;
+    // Only valid when mFinishRequested == true.
+    private boolean mFinishTargetIsLauncher;
     private RunnableList mPendingFinishCallbacks = new RunnableList();
 
     public RecentsAnimationController(RecentsAnimationControllerCompat controller,
@@ -145,6 +147,7 @@
 
         // Finish not yet requested
         mFinishRequested = true;
+        mFinishTargetIsLauncher = toRecents;
         mOnFinishedListener.accept(this);
         mPendingFinishCallbacks.add(callback);
         UI_HELPER_EXECUTOR.execute(() -> {
@@ -217,4 +220,12 @@
     public RecentsAnimationControllerCompat getController() {
         return mController;
     }
+
+    /**
+     * RecentsAnimationListeners can check this in onRecentsAnimationFinished() to determine whether
+     * the animation was finished to launcher vs an app.
+     */
+    public boolean getFinishTargetIsLauncher() {
+        return mFinishTargetIsLauncher;
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
index 3861bab..b6d9016 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
@@ -31,9 +31,9 @@
     public final Rect minimizedHomeBounds;
 
     public RecentsAnimationTargets(RemoteAnimationTargetCompat[] apps,
-            RemoteAnimationTargetCompat[] wallpapers, Rect homeContentInsets,
-            Rect minimizedHomeBounds) {
-        super(apps, wallpapers, new RemoteAnimationTargetCompat[0], MODE_CLOSING);
+            RemoteAnimationTargetCompat[] wallpapers, RemoteAnimationTargetCompat[] nonApps,
+            Rect homeContentInsets, Rect minimizedHomeBounds) {
+        super(apps, wallpapers, nonApps, MODE_CLOSING);
         this.homeContentInsets = homeContentInsets;
         this.minimizedHomeBounds = minimizedHomeBounds;
     }
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index d040904..d6b95c1 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -33,6 +33,8 @@
 import android.os.UserHandle;
 import android.util.Log;
 import android.view.MotionEvent;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
 
 import com.android.launcher3.util.MainThreadInitializedObject;
@@ -85,6 +87,7 @@
     private float mLastNavButtonAlpha;
     private boolean mLastNavButtonAnimate;
     private boolean mHasNavButtonAlphaBeenSet = false;
+    private Runnable mPendingSetNavButtonAlpha = null;
 
     // TODO(141886704): Find a way to remove this
     private int mLastSystemUiStateFlags;
@@ -157,6 +160,11 @@
             setSmartspaceCallback(mPendingSmartspaceCallback);
             mPendingSmartspaceCallback = null;
         }
+
+        if (mPendingSetNavButtonAlpha != null) {
+            mPendingSetNavButtonAlpha.run();
+            mPendingSetNavButtonAlpha = null;
+        }
     }
 
     public void clearProxy() {
@@ -240,14 +248,18 @@
         boolean changed = Float.compare(alpha, mLastNavButtonAlpha) != 0
                 || animate != mLastNavButtonAnimate
                 || !mHasNavButtonAlphaBeenSet;
-        if (mSystemUiProxy != null && changed) {
-            mLastNavButtonAlpha = alpha;
-            mLastNavButtonAnimate = animate;
-            mHasNavButtonAlphaBeenSet = true;
-            try {
-                mSystemUiProxy.setNavBarButtonAlpha(alpha, animate);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call setNavBarButtonAlpha", e);
+        if (changed) {
+            if (mSystemUiProxy == null) {
+                mPendingSetNavButtonAlpha = () -> setNavBarButtonAlpha(alpha, animate);
+            } else {
+                mLastNavButtonAlpha = alpha;
+                mLastNavButtonAnimate = animate;
+                mHasNavButtonAlphaBeenSet = true;
+                try {
+                    mSystemUiProxy.setNavBarButtonAlpha(alpha, animate);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Failed call setNavBarButtonAlpha", e);
+                }
             }
         }
     }
@@ -552,6 +564,22 @@
         }
     }
 
+    /**
+     * Start multiple tasks in split-screen simultaneously.
+     */
+    public void startTasksWithLegacyTransition(int mainTaskId, Bundle mainOptions, int sideTaskId,
+            Bundle sideOptions, @SplitConfigurationOptions.StagePosition int sidePosition,
+            RemoteAnimationAdapter adapter) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSplitScreen.startTasksWithLegacyTransition(mainTaskId, mainOptions, sideTaskId,
+                        sideOptions, sidePosition, adapter);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call startTasksWithLegacyTransition");
+            }
+        }
+    }
+
     public void startShortcut(String packageName, String shortcutId, int stage, int position,
             Bundle options, UserHandle user) {
         if (mSplitScreen != null) {
@@ -585,6 +613,24 @@
         }
     }
 
+    /**
+     * Call this when going to recents so that shell can set-up and provide appropriate leashes
+     * for animation (eg. DividerBar).
+     *
+     * @param cancel true if recents starting is being cancelled.
+     * @return RemoteAnimationTargets of windows that need to animate but only exist in shell.
+     */
+    public RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel) {
+        if (mSplitScreen != null) {
+            try {
+                return mSplitScreen.onGoingToRecentsLegacy(cancel);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call onGoingToRecentsLegacy");
+            }
+        }
+        return null;
+    }
+
     //
     // One handed
     //
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 67bd85f..fe07cbd 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -28,6 +28,7 @@
 import android.os.SystemProperties;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
 import com.android.launcher3.Utilities;
@@ -108,7 +109,8 @@
 
         final BaseActivityInterface activityInterface = gestureState.getActivityInterface();
         mLastGestureState = gestureState;
-        mCallbacks = new RecentsAnimationCallbacks(activityInterface.allowMinimizeSplitScreen());
+        mCallbacks = new RecentsAnimationCallbacks(SystemUiProxy.INSTANCE.get(mCtx),
+                activityInterface.allowMinimizeSplitScreen());
         mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() {
             @Override
             public void onRecentsAnimationStart(RecentsAnimationController controller,
@@ -262,6 +264,11 @@
         mLastAppearedTaskTarget = null;
     }
 
+    @Nullable
+    public RecentsAnimationCallbacks getCurrentCallbacks() {
+        return mCallbacks;
+    }
+
     public void dump() {
         // TODO
     }
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index e75d751..080533b 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -68,13 +68,14 @@
         final BaseDraggingActivity activity = BaseActivity.fromContext(taskView.getContext());
         for (TaskShortcutFactory menuOption : MENU_OPTIONS) {
             SystemShortcut shortcut = menuOption.getShortcut(activity, taskView);
-            if (menuOption == TaskShortcutFactory.SPLIT_SCREEN &&
-                    FeatureFlags.ENABLE_SPLIT_SELECT.get()) {
-                addSplitOptions(shortcuts, activity, taskView, deviceProfile);
+            if (shortcut == null) {
                 continue;
             }
 
-            if (shortcut != null) {
+            if (menuOption == TaskShortcutFactory.SPLIT_SCREEN &&
+                    FeatureFlags.ENABLE_SPLIT_SELECT.get()) {
+                addSplitOptions(shortcuts, activity, taskView, deviceProfile);
+            } else {
                 shortcuts.add(shortcut);
             }
         }
@@ -88,7 +89,6 @@
             SystemShortcut screenshotShortcut = TaskShortcutFactory.SCREENSHOT
                     .getShortcut(activity, taskView);
             if (screenshotShortcut != null) {
-                screenshotShortcut.setHasFinishRecentsInAction(true);
                 shortcuts.add(screenshotShortcut);
             }
 
@@ -97,7 +97,6 @@
                 SystemShortcut modalShortcut = TaskShortcutFactory.MODAL
                         .getShortcut(activity, taskView);
                 if (modalShortcut != null) {
-                    modalShortcut.setHasFinishRecentsInAction(true);
                     shortcuts.add(modalShortcut);
                 }
             }
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 37fda73..f292f1a 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -15,6 +15,7 @@
  */
 package com.android.quickstep;
 
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 
@@ -405,77 +406,39 @@
     }
 
     /** Legacy version (until shell transitions are enabled) */
-    public static void composeRecentsSplitLaunchAnimatorLegacy(@NonNull AnimatorSet anim,
+    public static void composeRecentsSplitLaunchAnimatorLegacy(@NonNull TaskView initialView,
             @NonNull TaskView v, @NonNull RemoteAnimationTargetCompat[] appTargets,
             @NonNull RemoteAnimationTargetCompat[] wallpaperTargets,
-            @NonNull RemoteAnimationTargetCompat[] nonAppTargets, boolean launcherClosing,
-            @NonNull StateManager stateManager, @NonNull DepthController depthController,
-            int targetStage) {
-        PendingAnimation out = new PendingAnimation(RECENTS_LAUNCH_DURATION);
-        boolean isRunningTask = v.isRunningTask();
-        TransformParams params = null;
-        TaskViewSimulator tvs = null;
-        RecentsView recentsView = v.getRecentsView();
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask) {
-            params = recentsView.getLiveTileParams();
-            tvs = recentsView.getLiveTileTaskViewSimulator();
+            @NonNull RemoteAnimationTargetCompat[] nonAppTargets,
+            @NonNull Runnable finishCallback) {
+
+        final int[] splitRoots = new int[2];
+        for (int i = 0; i < appTargets.length; ++i) {
+            final int taskId = appTargets[i].taskInfo != null ? appTargets[i].taskInfo.taskId : -1;
+            final int mode = appTargets[i].mode;
+            if (taskId == initialView.getTask().key.id || taskId == v.getTask().key.id) {
+                if (mode != MODE_OPENING) {
+                    throw new IllegalStateException(
+                            "Expected task to be opening, but it is " + mode);
+                }
+                splitRoots[taskId == initialView.getTask().key.id ? 0 : 1] = i;
+            }
         }
 
-        boolean inLiveTileMode =
-                ENABLE_QUICKSTEP_LIVE_TILE.get() && recentsView.getRunningTaskIndex() != -1;
-        final RemoteAnimationTargets targets =
-                new RemoteAnimationTargets(appTargets, wallpaperTargets, nonAppTargets,
-                        inLiveTileMode ? MODE_CLOSING : MODE_OPENING);
+        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
 
-        if (params == null) {
-            SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v);
-            targets.addReleaseCheck(applier);
-
-            params = new TransformParams()
-                    .setSyncTransactionApplier(applier)
-                    .setTargetSet(targets);
+        // This is where we should animate the split roots. For now, though, just make them visible.
+        for (int i = 0; i < 2; ++i) {
+            t.show(appTargets[splitRoots[i]].leash.getSurfaceControl());
+            t.setAlpha(appTargets[splitRoots[i]].leash.getSurfaceControl(), 1.f);
         }
 
-        Rect crop = new Rect();
-        Context context = v.getContext();
-        DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile();
-        if (tvs == null && targets.apps.length > 0) {
-            tvs = new TaskViewSimulator(recentsView.getContext(), recentsView.getSizeStrategy());
-            tvs.setDp(dp);
+        // This contains the initial state (before animation), so apply this at the beginning of
+        // the animation.
+        t.apply();
 
-            // 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(recentsView.getContext())
-                    .getInfo().rotation;
-            tvs.getOrientationState().update(displayRotation, displayRotation);
-
-            tvs.setPreview(targets.apps[targets.apps.length - 1]);
-            tvs.fullScreenProgress.value = 0;
-            tvs.recentsViewScale.value = 1;
-//            tvs.setScroll(startScroll);
-
-            // Fade in the task during the initial 20% of the animation
-            out.addFloat(params, TransformParams.TARGET_ALPHA, 0, 1,
-                    clampToProgress(LINEAR, 0, 0.2f));
-        }
-
-        TaskViewSimulator topMostSimulator = null;
-
-        if (tvs != null) {
-            out.setFloat(tvs.fullScreenProgress,
-                    AnimatedFloat.VALUE, 1, TOUCH_RESPONSE_INTERPOLATOR);
-            out.setFloat(tvs.recentsViewScale,
-                    AnimatedFloat.VALUE, tvs.getFullScreenScale(), TOUCH_RESPONSE_INTERPOLATOR);
-            out.setFloat(tvs.recentsViewScroll,
-                    AnimatedFloat.VALUE, 0, TOUCH_RESPONSE_INTERPOLATOR);
-
-            TaskViewSimulator finalTsv = tvs;
-            TransformParams finalParams = params;
-            out.addOnFrameCallback(() -> finalTsv.apply(finalParams));
-            topMostSimulator = tvs;
-        }
-
-        anim.play(out.buildAnim());
+        // Once there is an animation, this should be called AFTER the animation completes.
+        finishCallback.run();
     }
 
     public static void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
@@ -490,6 +453,10 @@
         PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
         createRecentsWindowAnimator(taskView, skipLauncherChanges, appTargets, wallpaperTargets,
                 nonAppTargets, depthController, pa);
+        if (launcherClosing) {
+            // TODO(b/182592057): differentiate between "restore split" vs "launch fullscreen app"
+            TaskViewUtils.setDividerBarShown(nonAppTargets, true);
+        }
 
         Animator childStateAnimation = null;
         // Found a visible recents task that matches the opening app, lets launch the app from there
@@ -542,4 +509,19 @@
         stateManager.setCurrentAnimation(anim, childStateAnimation);
         anim.addListener(windowAnimEndListener);
     }
+
+    static void setDividerBarShown(RemoteAnimationTargetCompat[] nonApps, boolean shown) {
+        // TODO(b/182592057): make this part of the animations instead.
+        if (nonApps != null && nonApps.length > 0) {
+            for (int i = 0; i < nonApps.length; ++i) {
+                final RemoteAnimationTargetCompat targ = nonApps[i];
+                if (targ.windowType == TYPE_DOCK_DIVIDER) {
+                    SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+                    t.setVisibility(targ.leash.getSurfaceControl(), shown);
+                    t.apply();
+                    t.close();
+                }
+            }
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 069ed11..af1a01a 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -96,6 +96,7 @@
 import com.android.quickstep.inputconsumers.ResetGestureInputConsumer;
 import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer;
 import com.android.quickstep.inputconsumers.SysUiOverlayInputConsumer;
+import com.android.quickstep.inputconsumers.TaskbarStashInputConsumer;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.AssistantUtilities;
 import com.android.quickstep.util.ProtoTracer;
@@ -262,16 +263,35 @@
         }
 
         @Override
-        public void onSplitScreenSecondaryBoundsChanged(Rect bounds, Rect insets)  {
+        public void onSplitScreenSecondaryBoundsChanged(Rect bounds, Rect insets) {
             WindowBounds wb = new WindowBounds(bounds, insets);
             MAIN_EXECUTOR.execute(() -> SplitScreenBounds.INSTANCE.setSecondaryWindowBounds(wb));
         }
 
         @Override
-        public void onImeWindowStatusChanged(int displayId, IBinder token, int vis,
-                int backDisposition, boolean showImeSwitcher) {
-            MAIN_EXECUTOR.execute(() -> mTaskbarManager.updateImeStatus(
-                    displayId, vis, backDisposition, showImeSwitcher));
+        public void onRotationProposal(int rotation, boolean isValid) {
+            executeForTaskbarManager(() -> mTaskbarManager.onRotationProposal(rotation, isValid));
+        }
+
+        @Override
+        public void disable(int displayId, int state1, int state2, boolean animate) {
+            executeForTaskbarManager(() -> mTaskbarManager
+                    .disableNavBarElements(displayId, state1, state2, animate));
+        }
+
+        @Override
+        public void onSystemBarAttributesChanged(int displayId, int behavior) {
+            executeForTaskbarManager(() -> mTaskbarManager
+                    .onSystemBarAttributesChanged(displayId, behavior));
+        }
+
+        private void executeForTaskbarManager(final Runnable r) {
+            MAIN_EXECUTOR.execute(() -> {
+                if (mTaskbarManager == null) {
+                    return;
+                }
+                r.run();
+            });
         }
 
         public TaskbarManager getTaskbarManager() {
@@ -655,6 +675,14 @@
                         mDeviceState, event);
             }
 
+            // If Taskbar is present, we listen for long press to unstash it.
+            BaseActivityInterface activityInterface = newGestureState.getActivityInterface();
+            StatefulActivity activity = activityInterface.getCreatedActivity();
+            if (activity != null && activity.getDeviceProfile().isTaskbarPresent) {
+                base = new TaskbarStashInputConsumer(this, base, mInputMonitorCompat,
+                        activityInterface);
+            }
+
             if (FeatureFlags.ENABLE_QUICK_CAPTURE_GESTURE.get()) {
                 OverscrollPlugin plugin = null;
                 if (FeatureFlags.FORCE_LOCAL_OVERSCROLL_PLUGIN.get()) {
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index f0364eb..50b69dc 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -15,6 +15,8 @@
  */
 package com.android.quickstep.fallback;
 
+import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
+import static com.android.launcher3.anim.Interpolators.INSTANT;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
@@ -77,9 +79,7 @@
         float clearAllButtonAlpha = state.hasClearAllButton() ? 1 : 0;
         setter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
                 clearAllButtonAlpha, LINEAR);
-        float overviewButtonAlpha =
-                state.hasOverviewActions() && mRecentsView.shouldShowOverviewActionsForState(state)
-                        ? 1 : 0;
+        float overviewButtonAlpha = state.hasOverviewActions() ? 1 : 0;
         setter.setFloat(mActivity.getActionsView().getVisibilityAlpha(),
                 MultiValueAlpha.VALUE, overviewButtonAlpha, LINEAR);
 
@@ -94,8 +94,9 @@
         setter.setFloat(mRecentsView, TASK_MODALNESS, state.getOverviewModalness(),
                 config.getInterpolator(ANIM_OVERVIEW_MODAL, LINEAR));
         setter.setFloat(mRecentsView, FULLSCREEN_PROGRESS, state.isFullScreen() ? 1 : 0, LINEAR);
-        setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS,
-                state.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()) ? 1f : 0f, LINEAR);
+        boolean showAsGrid = state.displayOverviewTasksAsGrid(mActivity.getDeviceProfile());
+        setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS, showAsGrid ? 1f : 0f,
+                showAsGrid ? INSTANT : FINAL_FRAME);
 
         setter.setViewBackgroundColor(mActivity.getScrimView(), state.getScrimColor(mActivity),
                 config.getInterpolator(ANIM_SCRIM_FADE, LINEAR));
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index ac3fb27..d963188 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -26,14 +26,13 @@
 import android.content.Context;
 import android.os.Build;
 import android.util.AttributeSet;
-import android.util.Log;
+import android.view.MotionEvent;
 
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.statemanager.StateManager.StateListener;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.quickstep.FallbackActivityInterface;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.RecentsActivity;
@@ -122,6 +121,12 @@
         }
     }
 
+    @Nullable
+    @Override
+    protected TaskView getHomeTaskView() {
+        return mHomeTaskInfo != null ? getTaskView(mHomeTaskInfo.taskId) : null;
+    }
+
     @Override
     protected boolean shouldAddStubTaskView(RunningTaskInfo runningTaskInfo) {
         if (mHomeTaskInfo != null && runningTaskInfo != null &&
@@ -205,4 +210,11 @@
             setDisallowScrollToClearAll(!state.hasClearAllButton());
         }
     }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        boolean result = super.onTouchEvent(ev);
+        // Do not let touch escape to siblings below this view.
+        return result || mActivity.getStateManager().getState().overviewUi();
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsState.java b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
index b6cfdce..111a940 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsState.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
@@ -40,15 +40,16 @@
     private static final int FLAG_SHOW_AS_GRID = BaseState.getFlag(4);
     private static final int FLAG_SCRIM = BaseState.getFlag(5);
     private static final int FLAG_LIVE_TILE = BaseState.getFlag(6);
+    private static final int FLAG_OVERVIEW_UI = BaseState.getFlag(7);
 
     public static final RecentsState DEFAULT = new RecentsState(0,
             FLAG_CLEAR_ALL_BUTTON | FLAG_OVERVIEW_ACTIONS | FLAG_SHOW_AS_GRID | FLAG_SCRIM
-                    | FLAG_LIVE_TILE);
+                    | FLAG_LIVE_TILE | FLAG_OVERVIEW_UI);
     public static final RecentsState MODAL_TASK = new ModalState(1,
             FLAG_DISABLE_RESTORE | FLAG_CLEAR_ALL_BUTTON | FLAG_OVERVIEW_ACTIONS | FLAG_MODAL
-                    | FLAG_SHOW_AS_GRID | FLAG_SCRIM | FLAG_LIVE_TILE);
+                    | FLAG_SHOW_AS_GRID | FLAG_SCRIM | FLAG_LIVE_TILE | FLAG_OVERVIEW_UI);
     public static final RecentsState BACKGROUND_APP = new BackgroundAppState(2,
-            FLAG_DISABLE_RESTORE | FLAG_NON_INTERACTIVE | FLAG_FULL_SCREEN);
+            FLAG_DISABLE_RESTORE | FLAG_NON_INTERACTIVE | FLAG_FULL_SCREEN | FLAG_OVERVIEW_UI);
     public static final RecentsState HOME = new RecentsState(3, 0);
     public static final RecentsState BG_LAUNCHER = new LauncherState(4, 0);
 
@@ -140,6 +141,13 @@
         return deviceProfile.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get();
     }
 
+    /**
+     * True if the state has overview panel visible.
+     */
+    public boolean overviewUi() {
+        return hasFlag(FLAG_OVERVIEW_UI);
+    }
+
     private static class ModalState extends RecentsState {
 
         public ModalState(int id, int flags) {
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java
new file mode 100644
index 0000000..83f689f
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.inputconsumers;
+
+import android.content.Context;
+import android.view.GestureDetector;
+import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.MotionEvent;
+
+import com.android.quickstep.BaseActivityInterface;
+import com.android.quickstep.InputConsumer;
+import com.android.systemui.shared.system.InputMonitorCompat;
+
+/**
+ * Listens for a long press, and cancels the current gesture if that causes Taskbar to be unstashed.
+ */
+public class TaskbarStashInputConsumer extends DelegateInputConsumer {
+
+    private final BaseActivityInterface mActivityInterface;
+    private final GestureDetector mLongPressDetector;
+
+    public TaskbarStashInputConsumer(Context context, InputConsumer delegate,
+            InputMonitorCompat inputMonitor, BaseActivityInterface activityInterface) {
+        super(delegate, inputMonitor);
+        mActivityInterface = activityInterface;
+
+        mLongPressDetector = new GestureDetector(context, new SimpleOnGestureListener() {
+            @Override
+            public void onLongPress(MotionEvent motionEvent) {
+                onLongPressDetected(motionEvent);
+            }
+        });
+    }
+
+    @Override
+    public int getType() {
+        return TYPE_TASKBAR_STASH | mDelegate.getType();
+    }
+
+    @Override
+    public void onMotionEvent(MotionEvent ev) {
+        mLongPressDetector.onTouchEvent(ev);
+        if (mState != STATE_ACTIVE) {
+            mDelegate.onMotionEvent(ev);
+        }
+    }
+
+    private void onLongPressDetected(MotionEvent motionEvent) {
+        if (mActivityInterface.onLongPressToUnstashTaskbar()) {
+            setActive(motionEvent);
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
index 7eca360..6bdc284 100644
--- a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
+++ b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
@@ -16,7 +16,7 @@
 
 package com.android.quickstep.logging;
 
-import static com.android.launcher3.InvariantDeviceProfile.KEY_MIGRATION_SRC_HOTSEAT_COUNT;
+import static com.android.launcher3.InvariantDeviceProfile.KEY_MIGRATION_SRC_WORKSPACE_SIZE;
 import static com.android.launcher3.Utilities.getDevicePrefs;
 import static com.android.launcher3.Utilities.getPrefs;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_GRID_SIZE_2;
@@ -43,6 +43,7 @@
 
 import com.android.launcher3.AutoInstallsLayout;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.InstanceIdSequence;
 import com.android.launcher3.logging.StatsLogManager;
@@ -133,7 +134,8 @@
 
     @Override
     public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
-        if (LAST_PREDICTION_ENABLED_STATE.equals(key) || KEY_MIGRATION_SRC_HOTSEAT_COUNT.equals(key)
+        if (LAST_PREDICTION_ENABLED_STATE.equals(key)
+                || KEY_MIGRATION_SRC_WORKSPACE_SIZE.equals(key)
                 || mLoggablePrefs.containsKey(key)) {
             dispatchUserEvent();
         }
@@ -153,23 +155,25 @@
 
         SharedPreferences prefs = getPrefs(mContext);
         StatsLogManager.LauncherEvent gridSizeChangedEvent = null;
-        // TODO(b/184981523): This doesn't work for 2-panel grid, which has 6 hotseat icons
-        switch (prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, -1)) {
-            case 5:
-                gridSizeChangedEvent = LAUNCHER_GRID_SIZE_5;
-                break;
-            case 4:
-                gridSizeChangedEvent = LAUNCHER_GRID_SIZE_4;
-                break;
-            case 3:
-                gridSizeChangedEvent = LAUNCHER_GRID_SIZE_3;
-                break;
-            case 2:
-                gridSizeChangedEvent = LAUNCHER_GRID_SIZE_2;
-                break;
-            default:
-                // Ignore illegal input.
-                break;
+        String workspaceSize = prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, null);
+        if (workspaceSize != null) {
+            switch (Utilities.parsePoint(workspaceSize).x) {
+                case 5:
+                    gridSizeChangedEvent = LAUNCHER_GRID_SIZE_5;
+                    break;
+                case 4:
+                    gridSizeChangedEvent = LAUNCHER_GRID_SIZE_4;
+                    break;
+                case 3:
+                    gridSizeChangedEvent = LAUNCHER_GRID_SIZE_3;
+                    break;
+                case 2:
+                    gridSizeChangedEvent = LAUNCHER_GRID_SIZE_2;
+                    break;
+                default:
+                    // Ignore illegal input.
+                    break;
+            }
         }
         if (gridSizeChangedEvent != null) {
             logger.log(gridSizeChangedEvent);
diff --git a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
index 7f94839..f1b4e3d 100644
--- a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
+++ b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
@@ -49,6 +49,7 @@
 
     private enum RecentsResistanceParams {
         FROM_APP(0.75f, 0.5f, 1f),
+        FROM_APP_TABLET(0.9f, 0.75f, 1f),
         FROM_OVERVIEW(1f, 0.75f, 0.5f);
 
         RecentsResistanceParams(float scaleStartResist, float scaleMaxResist,
@@ -228,7 +229,7 @@
 
         // These are not required, or can have a default value that is generally correct.
         @Nullable public PendingAnimation resistAnim = null;
-        public RecentsResistanceParams resistanceParams = RecentsResistanceParams.FROM_APP;
+        public RecentsResistanceParams resistanceParams;
         public float startScale = 1f;
         public float startTranslation = 0f;
 
@@ -242,6 +243,11 @@
             this.scaleProperty = scaleProperty;
             this.translationTarget = translationTarget;
             this.translationProperty = translationProperty;
+            if (dp.isTablet) {
+                resistanceParams = RecentsResistanceParams.FROM_APP_TABLET;
+            } else {
+                resistanceParams = RecentsResistanceParams.FROM_APP;
+            }
         }
 
         private RecentsParams setResistAnim(PendingAnimation resistAnim) {
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index a147b68..2351a4e 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -22,33 +22,26 @@
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
 
-import android.animation.AnimatorSet;
-import android.app.ActivityOptions;
+import android.app.ActivityThread;
 import android.content.res.Resources;
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
-import android.util.Pair;
 import android.view.Gravity;
+import android.view.RemoteAnimationAdapter;
 import android.view.SurfaceControl;
 import android.window.TransitionInfo;
 
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.BaseActivity;
-import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InsettableFrameLayout;
-import com.android.launcher3.LauncherAnimationRunner;
-import com.android.launcher3.LauncherAnimationRunner.RemoteAnimationFactory;
 import com.android.launcher3.R;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskAnimationManager;
 import com.android.quickstep.TaskViewUtils;
 import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
 import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -92,37 +85,27 @@
                     ? new int[]{mInitialTaskView.getTask().key.id, taskView.getTask().key.id}
                     : new int[]{taskView.getTask().key.id, mInitialTaskView.getTask().key.id};
 
-            RemoteSplitLaunchAnimationRunner animationRunner =
-                    new RemoteSplitLaunchAnimationRunner(mInitialTaskView, taskView);
+            RemoteSplitLaunchTransitionRunner animationRunner =
+                    new RemoteSplitLaunchTransitionRunner(mInitialTaskView, taskView);
             mSystemUiProxy.startTasks(taskIds[0], null /* mainOptions */, taskIds[1],
                     null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT,
                     new RemoteTransitionCompat(animationRunner, MAIN_EXECUTOR));
-            return;
+        } else {
+            // Assume initial task is for top/left part of screen
+            final int[] taskIds = mInitialPosition.mStagePosition == STAGE_POSITION_TOP_OR_LEFT
+                    ? new int[]{mInitialTaskView.getTask().key.id, taskView.getTask().key.id}
+                    : new int[]{taskView.getTask().key.id, mInitialTaskView.getTask().key.id};
+
+            RemoteSplitLaunchAnimationRunner animationRunner =
+                    new RemoteSplitLaunchAnimationRunner(mInitialTaskView, taskView);
+            final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
+                    RemoteAnimationAdapterCompat.wrapRemoteAnimationRunner(animationRunner),
+                    300, 150,
+                    ActivityThread.currentActivityThread().getApplicationThread());
+
+            mSystemUiProxy.startTasksWithLegacyTransition(taskIds[0], null /* mainOptions */,
+                    taskIds[1], null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT, adapter);
         }
-        // Assume initial mInitialTaskId is for top/left part of screen
-        RemoteAnimationFactory initialSplitRunnerWrapped =  new SplitLaunchAnimationRunner(
-                mInitialTaskView, 0);
-        RemoteAnimationFactory secondarySplitRunnerWrapped =  new SplitLaunchAnimationRunner(
-                taskView, 1);
-        RemoteAnimationRunnerCompat initialSplitRunner = new LauncherAnimationRunner(
-                new Handler(Looper.getMainLooper()), initialSplitRunnerWrapped,
-                true /* startAtFrontOfQueue */);
-        RemoteAnimationRunnerCompat secondarySplitRunner = new LauncherAnimationRunner(
-                new Handler(Looper.getMainLooper()), secondarySplitRunnerWrapped,
-                true /* startAtFrontOfQueue */);
-        ActivityOptions initialOptions = ActivityOptionsCompat.makeRemoteAnimation(
-                new RemoteAnimationAdapterCompat(initialSplitRunner, 300, 150));
-        ActivityOptions secondaryOptions = ActivityOptionsCompat.makeRemoteAnimation(
-                new RemoteAnimationAdapterCompat(secondarySplitRunner, 300, 150));
-        mSystemUiProxy.startTask(mInitialTaskView.getTask().key.id, mInitialPosition.mStageType,
-                mInitialPosition.mStagePosition,
-                /*null*/ initialOptions.toBundle());
-        Pair<Integer, Integer> compliment = getComplimentaryStageAndPosition(mInitialPosition);
-        mSystemUiProxy.startTask(taskView.getTask().key.id, compliment.first,
-                compliment.second,
-                /*null*/ secondaryOptions.toBundle());
-        // After successful launch, call resetState
-        resetState();
     }
 
     /**
@@ -153,12 +136,12 @@
     /**
      * Requires Shell Transitions
      */
-    private class RemoteSplitLaunchAnimationRunner implements RemoteTransitionRunner {
+    private class RemoteSplitLaunchTransitionRunner implements RemoteTransitionRunner {
 
         private final TaskView mInitialTaskView;
         private final TaskView mTaskView;
 
-        RemoteSplitLaunchAnimationRunner(TaskView initialTaskView, TaskView taskView) {
+        RemoteSplitLaunchTransitionRunner(TaskView initialTaskView, TaskView taskView) {
             mInitialTaskView = initialTaskView;
             mTaskView = taskView;
         }
@@ -175,48 +158,34 @@
 
     /**
      * LEGACY
-     * @return the opposite stage and position from the {@param position} provided as first and
-     *         second object, respectively
-     * Ex. If position is has stage = Main and position = Top/Left, this will return
-     * Pair(stage=Side, position=Bottom/Left)
-     */
-    private Pair<Integer, Integer> getComplimentaryStageAndPosition(SplitPositionOption position) {
-        // Right now this is as simple as flipping between 0 and 1
-        int complimentStageType = position.mStageType ^ 1;
-        int complimentStagePosition = position.mStagePosition ^ 1;
-        return new Pair<>(complimentStageType, complimentStagePosition);
-    }
-
-    /**
-     * LEGACY
      * Remote animation runner for animation to launch an app.
      */
-    private class SplitLaunchAnimationRunner implements RemoteAnimationFactory {
+    private class RemoteSplitLaunchAnimationRunner implements RemoteAnimationRunnerCompat {
 
-        private final TaskView mV;
-        private final int mTargetState;
+        private final TaskView mInitialTaskView;
+        private final TaskView mTaskView;
 
-        SplitLaunchAnimationRunner(TaskView v, int targetState) {
-            mV = v;
-            mTargetState = targetState;
+        RemoteSplitLaunchAnimationRunner(TaskView initialTaskView, TaskView taskView) {
+            mInitialTaskView = initialTaskView;
+            mTaskView = taskView;
         }
 
         @Override
-        public void onCreateAnimation(int transit,
-                RemoteAnimationTargetCompat[] appTargets,
-                RemoteAnimationTargetCompat[] wallpaperTargets,
-                RemoteAnimationTargetCompat[] nonAppTargets,
-                LauncherAnimationRunner.AnimationResult result) {
-            AnimatorSet anim = new AnimatorSet();
-            BaseQuickstepLauncher activity = BaseActivity.fromContext(mV.getContext());
-            TaskViewUtils.composeRecentsSplitLaunchAnimatorLegacy(anim, mV,
-                    appTargets, wallpaperTargets, nonAppTargets, true, activity.getStateManager(),
-                    activity.getDepthController(), mTargetState);
-            result.setAnimation(anim, activity);
+        public void onAnimationStart(int transit, RemoteAnimationTargetCompat[] apps,
+                RemoteAnimationTargetCompat[] wallpapers, RemoteAnimationTargetCompat[] nonApps,
+                Runnable finishedCallback) {
+            TaskViewUtils.composeRecentsSplitLaunchAnimatorLegacy(mInitialTaskView, mTaskView, apps,
+                    wallpapers, nonApps, finishedCallback);
+            // After successful launch, call resetState
+            resetState();
+        }
+
+        @Override
+        public void onAnimationCancelled() {
+            resetState();
         }
     }
 
-
     /**
      * To be called if split select was cancelled
      */
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index c97ca32..7eee415 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -15,7 +15,6 @@
  */
 package com.android.quickstep.util;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.states.RotationHelper.deltaRotation;
 import static com.android.launcher3.touch.PagedOrientationHandler.MATRIX_POST_TRANSLATE;
 import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation;
@@ -62,7 +61,6 @@
     private final boolean mIsRecentsRtl;
 
     private final Rect mTaskRect = new Rect();
-    private boolean mDrawsBelowRecents;
     private final PointF mPivot = new PointF();
     private DeviceProfile mDp;
 
@@ -163,10 +161,6 @@
         recentsViewScroll.value = scroll;
     }
 
-    public void setDrawsBelowRecents(boolean drawsBelowRecents) {
-        mDrawsBelowRecents = drawsBelowRecents;
-    }
-
     /**
      * Adds animation for all the components corresponding to transition from an app to overview.
      */
@@ -256,8 +250,8 @@
 
         float fullScreenProgress = Utilities.boundToRange(this.fullScreenProgress.value, 0, 1);
         mCurrentFullscreenParams.setProgress(
-                fullScreenProgress, recentsViewScale.value, mTaskRect.width(), mDp,
-                mPositionHelper);
+                fullScreenProgress, recentsViewScale.value, /*taskViewScale=*/1f, mTaskRect.width(),
+                mDp, mPositionHelper);
 
         // Apply thumbnail matrix
         RectF insets = mCurrentFullscreenParams.mCurrentDrawnInsets;
@@ -301,12 +295,6 @@
         builder.withMatrix(mMatrix)
                 .withWindowCrop(mTmpCropRect)
                 .withCornerRadius(getCurrentCornerRadius());
-
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && params.getRecentsSurface() != null) {
-            // When relativeLayer = 0, it reverts the surfaces back to the original order.
-            builder.withRelativeLayerTo(params.getRecentsSurface(),
-                    mDrawsBelowRecents ? Integer.MIN_VALUE : 0);
-        }
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 8c115e5..563bb53 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -180,7 +180,6 @@
         } else {
             mDisabledFlags &= ~disabledFlags;
         }
-        //
         boolean isEnabled = (mDisabledFlags & ~DISABLED_ROTATED) == 0;
         LayoutUtils.setViewEnabled(this, isEnabled);
     }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 39627be..6d3f855 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -32,7 +32,6 @@
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.Utilities.squaredTouchSlop;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
-import static com.android.launcher3.anim.Interpolators.ACCEL_0_5;
 import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
@@ -129,8 +128,8 @@
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.ResourceBasedOverride.Overrides;
-import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.launcher3.util.RunnableList;
+import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.TranslateEdgeEffect;
@@ -348,6 +347,9 @@
     private static final int ADDITION_TASK_DURATION = 200;
     private static final float INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.55f;
     private static final float ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.05f;
+    private static final float ANIMATION_DISMISS_PROGRESS_MIDPOINT = 0.5f;
+
+    private static final float SIGNIFICANT_MOVE_THRESHOLD_TABLET = 0.15f;
 
     protected final RecentsOrientedState mOrientationState;
     protected final BaseActivityInterface<STATE_TYPE, ACTIVITY_TYPE> mSizeStrategy;
@@ -355,6 +357,13 @@
     protected SurfaceTransactionApplier mSyncTransactionApplier;
     protected int mTaskWidth;
     protected int mTaskHeight;
+    // Used to position the top of a task in the top row of the grid
+    private float mTaskGridVerticalDiff;
+    // The vertical space one grid task takes + space between top and bottom row.
+    private float mTopBottomRowHeightDiff;
+    // mTaskGridVerticalDiff and mTopBottomRowHeightDiff summed together provides the top
+    // position for bottom row of grid tasks.
+
     protected final TransformParams mLiveTileParams = new TransformParams();
     protected final TaskViewSimulator mLiveTileTaskViewSimulator;
     protected final Rect mLastComputedTaskSize = new Rect();
@@ -367,6 +376,7 @@
     protected final Rect mTempRect = new Rect();
     protected final RectF mTempRectF = new RectF();
     private final PointF mTempPointF = new PointF();
+    private final Matrix mTempMatrix = new Matrix();
     private final float[] mTempFloat = new float[1];
     private final List<OnScrollChangedListener> mScrollListeners = new ArrayList<>();
 
@@ -493,9 +503,8 @@
     protected boolean mRunningTaskTileHidden;
     private Task mTmpRunningTask;
     protected int mFocusedTaskId = -1;
-    private float mFocusedTaskRatio;
 
-    private boolean mRunningTaskIconScaledDown = false;
+    private boolean mTaskIconScaledDown = false;
     private boolean mRunningTaskShowScreenshot = false;
 
     private boolean mOverviewStateEnabled;
@@ -632,7 +641,6 @@
         mLiveTileTaskViewSimulator = new TaskViewSimulator(getContext(), getSizeStrategy());
         mLiveTileTaskViewSimulator.recentsViewScale.value = 1;
         mLiveTileTaskViewSimulator.setOrientationState(mOrientationState);
-        mLiveTileTaskViewSimulator.setDrawsBelowRecents(true);
 
         mTintingColor = getForegroundScrimDimColor(context);
     }
@@ -822,7 +830,7 @@
         // taskview for entering split screen, we only pretend to dismiss the task
         if (child instanceof TaskView && child != mSplitHiddenTaskView) {
             TaskView taskView = (TaskView) child;
-            mHasVisibleTaskData.delete(taskView.getTask().key.id);
+            mHasVisibleTaskData.delete(taskView.getTaskId());
             mTaskViewPool.recycle(taskView);
             mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
         }
@@ -944,7 +952,7 @@
     public TaskView getTaskView(int taskId) {
         for (int i = 0; i < getTaskViewCount(); i++) {
             TaskView taskView = getTaskViewAt(i);
-            if (taskView.hasTaskId(taskId)) {
+            if (taskView.getTaskId() == taskId) {
                 return taskView;
             }
         }
@@ -988,6 +996,7 @@
         super.onPageEndTransition();
         if (isClearAllHidden()) {
             mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false);
+        } else {
         }
         if (getNextPage() > 0) {
             setSwipeDownShouldLaunchApp(true);
@@ -995,6 +1004,12 @@
     }
 
     @Override
+    protected float getSignificantMoveThreshold() {
+        return mActivity.getDeviceProfile().isTablet ? SIGNIFICANT_MOVE_THRESHOLD_TABLET
+                : super.getSignificantMoveThreshold();
+    }
+
+    @Override
     public boolean onTouchEvent(MotionEvent ev) {
         super.onTouchEvent(ev);
 
@@ -1143,6 +1158,9 @@
             final TaskView taskView = (TaskView) getChildAt(pageIndex);
             taskView.bind(task, mOrientationState);
         }
+        if (mFocusedTaskId == -1 && getTaskViewCount() > 0) {
+            mFocusedTaskId = getTaskViewAt(0).getTaskId();
+        }
         updateTaskSize();
 
         if (mNextPage == INVALID_PAGE) {
@@ -1204,8 +1222,9 @@
     public void resetTaskVisuals() {
         for (int i = getTaskViewCount() - 1; i >= 0; i--) {
             TaskView taskView = getTaskViewAt(i);
-            if (mIgnoreResetTaskId != taskView.getTask().key.id) {
+            if (mIgnoreResetTaskId != taskView.getTaskId()) {
                 taskView.resetViewTransforms();
+                taskView.setIconScaleAndDim(mTaskIconScaledDown ? 0 : 1);
                 taskView.setStableAlpha(mContentAlpha);
                 taskView.setFullscreenProgress(mFullscreenProgress);
                 taskView.setModalness(mTaskModalness);
@@ -1220,6 +1239,8 @@
             mLiveTileTaskViewSimulator.fullScreenProgress.value = 0;
             mLiveTileTaskViewSimulator.recentsViewScale.value = 1;
 
+            mLiveTileParams.setTargetAlpha(1);
+
             // Similar to setRunningTaskHidden below, reapply the state before runningTaskView is
             // null.
             if (!mRunningTaskShowScreenshot) {
@@ -1230,11 +1251,6 @@
             setRunningTaskHidden(mRunningTaskTileHidden);
         }
 
-        // Force apply the scale.
-        if (mIgnoreResetTaskId != mRunningTaskId) {
-            applyRunningTaskIconScale();
-        }
-
         updateCurveProperties();
         // Update the set of visible task's data
         loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
@@ -1341,6 +1357,11 @@
         mSizeStrategy.calculateGridTaskSize(mActivity, mActivity.getDeviceProfile(),
                 mLastComputedGridTaskSize, mOrientationHandler);
 
+        mTaskGridVerticalDiff = mLastComputedGridTaskSize.top - mLastComputedTaskSize.top;
+        mTopBottomRowHeightDiff =
+                mLastComputedGridTaskSize.height() + dp.overviewTaskThumbnailTopMarginPx
+                        + mRowSpacing;
+
         // Force TaskView to update size from thumbnail
         updateTaskSize();
 
@@ -1371,6 +1392,15 @@
      * Updates TaskView scaling and translation required to support variable width.
      */
     private void updateTaskSize() {
+        updateTaskSize(false);
+    }
+
+    /**
+     * Updates TaskView scaling and translation required to support variable width.
+     *
+     * @param isTaskDismissal indicates if update was called due to task dismissal
+     */
+    private void updateTaskSize(boolean isTaskDismissal) {
         final int taskCount = getTaskViewCount();
         if (taskCount == 0) {
             return;
@@ -1380,18 +1410,17 @@
         for (int i = 0; i < taskCount; i++) {
             TaskView taskView = getTaskViewAt(i);
             taskView.updateTaskSize();
-            taskView.getPrimaryFullscreenTranslationProperty().set(taskView,
-                    accumulatedTranslationX);
-            taskView.getSecondaryFullscreenTranslationProperty().set(taskView, 0f);
+            taskView.getPrimaryNonGridTranslationProperty().set(taskView, accumulatedTranslationX);
+            taskView.getSecondaryNonGridTranslationProperty().set(taskView, 0f);
             // Compensate space caused by TaskView scaling.
             float widthDiff =
-                    taskView.getLayoutParams().width * (1 - taskView.getFullscreenScale());
+                    taskView.getLayoutParams().width * (1 - taskView.getNonGridScale());
             accumulatedTranslationX += mIsRtl ? widthDiff : -widthDiff;
         }
 
         mClearAllButton.setFullscreenTranslationPrimary(accumulatedTranslationX);
 
-        updateGridProperties();
+        updateGridProperties(isTaskDismissal);
     }
 
     public void getTaskSize(Rect outRect) {
@@ -1406,19 +1435,7 @@
     public Point getSelectedTaskSize() {
         mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(), mTempRect,
                 mOrientationHandler);
-        int taskWidth = mTempRect.width();
-        int taskHeight = mTempRect.height();
-        if (mFocusedTaskId != -1) {
-            int boxLength = Math.max(taskWidth, taskHeight);
-            if (mFocusedTaskRatio > 1) {
-                taskWidth = boxLength;
-                taskHeight = (int) (boxLength / mFocusedTaskRatio);
-            } else {
-                taskWidth = (int) (boxLength * mFocusedTaskRatio);
-                taskHeight = boxLength;
-            }
-        }
-        return new Point(taskWidth, taskHeight);
+        return new Point(mTempRect.width(), mTempRect.height());
     }
 
     /** Gets the last computed task size */
@@ -1450,13 +1467,7 @@
             loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
 
             // After scrolling, update ActionsView's visibility.
-            TaskView focusedTaskView = getFocusedTaskView();
-            if (focusedTaskView != null) {
-                float scrollDiff = Math.abs(getScrollForPage(indexOfChild(focusedTaskView))
-                        - mOrientationHandler.getPrimaryScroll(this));
-                float delta = (mGridSideMargin - scrollDiff) / (float) mGridSideMargin;
-                mActionsView.getScrollAlpha().setValue(Utilities.boundToRange(delta, 0, 1));
-            }
+            updateActionsViewScrollAlpha();
         }
 
         // Update the high res thumbnail loader state
@@ -1464,6 +1475,20 @@
         return scrolling;
     }
 
+    private void updateActionsViewScrollAlpha() {
+        float scrollAlpha = 1f;
+        if (showAsGrid()) {
+            TaskView focusedTaskView = getFocusedTaskView();
+            if (focusedTaskView != null) {
+                float scrollDiff = Math.abs(getScrollForPage(indexOfChild(focusedTaskView))
+                        - mOrientationHandler.getPrimaryScroll(this));
+                float delta = (mGridSideMargin - scrollDiff) / (float) mGridSideMargin;
+                scrollAlpha = Utilities.boundToRange(delta, 0, 1);
+            }
+        }
+        mActionsView.getScrollAlpha().setValue(scrollAlpha);
+    }
+
     /**
      * Scales and adjusts translation of adjacent pages as if on a curved carousel.
      */
@@ -1606,7 +1631,7 @@
         setCurrentTask(-1);
         mIgnoreResetTaskId = -1;
         mTaskListChangeId = -1;
-        mFocusedTaskId = -1;
+        mFocusedTaskId = getTaskViewCount() > 0 ? getTaskViewAt(0).getTaskId() : -1;
 
         if (mRecentsAnimationController != null) {
             if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile) {
@@ -1618,7 +1643,6 @@
         }
         setEnableDrawingLiveTile(false);
         mLiveTileParams.setTargetSet(null);
-        mLiveTileTaskViewSimulator.setDrawsBelowRecents(true);
 
         // These are relatively expensive and don't need to be done this frame (RecentsView isn't
         // visible anyway), so defer by a frame to get off the critical path, e.g. app to home.
@@ -1648,11 +1672,8 @@
         return getTaskView(mFocusedTaskId);
     }
 
-    /**
-     * Returns the width to height ratio of the focused {@link TaskView}.
-     */
-    public float getFocusedTaskRatio() {
-        return mFocusedTaskRatio;
+    protected @Nullable TaskView getHomeTaskView() {
+        return null;
     }
 
     /**
@@ -1691,7 +1712,7 @@
         setEnableFreeScroll(false);
         setEnableDrawingLiveTile(false);
         setRunningTaskHidden(true);
-        setRunningTaskIconScaledDown(true);
+        setTaskIconScaledDown(true);
     }
 
     /**
@@ -1699,9 +1720,7 @@
      * {@link #onGestureAnimationStart} and {@link #onGestureAnimationEnd()}.
      */
     public void onSwipeUpAnimationSuccess() {
-        if (getRunningTaskView() != null) {
-            animateUpRunningTaskIconScale();
-        }
+        animateUpTaskIconScale();
         setSwipeDownShouldLaunchApp(true);
     }
 
@@ -1783,12 +1802,8 @@
             setRunningTaskViewShowScreenshot(true);
         }
         setRunningTaskHidden(false);
-        animateUpRunningTaskIconScale();
-
-        if (mCurrentGestureEndTarget == GestureState.GestureEndTarget.RECENTS
-                && (!showAsGrid() || getFocusedTaskView() != null)) {
-            animateActionsViewIn();
-        }
+        animateUpTaskIconScale();
+        animateActionsViewIn();
 
         mCurrentGestureEndTarget = null;
     }
@@ -1830,9 +1845,7 @@
         boolean runningTaskTileHidden = mRunningTaskTileHidden;
         int runningTaskId = runningTaskInfo == null ? -1 : runningTaskInfo.taskId;
         setCurrentTask(runningTaskId);
-        if (mActivity.getDeviceProfile().isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()) {
-            setFocusedTask(runningTaskId);
-        }
+        mFocusedTaskId = runningTaskId;
         setCurrentPage(getRunningTaskIndex());
         setRunningTaskViewShowScreenshot(false);
         setRunningTaskHidden(runningTaskTileHidden);
@@ -1853,7 +1866,7 @@
 
         if (mRunningTaskId != -1) {
             // Reset the state on the old running task view
-            setRunningTaskIconScaledDown(false);
+            setTaskIconScaledDown(false);
             setRunningTaskViewShowScreenshot(true);
             setRunningTaskHidden(false);
         }
@@ -1861,15 +1874,6 @@
     }
 
     /**
-     * Sets the focused task id and store the width to height ratio of the focused task.
-     */
-    protected void setFocusedTask(int focusedTaskId) {
-        mFocusedTaskId = focusedTaskId;
-        mFocusedTaskRatio =
-                mLastComputedTaskSize.width() / (float) mLastComputedTaskSize.height();
-    }
-
-    /**
      * Hides the tile associated with {@link #mRunningTaskId}
      */
     public void setRunningTaskHidden(boolean isHidden) {
@@ -1894,21 +1898,13 @@
         }
     }
 
-    public void setRunningTaskIconScaledDown(boolean isScaledDown) {
-        if (mRunningTaskIconScaledDown != isScaledDown) {
-            mRunningTaskIconScaledDown = isScaledDown;
-            applyRunningTaskIconScale();
-        }
-    }
-
-    public boolean isTaskIconScaledDown(TaskView taskView) {
-        return mRunningTaskIconScaledDown && getRunningTaskView() == taskView;
-    }
-
-    private void applyRunningTaskIconScale() {
-        TaskView firstTask = getRunningTaskView();
-        if (firstTask != null) {
-            firstTask.setIconScaleAndDim(mRunningTaskIconScaledDown ? 0 : 1);
+    public void setTaskIconScaledDown(boolean isScaledDown) {
+        if (mTaskIconScaledDown != isScaledDown) {
+            mTaskIconScaledDown = isScaledDown;
+            int taskCount = getTaskViewCount();
+            for (int i = 0; i < taskCount; i++) {
+                getTaskViewAt(i).setIconScaleAndDim(mTaskIconScaledDown ? 0 : 1);
+            }
         }
     }
 
@@ -1919,19 +1915,13 @@
         anim.start();
     }
 
-    private void animateActionsViewOut() {
-        ObjectAnimator anim = ObjectAnimator.ofFloat(
-                mActionsView.getVisibilityAlpha(), MultiValueAlpha.VALUE, 1, 0);
-        anim.setDuration(TaskView.SCALE_ICON_DURATION);
-        anim.start();
-    }
-
-    public void animateUpRunningTaskIconScale() {
-        mRunningTaskIconScaledDown = false;
-        TaskView firstTask = getRunningTaskView();
-        if (firstTask != null) {
-            firstTask.setIconScaleAnimStartProgress(0f);
-            firstTask.animateIconScaleAndDimIntoView();
+    public void animateUpTaskIconScale() {
+        mTaskIconScaledDown = false;
+        int taskCount = getTaskViewCount();
+        for (int i = 0; i < taskCount; i++) {
+            TaskView taskView = getTaskViewAt(i);
+            taskView.setIconScaleAnimStartProgress(0f);
+            taskView.animateIconScaleAndDimIntoView();
         }
     }
 
@@ -1957,20 +1947,8 @@
             return;
         }
 
-        final int boxLength = Math.max(mLastComputedGridTaskSize.width(),
-                mLastComputedGridTaskSize.height());
         int taskTopMargin = mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
 
-        /*
-         * taskGridVerticalDiff is used to position the top of a task in the top row of the grid
-         * heightOffset is the vertical space one grid task takes + space between top and
-         *   bottom row
-         * Summed together they provide the top position for bottom row of grid tasks
-         */
-        final float taskGridVerticalDiff =
-                mLastComputedGridTaskSize.top - mLastComputedTaskSize.top;
-        final float heightOffset = (boxLength + taskTopMargin) + mRowSpacing;
-
         int topRowWidth = 0;
         int bottomRowWidth = 0;
         float topAccumulatedTranslationX = 0;
@@ -1990,6 +1968,8 @@
         int snappedTaskRowWidth = 0;
         int snappedPage = getNextPage();
         TaskView snappedTaskView = getTaskViewAtByAbsoluteIndex(snappedPage);
+        TaskView homeTaskView = getHomeTaskView();
+        TaskView nextFocusedTaskView = null;
 
         if (!isTaskDismissal) {
             mTopRowIdSet.clear();
@@ -2027,15 +2007,20 @@
                     // calculate the distance focused task need to shift.
                     focusedTaskShift += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing;
                 }
-                int taskId = taskView.getTask().key.id;
+                int taskId = taskView.getTaskId();
                 boolean isTopRow = isTaskDismissal ? mTopRowIdSet.contains(taskId)
                         : topRowWidth <= bottomRowWidth;
                 if (isTopRow) {
-                    topRowWidth += taskWidthAndSpacing;
+                    if (homeTaskView != null && nextFocusedTaskView == null) {
+                        // TaskView will be focused when swipe up, don't count towards row width.
+                        nextFocusedTaskView = taskView;
+                    } else {
+                        topRowWidth += taskWidthAndSpacing;
+                    }
                     topSet.add(i);
                     mTopRowIdSet.add(taskId);
 
-                    taskView.setGridTranslationY(taskGridVerticalDiff);
+                    taskView.setGridTranslationY(mTaskGridVerticalDiff);
 
                     // Move horizontally into empty space.
                     float widthOffset = 0;
@@ -2054,7 +2039,7 @@
                     bottomSet.add(i);
 
                     // Move into bottom row.
-                    taskView.setGridTranslationY(heightOffset + taskGridVerticalDiff);
+                    taskView.setGridTranslationY(mTopBottomRowHeightDiff + mTaskGridVerticalDiff);
 
                     // Move horizontally into empty space.
                     float widthOffset = 0;
@@ -2078,20 +2063,18 @@
         // We need to maintain snapped task's page scroll invariant between quick switch and
         // overview, so we sure snapped task's grid translation is 0, and add a non-fullscreen
         // translationX that is the same as snapped task's full scroll adjustment.
-        float snappedTaskFullscreenScrollAdjustment = 0;
+        float snappedTaskNonGridScrollAdjustment = 0;
         float snappedTaskGridTranslationX = 0;
         if (snappedTaskView != null) {
-            snappedTaskFullscreenScrollAdjustment = snappedTaskView.getScrollAdjustment(
+            snappedTaskNonGridScrollAdjustment = snappedTaskView.getScrollAdjustment(
                     /*fullscreenEnabled=*/true, /*gridEnabled=*/false);
             snappedTaskGridTranslationX = gridTranslations[snappedPage - mTaskViewStartIndex];
         }
 
         for (int i = 0; i < taskCount; i++) {
             TaskView taskView = getTaskViewAt(i);
-            taskView.setGridTranslationX(gridTranslations[i] - snappedTaskGridTranslationX);
-            taskView.getPrimaryNonFullscreenTranslationProperty().set(taskView,
-                    snappedTaskFullscreenScrollAdjustment);
-            taskView.getSecondaryNonFullscreenTranslationProperty().set(taskView, 0f);
+            taskView.setGridTranslationX(gridTranslations[i] - snappedTaskGridTranslationX
+                    + snappedTaskNonGridScrollAdjustment);
         }
 
         // Use the accumulated translation of the row containing the last task.
@@ -2126,7 +2109,7 @@
 
         float clearAllTotalTranslationX =
                 clearAllAccumulatedTranslation + clearAllShorterRowCompensation
-                        + clearAllShortTotalCompensation + snappedTaskFullscreenScrollAdjustment;
+                        + clearAllShortTotalCompensation + snappedTaskNonGridScrollAdjustment;
         if (focusedTaskIndex < taskCount) {
             // Shift by focused task's width and spacing if a task is focused.
             clearAllTotalTranslationX +=
@@ -2137,8 +2120,10 @@
         // of swiping up after quick switch.
         if (snappedTaskView != null) {
             int distanceFromClearAll = longRowWidth - snappedTaskRowWidth;
+            // ClearAllButton should be off screen when snapped task is in its snapped position.
             int minimumDistance =
-                    mLastComputedGridSize.width() - snappedTaskView.getLayoutParams().width;
+                    mTaskWidth - snappedTaskView.getLayoutParams().width
+                            + (mLastComputedGridSize.width() - mTaskWidth) / 2;
             if (distanceFromClearAll < minimumDistance) {
                 int distanceDifference = minimumDistance - distanceFromClearAll;
                 clearAllTotalTranslationX += mIsRtl ? -distanceDifference : distanceDifference;
@@ -2158,8 +2143,8 @@
         if (taskView1 == null || taskView2 == null) {
             return false;
         }
-        int taskId1 = taskView1.getTask().key.id;
-        int taskId2 = taskView2.getTask().key.id;
+        int taskId1 = taskView1.getTaskId();
+        int taskId2 = taskView2.getTaskId();
         if (taskId1 == mFocusedTaskId || taskId2 == mFocusedTaskId) {
             return false;
         }
@@ -2237,6 +2222,10 @@
         // Use setFloat instead of setViewAlpha as we want to keep the view visible even when it's
         // alpha is set to 0 so that it can be recycled in the view pool properly
         anim.setFloat(taskView, VIEW_ALPHA, 0, clampToProgress(ACCEL, 0, 0.5f));
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && taskView.isRunningTask()) {
+            anim.setFloat(mLiveTileParams, TransformParams.TARGET_ALPHA, 0,
+                    clampToProgress(ACCEL, 0, 0.5f));
+        }
         SplitSelectStateController splitController = mSplitPlaceholderView.getSplitController();
 
         ResourceProvider rp = DynamicResource.provider(mActivity);
@@ -2289,8 +2278,16 @@
         }
     }
 
-    public PendingAnimation createTaskDismissAnimation(TaskView taskView, boolean animateTaskView,
-            boolean shouldRemoveTask, long duration) {
+    /**
+     * Creates a {@link PendingAnimation} for dismissing the specified {@link TaskView}.
+     * @param dismissedTaskView the {@link TaskView} to be dismissed
+     * @param animateTaskView whether the {@link TaskView} to be dismissed should be animated
+     * @param shouldRemoveTask whether the associated {@link Task} should be removed from
+     *                         ActivityManager after dismissal
+     * @param duration duration of the animation
+     */
+    public PendingAnimation createTaskDismissAnimation(TaskView dismissedTaskView,
+            boolean animateTaskView, boolean shouldRemoveTask, long duration) {
         if (mPendingAnimation != null) {
             mPendingAnimation.createPlaybackController().dispatchOnCancel().dispatchOnEnd();
         }
@@ -2301,30 +2298,65 @@
             return anim;
         }
 
+        boolean showAsGrid = showAsGrid();
+        int taskCount = getTaskViewCount();
+        int dismissedIndex = indexOfChild(dismissedTaskView);
+        int dismissedTaskId = dismissedTaskView.getTaskId();
+
+        // Grid specific properties.
+        boolean isFocusedTaskDismissed = false;
+        TaskView nextFocusedTaskView = null;
+        boolean nextFocusedTaskFromTop = false;
+        float dismissedTaskWidth = 0;
+        float nextFocusedTaskWidth = 0;
+
+        // Non-grid specific properties.
         int[] oldScroll = new int[count];
         int[] newScroll = new int[count];
-        getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC);
-        getPageScrolls(newScroll, false, (v) -> v.getVisibility() != GONE && v != taskView);
-        int taskCount = getTaskViewCount();
         int scrollDiffPerPage = 0;
-        if (count > 1) {
-            scrollDiffPerPage = Math.abs(oldScroll[1] - oldScroll[0]);
-        }
-        int draggedIndex = indexOfChild(taskView);
-
-        boolean isFocusedTaskDismissed = taskView.getTask().key.id == mFocusedTaskId;
-        if (isFocusedTaskDismissed && showAsGrid()) {
-            anim.setFloat(mActionsView, VIEW_ALPHA, 0, clampToProgress(ACCEL_0_5, 0, 0.5f));
-        }
-        float dismissedTaskWidth = taskView.getLayoutParams().width + mPageSpacing;
         boolean needsCurveUpdates = false;
+
+        if (showAsGrid) {
+            dismissedTaskWidth = dismissedTaskView.getLayoutParams().width + mPageSpacing;
+            isFocusedTaskDismissed = dismissedTaskId == mFocusedTaskId;
+            if (isFocusedTaskDismissed) {
+                nextFocusedTaskFromTop =
+                        mTopRowIdSet.size() > 0 && mTopRowIdSet.size() >= (taskCount - 1) / 2f;
+                // Pick the next focused task from the preferred row.
+                for (int i = 0; i < taskCount; i++) {
+                    TaskView taskView = getTaskViewAt(i);
+                    if (taskView == dismissedTaskView) {
+                        continue;
+                    }
+                    boolean isTopRow = mTopRowIdSet.contains(taskView.getTaskId());
+                    if ((nextFocusedTaskFromTop && isTopRow
+                            || (!nextFocusedTaskFromTop && !isTopRow))) {
+                        nextFocusedTaskView = taskView;
+                        break;
+                    }
+                }
+                if (nextFocusedTaskView != null) {
+                    nextFocusedTaskWidth =
+                            nextFocusedTaskView.getLayoutParams().width + mPageSpacing;
+                }
+            }
+        } else {
+            getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC);
+            getPageScrolls(newScroll, false,
+                    v -> v.getVisibility() != GONE && v != dismissedTaskView);
+            if (count > 1) {
+                scrollDiffPerPage = Math.abs(oldScroll[1] - oldScroll[0]);
+            }
+        }
+
+        int distanceFromDismissedTask = 0;
         for (int i = 0; i < count; i++) {
             View child = getChildAt(i);
-            if (child == taskView) {
+            if (child == dismissedTaskView) {
                 if (animateTaskView) {
-                    addDismissedTaskAnimations(taskView, duration, anim);
+                    addDismissedTaskAnimations(dismissedTaskView, duration, anim);
                 }
-            } else if (!showAsGrid()) {
+            } else if (!showAsGrid) {
                 // Compute scroll offsets from task dismissal for animation.
                 // If we just take newScroll - oldScroll, everything to the right of dragged task
                 // translates to the left. We need to offset this in some cases:
@@ -2333,15 +2365,15 @@
                 // - Current page is rightmost page (leftmost for RTL)
                 // - Dragging an adjacent page on the left side (right side for RTL)
                 int offset = mIsRtl ? scrollDiffPerPage : 0;
-                if (mCurrentPage == draggedIndex) {
+                if (mCurrentPage == dismissedIndex) {
                     int lastPage = taskCount - 1;
                     if (mCurrentPage == lastPage) {
                         offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage;
                     }
                 } else {
-                    // Dragging an adjacent page.
+                    // Dismissing an adjacent page.
                     int negativeAdjacent = mCurrentPage - 1; // (Right in RTL, left in LTR)
-                    if (draggedIndex == negativeAdjacent) {
+                    if (dismissedIndex == negativeAdjacent) {
                         offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage;
                     }
                 }
@@ -2354,7 +2386,7 @@
 
                     float additionalDismissDuration =
                             ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET * Math.abs(
-                                    i - draggedIndex);
+                                    i - dismissedIndex);
                     anim.setFloat(child, translationProperty, scrollDiff, clampToProgress(LINEAR,
                             Utilities.boundToRange(INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
                                     + additionalDismissDuration, 0f, 1f), 1));
@@ -2371,21 +2403,46 @@
                     needsCurveUpdates = true;
                 }
             } else if (child instanceof TaskView) {
+                TaskView taskView = (TaskView) child;
+                if (isFocusedTaskDismissed) {
+                    if (!isSameGridRow(taskView, nextFocusedTaskView)) {
+                        continue;
+                    }
+                } else {
+                    if (i < dismissedIndex || !isSameGridRow(taskView, dismissedTaskView)) {
+                        continue;
+                    }
+                }
                 // Animate task with index >= dismissed index and in the same row as the
-                // dismissed index, or if the dismissed task was the focused task. Offset
-                // successive task dismissal durations for a staggered effect.
-                if (isFocusedTaskDismissed || (i >= draggedIndex && isSameGridRow((TaskView) child,
-                        taskView))) {
-                    FloatProperty translationProperty =
-                            ((TaskView) child).getPrimaryDismissTranslationProperty();
-                    float additionalDismissDuration =
-                            ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET * Math.abs(
-                                    i - draggedIndex);
-                    anim.setFloat(child, translationProperty,
-                            !mIsRtl ? -dismissedTaskWidth : dismissedTaskWidth,
-                            clampToProgress(LINEAR, Utilities.boundToRange(
-                                    INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
-                                            + additionalDismissDuration, 0f, 1f), 1));
+                // dismissed index or next focused index. Offset successive task dismissal
+                // durations for a staggered effect.
+                float animationStartProgress = Utilities.boundToRange(
+                        INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
+                                + ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
+                                * ++distanceFromDismissedTask, 0f, 1f);
+                if (taskView == nextFocusedTaskView) {
+                    // Enlarge the task to be focused next, and translate into focus position.
+                    float scale = mTaskWidth / (float) mLastComputedGridTaskSize.width();
+                    anim.setFloat(taskView, TaskView.SNAPSHOT_SCALE, scale,
+                            clampToProgress(LINEAR, animationStartProgress, 1f));
+                    anim.setFloat(taskView, taskView.getPrimaryDismissTranslationProperty(),
+                            mIsRtl ? dismissedTaskWidth : -dismissedTaskWidth,
+                            clampToProgress(LINEAR, animationStartProgress, 1f));
+                    float secondaryTranslation = -mTaskGridVerticalDiff;
+                    if (!nextFocusedTaskFromTop) {
+                        secondaryTranslation -= mTopBottomRowHeightDiff;
+                    }
+                    anim.setFloat(taskView, taskView.getSecondaryDissmissTranslationProperty(),
+                            secondaryTranslation,
+                            clampToProgress(LINEAR, animationStartProgress, 1f));
+                    anim.setFloat(taskView, TaskView.FOCUS_TRANSITION, 0f,
+                            clampToProgress(LINEAR, 0f, ANIMATION_DISMISS_PROGRESS_MIDPOINT));
+                } else {
+                    float primaryTranslation =
+                            isFocusedTaskDismissed ? nextFocusedTaskWidth : dismissedTaskWidth;
+                    anim.setFloat(taskView, taskView.getPrimaryDismissTranslationProperty(),
+                            mIsRtl ? primaryTranslation : -primaryTranslation,
+                            clampToProgress(LINEAR, animationStartProgress, 1f));
                 }
             }
         }
@@ -2396,15 +2453,16 @@
 
         // Add a tiny bit of translation Z, so that it draws on top of other views
         if (animateTaskView) {
-            taskView.setTranslationZ(0.1f);
+            dismissedTaskView.setTranslationZ(0.1f);
         }
 
         mPendingAnimation = anim;
+        final TaskView finalNextFocusedTaskView = nextFocusedTaskView;
         mPendingAnimation.addEndListener(new Consumer<Boolean>() {
             @Override
             public void accept(Boolean success) {
                 if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
-                        && taskView.isRunningTask() && success) {
+                        && dismissedTaskView.isRunningTask() && success) {
                     finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
                             () -> onEnd(success));
                 } else {
@@ -2416,15 +2474,16 @@
             private void onEnd(boolean success) {
                 if (success) {
                     if (shouldRemoveTask) {
-                        if (taskView.getTask() != null) {
-                            if (ENABLE_QUICKSTEP_LIVE_TILE.get() && taskView.isRunningTask()) {
+                        if (dismissedTaskView.getTask() != null) {
+                            if (ENABLE_QUICKSTEP_LIVE_TILE.get()
+                                    && dismissedTaskView.isRunningTask()) {
                                 finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
-                                        () -> removeTaskInternal(taskView));
+                                        () -> removeTaskInternal(dismissedTaskId));
                             } else {
-                                removeTaskInternal(taskView);
+                                removeTaskInternal(dismissedTaskId);
                             }
                             mActivity.getStatsLogManager().logger()
-                                    .withItemInfo(taskView.getItemInfo())
+                                    .withItemInfo(dismissedTaskView.getItemInfo())
                                     .log(LAUNCHER_TASK_DISMISS_SWIPE_UP);
                         }
                     }
@@ -2434,32 +2493,34 @@
                     resetTaskVisuals();
 
                     int pageToSnapTo = mCurrentPage;
-                    // Snap to start if focused task was dismissed, as after quick switch it could
-                    // be at any page but the focused task always displays at the start.
-                    if (taskView.getTask().key.id == mFocusedTaskId) {
-                        pageToSnapTo = mTaskViewStartIndex;
-                    } else if (draggedIndex < pageToSnapTo || pageToSnapTo == (getTaskViewCount()
-                            - 1)) {
+                    if ((dismissedIndex < pageToSnapTo && !showAsGrid)
+                            || pageToSnapTo == taskCount - 1) {
                         pageToSnapTo -= 1;
                     }
-                    removeViewInLayout(taskView);
+                    if (showAsGrid) {
+                        int primaryScroll = mOrientationHandler.getPrimaryScroll(RecentsView.this);
+                        int currentPageScroll = getScrollForPage(pageToSnapTo);
+                        mCurrentPageScrollDiff = primaryScroll - currentPageScroll;
+                    }
+                    removeViewInLayout(dismissedTaskView);
+                    mTopRowIdSet.remove(dismissedTaskId);
 
-                    if (getTaskViewCount() == 0) {
+                    if (taskCount == 1) {
                         removeViewInLayout(mClearAllButton);
                         startHome();
                     } else {
-                        snapToPageImmediately(pageToSnapTo);
-                        dispatchScrollChanged();
-                        // Grid got messed up, reapply.
-                        updateGridProperties(true);
-                        if (showAsGrid() && getFocusedTaskView() == null
-                                && mActionsView.getVisibilityAlpha().getValue() == 1) {
-                            animateActionsViewOut();
+                        // Update focus task and its size.
+                        if (finalNextFocusedTaskView != null) {
+                            mFocusedTaskId = finalNextFocusedTaskView.getTaskId();
+                            mTopRowIdSet.remove(mFocusedTaskId);
+                            finalNextFocusedTaskView.animateIconScaleAndDimIntoView();
                         }
+                        updateTaskSize(true);
+                        // Update scroll and snap to page.
+                        updateScrollSynchronously();
+                        setCurrentPage(pageToSnapTo);
+                        dispatchScrollChanged();
                     }
-                    // Update the layout synchronously so that the position of next view is
-                    // immediately available.
-                    onLayout(false /*  changed */, getLeft(), getTop(), getRight(), getBottom());
                 }
                 onDismissAnimationEnds();
                 mPendingAnimation = null;
@@ -2468,10 +2529,9 @@
         return anim;
     }
 
-    private void removeTaskInternal(TaskView taskView) {
-        UI_HELPER_EXECUTOR.getHandler().postDelayed(() ->
-                        ActivityManagerWrapper.getInstance().removeTask(
-                                taskView.getTask().key.id),
+    private void removeTaskInternal(int dismissedTaskId) {
+        UI_HELPER_EXECUTOR.getHandler().postDelayed(
+                () -> ActivityManagerWrapper.getInstance().removeTask(dismissedTaskId),
                 REMOVE_TASK_WAIT_FOR_APP_STOP_MS);
     }
 
@@ -2502,7 +2562,8 @@
         int taskCount = getTaskViewCount();
         for (int i = 0; i < taskCount; i++) {
             TaskView taskView = getTaskViewAt(i);
-            if (taskView == mSplitHiddenTaskView && taskView != getFocusedTaskView()) {
+            if (taskView == mSplitHiddenTaskView
+                    && !(showAsGrid() && taskView == getFocusedTaskView())) {
                 // Case where the hidden task view would have overlapped w/ placeholder,
                 // but because it's going to hide we don't care
                 // TODO (b/187312247) edge case for thumbnails that are off screen but scroll on
@@ -2650,7 +2711,7 @@
         mContentAlpha = alpha;
         for (int i = getTaskViewCount() - 1; i >= 0; i--) {
             TaskView child = getTaskViewAt(i);
-            if (!mRunningTaskTileHidden || child.getTask().key.id != mRunningTaskId) {
+            if (!mRunningTaskTileHidden || child.getTaskId() != mRunningTaskId) {
                 child.setStableAlpha(alpha);
             }
         }
@@ -2779,6 +2840,12 @@
 
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        // If we're going to HOME, avoid unnecessary onLayout that cause TaskViews to re-arrange
+        // during animation to HOME.
+        if (mCurrentGestureEndTarget == GestureState.GestureEndTarget.HOME) {
+            return;
+        }
+
         super.onLayout(changed, left, top, right, bottom);
 
         updateEmptyStateUi(changed);
@@ -2875,6 +2942,12 @@
             outRect.offset(taskView.getPersistentTranslationX(),
                     taskView.getPersistentTranslationY());
             outRect.top += mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
+
+            mTempMatrix.reset();
+            float persistentScale = taskView.getPersistentScale();
+            mTempMatrix.postScale(persistentScale, persistentScale,
+                    mIsRtl ? outRect.right : outRect.left, outRect.top);
+            mTempMatrix.mapRect(outRect);
         }
         outRect.offset(mOrientationHandler.getPrimaryValue(-midPointScroll, 0),
                 mOrientationHandler.getSecondaryValue(-midPointScroll, 0));
@@ -3480,9 +3553,13 @@
     }
 
     /**
-     * Updates page scroll synchronously and layout child views.
+     * Updates page scroll synchronously after measure and layout child views.
      */
     public void updateScrollSynchronously() {
+        // onMeasure is needed to update child's measured width which is used in scroll calculation,
+        // in case TaskView sizes has changed when being focused/unfocused.
+        onMeasure(makeMeasureSpec(getMeasuredWidth(), EXACTLY),
+                makeMeasureSpec(getMeasuredHeight(), EXACTLY));
         onLayout(false /*  changed */, getLeft(), getTop(), getRight(), getBottom());
         updateMinAndMaxScrollX();
     }
@@ -3680,6 +3757,7 @@
     public void setOverviewGridEnabled(boolean overviewGridEnabled) {
         if (mOverviewGridEnabled != overviewGridEnabled) {
             mOverviewGridEnabled = overviewGridEnabled;
+            updateActionsViewScrollAlpha();
             // Request layout to ensure scroll position is recalculated with updated mGridProgress.
             requestLayout();
         }
@@ -3825,11 +3903,6 @@
                 && mCurrentGestureEndTarget != GestureState.GestureEndTarget.RECENTS;
     }
 
-    public boolean shouldShowOverviewActionsForState(STATE_TYPE state) {
-        return !state.displayOverviewTasksAsGrid(mActivity.getDeviceProfile())
-                || getFocusedTaskView() != null;
-    }
-
     /**
      * Used to register callbacks for when our empty message state changes.
      *
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index 906e854..1345a94 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -16,7 +16,6 @@
 
 package com.android.quickstep.views;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.quickstep.views.TaskThumbnailView.DIM_ALPHA;
 
 import android.animation.Animator;
@@ -221,17 +220,7 @@
                 menuOptionView, mActivity.getDeviceProfile());
         menuOptionView.setEnabled(menuOption.isEnabled());
         menuOptionView.setAlpha(menuOption.isEnabled() ? 1 : 0.5f);
-        menuOptionView.setOnClickListener(view -> {
-            if (ENABLE_QUICKSTEP_LIVE_TILE.get() && !menuOption.hasFinishRecentsInAction()) {
-                RecentsView recentsView = mTaskView.getRecentsView();
-                recentsView.switchToScreenshot(null,
-                        () -> recentsView.finishRecentsAnimation(true /* toRecents */,
-                                false /* shouldPip */,
-                                () -> menuOption.onClick(view)));
-            } else {
-                menuOption.onClick(view);
-            }
-        });
+        menuOptionView.setOnClickListener(menuOption::onClick);
         mOptionLayout.addView(menuOptionView);
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index c70596d..1ced86b 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -19,7 +19,6 @@
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
 
-import static com.android.launcher3.Utilities.comp;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN;
 
@@ -32,8 +31,6 @@
 import android.graphics.Insets;
 import android.graphics.Matrix;
 import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Shader;
@@ -85,7 +82,6 @@
     private TaskOverlay mOverlay;
     private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
     private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-    private final Paint mClearPaint = new Paint();
     private final Paint mDimmingPaintAfterClearing = new Paint();
     private final int mDimColor;
 
@@ -116,7 +112,6 @@
         super(context, attrs, defStyleAttr);
         mPaint.setFilterBitmap(true);
         mBackgroundPaint.setColor(Color.WHITE);
-        mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
         mActivity = BaseActivity.fromContext(context);
         // Initialize with placeholder value. It is overridden later by TaskView
         mFullscreenParams = TEMP_PARAMS.get(context);
@@ -168,6 +163,7 @@
             mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
             mPaint.setShader(mBitmapShader);
             updateThumbnailMatrix();
+            refreshOverlay();
         } else {
             mBitmapShader = null;
             mThumbnailData = null;
@@ -309,17 +305,7 @@
             float cornerRadius) {
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             if (mTask != null && getTaskView().isRunningTask() && !getTaskView().showScreenshot()) {
-                // TODO(b/189265196): Temporary fix to align the surface with the cutout perfectly.
-                // Round up only when the live tile task is displayed in Overview.
-                float rounding = comp(mFullscreenParams.mFullscreenProgress);
-                float left = x + rounding / 2;
-                float top = y + rounding / 2;
-                float right = width - rounding;
-                float bottom = height - rounding;
-
-                canvas.drawRoundRect(left, top, right, bottom, cornerRadius, cornerRadius,
-                        mClearPaint);
-                canvas.drawRoundRect(left, top, right, bottom, cornerRadius, cornerRadius,
+                canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius,
                         mDimmingPaintAfterClearing);
                 return;
             }
@@ -431,7 +417,9 @@
      */
     public static class PreviewPositionHelper {
 
-        // Contains the portion of the thumbnail that is clipped when fullscreen progress = 0.
+        private static final RectF EMPTY_RECT_F = new RectF();
+
+        // Contains the portion of the thumbnail that is unclipped when fullscreen progress = 1.
         private final RectF mClippedInsets = new RectF();
         private final Matrix mMatrix = new Matrix();
         private boolean mIsOrientationChanged;
@@ -636,15 +624,17 @@
                     break;
             }
             mClippedInsets.offsetTo(newLeftInset * scale, newTopInset * scale);
-            mMatrix.postTranslate(translateX - mClippedInsets.left,
-                    translateY - mClippedInsets.top);
+            mMatrix.postTranslate(translateX, translateY);
+            if (TaskView.FULL_THUMBNAIL) {
+                mMatrix.postTranslate(-mClippedInsets.left, -mClippedInsets.top);
+            }
         }
 
         /**
          * Insets to used for clipping the thumbnail (in case it is drawing outside its own space)
          */
         public RectF getInsetsToDrawInFullscreen() {
-            return mClippedInsets;
+            return TaskView.FULL_THUMBNAIL ? mClippedInsets : EMPTY_RECT_F;
         }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 2d322e9..b272def 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -147,13 +147,18 @@
      */
     public static final boolean CLIP_STATUS_AND_NAV_BARS = false;
 
+    /**
+     * Should the TaskView scale down to fit whole thumbnail in fullscreen.
+     */
+    public static final boolean FULL_THUMBNAIL = false;
+
     private static final float EDGE_SCALE_DOWN_FACTOR_CAROUSEL = 0.03f;
     private static final float EDGE_SCALE_DOWN_FACTOR_GRID = 0.00f;
 
     public static final long SCALE_ICON_DURATION = 120;
     private static final long DIM_ANIM_DURATION = 700;
 
-    private static final Interpolator FULLSCREEN_INTERPOLATOR = ACCEL_DEACCEL;
+    private static final Interpolator GRID_INTERPOLATOR = ACCEL_DEACCEL;
 
     /**
      * This technically can be a vanilla {@link TouchDelegate} class, however that class requires
@@ -167,7 +172,7 @@
     private static final List<Rect> SYSTEM_GESTURE_EXCLUSION_RECT =
             Collections.singletonList(new Rect());
 
-    private static final FloatProperty<TaskView> FOCUS_TRANSITION =
+    public static final FloatProperty<TaskView> FOCUS_TRANSITION =
             new FloatProperty<TaskView>("focusTransition") {
                 @Override
                 public void setValue(TaskView taskView, float v) {
@@ -284,55 +289,42 @@
                 }
             };
 
-    private static final FloatProperty<TaskView> FULLSCREEN_TRANSLATION_X =
-            new FloatProperty<TaskView>("fullscreenTranslationX") {
+    private static final FloatProperty<TaskView> NON_GRID_TRANSLATION_X =
+            new FloatProperty<TaskView>("nonGridTranslationX") {
                 @Override
                 public void setValue(TaskView taskView, float v) {
-                    taskView.setFullscreenTranslationX(v);
+                    taskView.setNonGridTranslationX(v);
                 }
 
                 @Override
                 public Float get(TaskView taskView) {
-                    return taskView.mFullscreenTranslationX;
+                    return taskView.mNonGridTranslationX;
                 }
             };
 
-    private static final FloatProperty<TaskView> FULLSCREEN_TRANSLATION_Y =
-            new FloatProperty<TaskView>("fullscreenTranslationY") {
+    private static final FloatProperty<TaskView> NON_GRID_TRANSLATION_Y =
+            new FloatProperty<TaskView>("nonGridTranslationY") {
                 @Override
                 public void setValue(TaskView taskView, float v) {
-                    taskView.setFullscreenTranslationY(v);
+                    taskView.setNonGridTranslationY(v);
                 }
 
                 @Override
                 public Float get(TaskView taskView) {
-                    return taskView.mFullscreenTranslationY;
+                    return taskView.mNonGridTranslationY;
                 }
             };
 
-    private static final FloatProperty<TaskView> NON_FULLSCREEN_TRANSLATION_X =
-            new FloatProperty<TaskView>("nonFullscreenTranslationX") {
+    public static final FloatProperty<TaskView> SNAPSHOT_SCALE =
+            new FloatProperty<TaskView>("snapshotScale") {
                 @Override
                 public void setValue(TaskView taskView, float v) {
-                    taskView.setNonFullscreenTranslationX(v);
+                    taskView.setSnapshotScale(v);
                 }
 
                 @Override
                 public Float get(TaskView taskView) {
-                    return taskView.mNonFullscreenTranslationX;
-                }
-            };
-
-    private static final FloatProperty<TaskView> NON_FULLSCREEN_TRANSLATION_Y =
-            new FloatProperty<TaskView>("nonFullscreenTranslationY") {
-                @Override
-                public void setValue(TaskView taskView, float v) {
-                    taskView.setNonFullscreenTranslationY(v);
-                }
-
-                @Override
-                public Float get(TaskView taskView) {
-                    return taskView.mNonFullscreenTranslationY;
+                    return taskView.mSnapshotView.getScaleX();
                 }
             };
 
@@ -344,7 +336,8 @@
     private final DigitalWellBeingToast mDigitalWellBeingToast;
     private float mFullscreenProgress;
     private float mGridProgress;
-    private float mFullscreenScale = 1;
+    private float mNonGridScale = 1;
+    private float mDismissScale = 1;
     private final FullscreenDrawParams mCurrentFullscreenParams;
     private final StatefulActivity mActivity;
 
@@ -356,16 +349,14 @@
     private float mTaskResistanceTranslationX;
     private float mTaskResistanceTranslationY;
     // The following translation variables should only be used in the same orientation as Launcher.
-    private float mFullscreenTranslationX;
-    private float mFullscreenTranslationY;
-    // Applied as a complement to fullscreenTranslation, for adjusting the carousel overview, or the
-    // in transition carousel before forming the grid on tablets.
-    private float mNonFullscreenTranslationX;
-    private float mNonFullscreenTranslationY;
     private float mBoxTranslationY;
     // The following grid translations scales with mGridProgress.
     private float mGridTranslationX;
     private float mGridTranslationY;
+    // Applied as a complement to gridTranslation, for adjusting the carousel overview and quick
+    // switch.
+    private float mNonGridTranslationX;
+    private float mNonGridTranslationY;
     // Used when in SplitScreenSelectState
     private float mSplitSelectTranslationY;
     private float mSplitSelectTranslationX;
@@ -524,8 +515,8 @@
         return mTask;
     }
 
-    public boolean hasTaskId(int taskId) {
-        return mTask != null && mTask.key != null && mTask.key.id == taskId;
+    public int getTaskId() {
+        return mTask != null && mTask.key != null ? mTask.key.id : -1;
     }
 
     public TaskThumbnailView getThumbnail() {
@@ -570,13 +561,7 @@
                     recentsView.getDepthController());
             anim.addListener(new AnimatorListenerAdapter() {
                 @Override
-                public void onAnimationStart(Animator animator) {
-                    recentsView.getLiveTileTaskViewSimulator().setDrawsBelowRecents(false);
-                }
-
-                @Override
                 public void onAnimationEnd(Animator animator) {
-                    recentsView.getLiveTileTaskViewSimulator().setDrawsBelowRecents(true);
                     mIsClickableAsLiveTile = true;
                 }
             });
@@ -747,7 +732,16 @@
     private void setIcon(Drawable icon) {
         if (icon != null) {
             mIconView.setDrawable(icon);
-            mIconView.setOnClickListener(v -> showTaskMenu());
+            mIconView.setOnClickListener(v -> {
+                if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask()) {
+                    RecentsView recentsView = getRecentsView();
+                    recentsView.switchToScreenshot(
+                            () -> recentsView.finishRecentsAnimation(true /* toRecents */,
+                                    this::showTaskMenu));
+                } else {
+                    showTaskMenu();
+                }
+            });
             mIconView.setOnLongClickListener(v -> {
                 requestDisallowInterceptTouchEvent(true);
                 return showTaskMenu();
@@ -862,6 +856,7 @@
                 mSplitSelectTranslationX = 0f;
         mDismissTranslationY = mTaskOffsetTranslationY = mTaskResistanceTranslationY =
                 mSplitSelectTranslationY = 0f;
+        setSnapshotScale(1f);
         applyTranslationX();
         applyTranslationY();
         setTranslationZ(0);
@@ -877,9 +872,8 @@
 
     @Override
     public void onRecycle() {
-        mFullscreenTranslationX = mFullscreenTranslationY = mNonFullscreenTranslationX =
-                mNonFullscreenTranslationY = mGridTranslationX = mGridTranslationY =
-                        mBoxTranslationY = 0f;
+        mNonGridTranslationX = mNonGridTranslationY =
+                mGridTranslationX = mGridTranslationY = mBoxTranslationY = 0f;
         resetViewTransforms();
         // Clear any references to the thumbnail (it will be re-read either from the cache or the
         // system on next bind)
@@ -962,13 +956,18 @@
         }
     }
 
-    private void setFullscreenScale(float fullscreenScale) {
-        mFullscreenScale = fullscreenScale;
+    private void setNonGridScale(float nonGridScale) {
+        mNonGridScale = nonGridScale;
         applyScale();
     }
 
-    public float getFullscreenScale() {
-        return mFullscreenScale;
+    public float getNonGridScale() {
+        return mNonGridScale;
+    }
+
+    private void setSnapshotScale(float dismissScale) {
+        mDismissScale = dismissScale;
+        applyScale();
     }
 
     /**
@@ -985,12 +984,23 @@
 
     private void applyScale() {
         float scale = 1;
-        float fullScreenProgress = FULLSCREEN_INTERPOLATOR.getInterpolation(mFullscreenProgress);
-        scale *= Utilities.mapRange(fullScreenProgress, 1f, mFullscreenScale);
+        scale *= getPersistentScale();
+        scale *= mDismissScale;
         setScaleX(scale);
         setScaleY(scale);
     }
 
+    /**
+     * Returns multiplication of scale that is persistent (e.g. fullscreen and grid), and does not
+     * change according to a temporary state.
+     */
+    public float getPersistentScale() {
+        float scale = 1;
+        float gridProgress = GRID_INTERPOLATOR.getInterpolation(mGridProgress);
+        scale *= Utilities.mapRange(gridProgress, mNonGridScale, 1f);
+        return scale;
+    }
+
     private void setSplitSelectTranslationX(float x) {
         mSplitSelectTranslationX = x;
         applyTranslationX();
@@ -1030,23 +1040,13 @@
         applyTranslationY();
     }
 
-    private void setFullscreenTranslationX(float fullscreenTranslationX) {
-        mFullscreenTranslationX = fullscreenTranslationX;
+    private void setNonGridTranslationX(float nonGridTranslationX) {
+        mNonGridTranslationX = nonGridTranslationX;
         applyTranslationX();
     }
 
-    private void setFullscreenTranslationY(float fullscreenTranslationY) {
-        mFullscreenTranslationY = fullscreenTranslationY;
-        applyTranslationY();
-    }
-
-    private void setNonFullscreenTranslationX(float nonFullscreenTranslationX) {
-        mNonFullscreenTranslationX = nonFullscreenTranslationX;
-        applyTranslationX();
-    }
-
-    private void setNonFullscreenTranslationY(float nonFullscreenTranslationY) {
-        mNonFullscreenTranslationY = nonFullscreenTranslationY;
+    private void setNonGridTranslationY(float nonGridTranslationY) {
+        mNonGridTranslationY = nonGridTranslationY;
         applyTranslationY();
     }
 
@@ -1070,13 +1070,10 @@
 
     public float getScrollAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
         float scrollAdjustment = 0;
-        if (fullscreenEnabled) {
-            scrollAdjustment += getPrimaryFullscreenTranslationProperty().get(this);
-        } else {
-            scrollAdjustment += getPrimaryNonFullscreenTranslationProperty().get(this);
-        }
         if (gridEnabled) {
             scrollAdjustment += mGridTranslationX;
+        } else {
+            scrollAdjustment += getPrimaryNonGridTranslationProperty().get(this);
         }
         return scrollAdjustment;
     }
@@ -1088,7 +1085,7 @@
     public float getSizeAdjustment(boolean fullscreenEnabled) {
         float sizeAdjustment = 1;
         if (fullscreenEnabled) {
-            sizeAdjustment *= mFullscreenScale;
+            sizeAdjustment *= mNonGridScale;
         }
         return sizeAdjustment;
     }
@@ -1113,9 +1110,7 @@
      * change according to a temporary state (e.g. task offset).
      */
     public float getPersistentTranslationX() {
-        return getFullscreenTrans(mFullscreenTranslationX)
-                + getNonFullscreenTrans(mNonFullscreenTranslationX)
-                + getGridTrans(mGridTranslationX);
+        return getNonGridTrans(mNonGridTranslationX) + getGridTrans(mGridTranslationX);
     }
 
     /**
@@ -1124,8 +1119,7 @@
      */
     public float getPersistentTranslationY() {
         return mBoxTranslationY
-                + getFullscreenTrans(mFullscreenTranslationY)
-                + getNonFullscreenTrans(mNonFullscreenTranslationY)
+                + getNonGridTrans(mNonGridTranslationY)
                 + getGridTrans(mGridTranslationY);
     }
 
@@ -1159,24 +1153,14 @@
                 TASK_RESISTANCE_TRANSLATION_X, TASK_RESISTANCE_TRANSLATION_Y);
     }
 
-    public FloatProperty<TaskView> getPrimaryFullscreenTranslationProperty() {
+    public FloatProperty<TaskView> getPrimaryNonGridTranslationProperty() {
         return getPagedOrientationHandler().getPrimaryValue(
-                FULLSCREEN_TRANSLATION_X, FULLSCREEN_TRANSLATION_Y);
+                NON_GRID_TRANSLATION_X, NON_GRID_TRANSLATION_Y);
     }
 
-    public FloatProperty<TaskView> getSecondaryFullscreenTranslationProperty() {
+    public FloatProperty<TaskView> getSecondaryNonGridTranslationProperty() {
         return getPagedOrientationHandler().getSecondaryValue(
-                FULLSCREEN_TRANSLATION_X, FULLSCREEN_TRANSLATION_Y);
-    }
-
-    public FloatProperty<TaskView> getPrimaryNonFullscreenTranslationProperty() {
-        return getPagedOrientationHandler().getPrimaryValue(
-                NON_FULLSCREEN_TRANSLATION_X, NON_FULLSCREEN_TRANSLATION_Y);
-    }
-
-    public FloatProperty<TaskView> getSecondaryNonFullscreenTranslationProperty() {
-        return getPagedOrientationHandler().getSecondaryValue(
-                NON_FULLSCREEN_TRANSLATION_X, NON_FULLSCREEN_TRANSLATION_Y);
+                NON_GRID_TRANSLATION_X, NON_GRID_TRANSLATION_Y);
     }
 
     @Override
@@ -1314,19 +1298,9 @@
         mIconView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE);
         getThumbnail().getTaskOverlay().setFullscreenProgress(progress);
 
-        applyTranslationX();
-        applyTranslationY();
-        applyScale();
-
         TaskThumbnailView thumbnail = getThumbnail();
         updateCurrentFullscreenParams(thumbnail.getPreviewPositionHelper());
 
-        if (!getRecentsView().isTaskIconScaledDown(this)) {
-            // Some of the items in here are dependent on the current fullscreen params, but don't
-            // update them if the icon is supposed to be scaled down.
-            setIconScaleAndDim(progress, true /* invert */);
-        }
-
         thumbnail.setFullscreenParams(mCurrentFullscreenParams);
         mOutlineProvider.updateParams(
                 mCurrentFullscreenParams,
@@ -1341,6 +1315,7 @@
         mCurrentFullscreenParams.setProgress(
                 mFullscreenProgress,
                 getRecentsView().getScaleX(),
+                getScaleX(),
                 getWidth(), mActivity.getDeviceProfile(),
                 previewPositionHelper);
     }
@@ -1351,7 +1326,7 @@
      */
     void updateTaskSize() {
         ViewGroup.LayoutParams params = getLayoutParams();
-        float fullscreenScale;
+        float nonGridScale;
         float boxTranslationY;
         int expectedWidth;
         int expectedHeight;
@@ -1364,56 +1339,36 @@
 
             int boxWidth;
             int boxHeight;
-            float thumbnailRatio;
             boolean isFocusedTask = isFocusedTask();
             if (isFocusedTask) {
                 // Task will be focused and should use focused task size. Use focusTaskRatio
                 // that is associated with the original orientation of the focused task.
                 boxWidth = taskWidth;
                 boxHeight = taskHeight;
-                thumbnailRatio = getRecentsView().getFocusedTaskRatio();
             } else {
                 // Otherwise task is in grid, and should use lastComputedGridTaskSize.
                 Rect lastComputedGridTaskSize = getRecentsView().getLastComputedGridTaskSize();
                 boxWidth = lastComputedGridTaskSize.width();
                 boxHeight = lastComputedGridTaskSize.height();
-                thumbnailRatio = mTask != null ? mTask.getVisibleThumbnailRatio(
-                        TaskView.CLIP_STATUS_AND_NAV_BARS) : 0f;
             }
-            int boxLength = Math.max(boxWidth, boxHeight);
 
             // Bound width/height to the box size.
-            if (thumbnailRatio == 0f) {
-                expectedWidth = boxWidth;
-                expectedHeight = boxHeight + thumbnailPadding;
-            } else if (thumbnailRatio > 1) {
-                expectedWidth = boxLength;
-                expectedHeight = (int) (boxLength / thumbnailRatio) + thumbnailPadding;
-            } else {
-                expectedWidth = (int) (boxLength * thumbnailRatio);
-                expectedHeight = boxLength + thumbnailPadding;
-            }
+            expectedWidth = boxWidth;
+            expectedHeight = boxHeight + thumbnailPadding;
 
             // Scale to to fit task Rect.
-            fullscreenScale = taskWidth / (float) boxWidth;
-
-            // In full screen, scale back TaskView to original size.
-            if (expectedWidth > boxWidth) {
-                fullscreenScale *= boxWidth / (float) expectedWidth;
-            } else if (expectedHeight - thumbnailPadding > boxHeight) {
-                fullscreenScale *= boxHeight / (float) (expectedHeight - thumbnailPadding);
-            }
+            nonGridScale = taskWidth / (float) boxWidth;
 
             // Align to top of task Rect.
             boxTranslationY = (expectedHeight - thumbnailPadding - taskHeight) / 2.0f;
         } else {
-            fullscreenScale = 1f;
+            nonGridScale = 1f;
             boxTranslationY = 0f;
             expectedWidth = ViewGroup.LayoutParams.MATCH_PARENT;
             expectedHeight = ViewGroup.LayoutParams.MATCH_PARENT;
         }
 
-        setFullscreenScale(fullscreenScale);
+        setNonGridScale(nonGridScale);
         setBoxTranslationY(boxTranslationY);
         if (params.width != expectedWidth || params.height != expectedHeight) {
             params.width = expectedWidth;
@@ -1422,20 +1377,15 @@
         }
     }
 
-    private float getFullscreenTrans(float endTranslation) {
-        float progress = FULLSCREEN_INTERPOLATOR.getInterpolation(mFullscreenProgress);
-        return Utilities.mapRange(progress, 0, endTranslation);
-    }
-
-    private float getNonFullscreenTrans(float endTranslation) {
-        return endTranslation - getFullscreenTrans(endTranslation);
-    }
-
     private float getGridTrans(float endTranslation) {
-        float progress = ACCEL_DEACCEL.getInterpolation(mGridProgress);
+        float progress = GRID_INTERPOLATOR.getInterpolation(mGridProgress);
         return Utilities.mapRange(progress, 0, endTranslation);
     }
 
+    private float getNonGridTrans(float endTranslation) {
+        return endTranslation - getGridTrans(endTranslation);
+    }
+
     public boolean isRunningTask() {
         if (getRecentsView() == null) {
             return false;
@@ -1487,7 +1437,6 @@
         private final float mCornerRadius;
         private final float mWindowCornerRadius;
 
-        public float mFullscreenProgress;
         public RectF mCurrentDrawnInsets = new RectF();
         public float mCurrentDrawnCornerRadius;
         /** The current scale we apply to the thumbnail to adjust for new left/right insets. */
@@ -1503,9 +1452,8 @@
         /**
          * Sets the progress in range [0, 1]
          */
-        public void setProgress(float fullscreenProgress, float parentScale, int previewWidth,
-                DeviceProfile dp, PreviewPositionHelper pph) {
-            mFullscreenProgress = fullscreenProgress;
+        public void setProgress(float fullscreenProgress, float parentScale, float taskViewScale,
+                int previewWidth, DeviceProfile dp, PreviewPositionHelper pph) {
             RectF insets = pph.getInsetsToDrawInFullscreen();
 
             float currentInsetsLeft = insets.left * fullscreenProgress;
@@ -1516,7 +1464,7 @@
 
             mCurrentDrawnCornerRadius =
                     Utilities.mapRange(fullscreenProgress, mCornerRadius, fullscreenCornerRadius)
-                            / parentScale;
+                            / parentScale / taskViewScale;
 
             // We scaled the thumbnail to fit the content (excluding insets) within task view width.
             // Now that we are drawing left/right insets again, we need to scale down to fit them.
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationBarRotationContextTest.java b/quickstep/tests/src/com/android/quickstep/NavigationBarRotationContextTest.java
new file mode 100644
index 0000000..af5819a
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/NavigationBarRotationContextTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.view.View;
+import android.view.WindowInsetsController;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.launcher3.taskbar.contextual.RotationButton;
+import com.android.launcher3.taskbar.contextual.RotationButtonController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+/** SysUI equivalent */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NavigationBarRotationContextTest {
+    private static final int DEFAULT_ROTATE = 0;
+    private static final int DEFAULT_DISPLAY = 0;
+
+
+    private RotationButtonController mRotationButtonController;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        Context mTargetContext = InstrumentationRegistry.getTargetContext();
+        final View view = new View(mTargetContext);
+        RotationButton rotationButton = mock(RotationButton.class);
+        mRotationButtonController = new RotationButtonController(mTargetContext, 0, 0);
+        mRotationButtonController.setRotationButton(rotationButton);
+        // Due to a mockito issue, only spy the object after setting the initial state
+        mRotationButtonController = spy(mRotationButtonController);
+        final AnimatedVectorDrawable kbd = mock(AnimatedVectorDrawable.class);
+        doReturn(view).when(rotationButton).getCurrentView();
+        doReturn(true).when(rotationButton).acceptRotationProposal();
+    }
+
+    @Test
+    public void testOnInvalidRotationProposal() {
+        mRotationButtonController.onRotationProposal(DEFAULT_ROTATE + 1,
+                false /* isValid */);
+        verify(mRotationButtonController, times(1))
+                .setRotateSuggestionButtonState(false /* visible */);
+    }
+
+    @Test
+    public void testOnSameRotationProposal() {
+        mRotationButtonController.onRotationProposal(DEFAULT_ROTATE,
+                true /* isValid */);
+        verify(mRotationButtonController, times(1))
+                .setRotateSuggestionButtonState(false /* visible */);
+    }
+
+    @Test
+    public void testOnRotationProposalShowButtonShowNav() {
+        // No navigation bar should not call to set visibility state
+        mRotationButtonController.onBehaviorChanged(DEFAULT_DISPLAY,
+                WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
+        mRotationButtonController.onTaskBarVisibilityChange(false /* showing */);
+        verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
+                false /* visible */);
+        verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
+                true /* visible */);
+
+        // No navigation bar with rotation change should not call to set visibility state
+        mRotationButtonController.onRotationProposal(DEFAULT_ROTATE + 1,
+                true /* isValid */);
+        verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
+                false /* visible */);
+        verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
+                true /* visible */);
+
+        // Since rotation has changed rotation should be pending, show mButton when showing nav bar
+        mRotationButtonController.onTaskBarVisibilityChange(true /* showing */);
+        verify(mRotationButtonController, times(1)).setRotateSuggestionButtonState(
+                true /* visible */);
+    }
+
+    @Test
+    public void testOnRotationProposalShowButton() {
+        // Navigation bar being visible should not call to set visibility state
+        mRotationButtonController.onTaskBarVisibilityChange(true /* showing */);
+        verify(mRotationButtonController, times(0))
+                .setRotateSuggestionButtonState(false /* visible */);
+        verify(mRotationButtonController, times(0))
+                .setRotateSuggestionButtonState(true /* visible */);
+
+        // Navigation bar is visible and rotation requested
+        mRotationButtonController.onRotationProposal(DEFAULT_ROTATE + 1,
+                true /* isValid */);
+        verify(mRotationButtonController, times(1))
+                .setRotateSuggestionButtonState(true /* visible */);
+    }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
index 6e19436..f44a812 100644
--- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
+++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
@@ -25,6 +25,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.launcher3.Launcher;
+import com.android.launcher3.ui.TaplTestsLauncher3;
 import com.android.launcher3.util.RaceConditionReproducer;
 import com.android.quickstep.NavigationModeSwitchRule.Mode;
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
@@ -45,6 +46,7 @@
     @Before
     public void setUp() throws Exception {
         super.setUp();
+        TaplTestsLauncher3.initialize(this);
         // b/143488140
         mLauncher.pressHome();
         // Start an activity where the gestures start.
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index a5038a1..9a6eda4 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -23,7 +23,6 @@
 import static org.junit.Assert.assertTrue;
 
 import android.content.Intent;
-import android.util.Log;
 
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -37,8 +36,8 @@
 import com.android.launcher3.tapl.Overview;
 import com.android.launcher3.tapl.OverviewActions;
 import com.android.launcher3.tapl.OverviewTask;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.ui.TaplTestsLauncher3;
+import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
 import com.android.quickstep.views.RecentsView;
 
@@ -94,6 +93,7 @@
 
     @Test
     @PortraitLandscape
+    @ScreenRecord //b/191344757
     public void testOverview() throws Exception {
         startTestAppsWithCheck();
         // mLauncher.pressHome() also tests an important case of pressing home while in background.
@@ -163,6 +163,7 @@
     @Test
     @NavigationModeSwitch
     @PortraitLandscape
+    @ScreenRecord //b/191344757
     public void testOverviewActions() throws Exception {
         // Experimenting for b/165029151:
         final Overview overview = mLauncher.pressHome().switchToOverview();
@@ -188,6 +189,7 @@
     @Test
     @NavigationModeSwitch
     @PortraitLandscape
+    @ScreenRecord //b/191344757
     public void testSwitchToOverview() throws Exception {
         assertNotNull("Workspace.switchToOverview() returned null",
                 mLauncher.pressHome().switchToOverview());
@@ -198,6 +200,7 @@
     @Test
     @NavigationModeSwitch
     @PortraitLandscape
+    @ScreenRecord //b/191344757
     public void testBackground() throws Exception {
         startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
         final Background background = getAndAssertBackground();
@@ -219,6 +222,7 @@
 
     @Test
     @PortraitLandscape
+    @ScreenRecord //b/191344757
     public void testAllAppsFromHome() throws Exception {
         // Test opening all apps
         assertNotNull("switchToAllApps() returned null",
diff --git a/res/color-v31/home_settings_switch_thumb_color.xml b/res/color-v31/home_settings_switch_thumb_color.xml
new file mode 100644
index 0000000..91d3d9b
--- /dev/null
+++ b/res/color-v31/home_settings_switch_thumb_color.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2021 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- Disabled status of thumb -->
+    <item android:state_enabled="false"
+        android:color="@color/home_settings_thumb_off_color" />
+    <!-- Toggle off status of thumb -->
+    <item android:state_checked="false"
+        android:color="@color/home_settings_thumb_off_color" />
+    <!-- Enabled or toggle on status of thumb -->
+    <item android:color="@color/home_settings_state_on_color" />
+</selector>
diff --git a/res/color-v31/home_settings_switch_track_color.xml b/res/color-v31/home_settings_switch_track_color.xml
new file mode 100644
index 0000000..50784f5
--- /dev/null
+++ b/res/color-v31/home_settings_switch_track_color.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2021 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- Disabled status of thumb -->
+    <item android:state_enabled="false"
+        android:color="@color/home_settings_track_off_color"
+        android:alpha="?android:attr/disabledAlpha" />
+    <!-- Toggle off status of thumb -->
+    <item android:state_checked="false"
+        android:color="@color/home_settings_track_off_color" />
+    <!-- Enabled or toggle on status of thumb -->
+    <item android:color="@color/home_settings_track_on_color" />
+</selector>
diff --git a/res/drawable-v31/home_settings_switch_thumb.xml b/res/drawable-v31/home_settings_switch_thumb.xml
new file mode 100644
index 0000000..260d5ea
--- /dev/null
+++ b/res/drawable-v31/home_settings_switch_thumb.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2021 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:top="4dp"
+        android:left="4dp"
+        android:right="4dp"
+        android:bottom="4dp">
+        <shape android:shape="oval" >
+            <size android:height="20dp" android:width="20dp" />
+            <solid android:color="@color/home_settings_switch_thumb_color" />
+        </shape>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/res/drawable-v31/home_settings_switch_track.xml b/res/drawable-v31/home_settings_switch_track.xml
new file mode 100644
index 0000000..502a300
--- /dev/null
+++ b/res/drawable-v31/home_settings_switch_track.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2021 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle"
+    android:width="52dp"
+    android:height="28dp">
+
+    <solid android:color="@color/home_settings_switch_track_color" />
+    <corners android:radius="35dp" />
+</shape>
\ No newline at end of file
diff --git a/res/layout-v31/settings_activity.xml b/res/layout-v31/settings_activity.xml
new file mode 100644
index 0000000..59e14f2
--- /dev/null
+++ b/res/layout-v31/settings_activity.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2021 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<androidx.coordinatorlayout.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/content_parent"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true">
+
+    <com.google.android.material.appbar.AppBarLayout
+        android:id="@+id/app_bar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="?android:attr/colorPrimary"
+        android:fitsSystemWindows="true"
+        android:outlineAmbientShadowColor="@android:color/transparent"
+        android:outlineSpotShadowColor="@android:color/transparent"
+        android:theme="@style/HomeSettings.CollapsingToolbar">
+
+        <com.google.android.material.appbar.CollapsingToolbarLayout
+            android:id="@+id/collapsing_toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="226dp"
+            android:clipToPadding="false"
+            app:collapsedTitleTextAppearance="@style/HomeSettings.CollapsedToolbarTitle"
+            app:contentScrim="@color/home_settings_header_collapsed"
+            app:expandedTitleMarginEnd="24dp"
+            app:expandedTitleMarginStart="24dp"
+            app:expandedTitleTextAppearance="@style/HomeSettings.ExpandedToolbarTitle"
+            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
+            app:maxLines="3"
+            app:scrimAnimationDuration="50"
+            app:scrimVisibleHeightTrigger="174dp"
+            app:statusBarScrim="@null"
+            app:titleCollapseMode="fade"
+            app:toolbarId="@id/action_bar">
+
+            <Toolbar
+                android:id="@+id/action_bar"
+                android:layout_width="match_parent"
+                android:layout_height="?attr/actionBarSize"
+                android:theme="?android:attr/actionBarTheme"
+                android:transitionName="shared_element_view"
+                app:layout_collapseMode="pin" />
+
+        </com.google.android.material.appbar.CollapsingToolbarLayout>
+    </com.google.android.material.appbar.AppBarLayout>
+
+    <FrameLayout
+        android:id="@+id/content_frame"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        app:layout_behavior="@string/appbar_scrolling_view_behavior" />
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/res/layout/add_item_confirmation_activity.xml b/res/layout/add_item_confirmation_activity.xml
index 0e06690..c57b75a 100644
--- a/res/layout/add_item_confirmation_activity.xml
+++ b/res/layout/add_item_confirmation_activity.xml
@@ -64,12 +64,19 @@
                 android:textColor="?android:attr/textColorSecondary"
                 android:alpha="0.7"/>
 
-            <include layout="@layout/widget_cell"
-                android:id="@+id/widget_cell"
+            <ScrollView
+                android:id="@+id/widget_preview_scroll_view"
                 android:layout_width="match_parent"
                 android:layout_height="0dp"
-                android:layout_weight="1"
-                android:layout_marginVertical="16dp" />
+                android:layout_marginVertical="16dp"
+                android:layout_weight="1">
+
+                <include
+                    android:id="@+id/widget_cell"
+                    layout="@layout/widget_cell"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content" />
+            </ScrollView>
 
             <LinearLayout
                 android:layout_width="match_parent"
@@ -80,9 +87,11 @@
                 <Button
                     style="@style/Button.FullRounded.Colored"
                     android:layout_width="wrap_content"
-                    android:layout_height="36dp"
+                    android:layout_height="wrap_content"
                     android:paddingHorizontal="16dp"
                     android:textSize="14sp"
+                    android:maxLines="2"
+                    android:ellipsize="end"
                     android:textColor="@color/button_text"
                     android:text="@android:string/cancel"
                     android:onClick="onCancelClick"/>
@@ -94,9 +103,11 @@
                 <Button
                     style="@style/Button.FullRounded.Colored"
                     android:layout_width="wrap_content"
-                    android:layout_height="36dp"
+                    android:layout_height="wrap_content"
                     android:paddingHorizontal="16dp"
                     android:textSize="14sp"
+                    android:maxLines="2"
+                    android:ellipsize="end"
                     android:textColor="@color/button_text"
                     android:text="@string/add_to_home_screen"
                     android:onClick="onPlaceAutomaticallyClick"/>
diff --git a/res/values-night-v31/colors.xml b/res/values-night-v31/colors.xml
new file mode 100644
index 0000000..2c1bc90
--- /dev/null
+++ b/res/values-night-v31/colors.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+/*
+* Copyright (C) 2021 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*      http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+-->
+
+<resources>
+    <color name="home_settings_header_accent">@android:color/system_accent1_100</color>
+    <color name="home_settings_header_collapsed">@android:color/system_neutral1_700</color>
+    <color name="home_settings_header_expanded">@android:color/system_neutral1_900</color>
+
+    <color name="home_settings_thumb_off_color">@android:color/system_neutral2_300</color>
+    <color name="home_settings_track_on_color">@android:color/system_accent2_700</color>
+    <color name="home_settings_track_off_color">@android:color/system_neutral1_700</color>
+</resources>
\ No newline at end of file
diff --git a/res/values-v31/colors.xml b/res/values-v31/colors.xml
index fb37e85..2407e93 100644
--- a/res/values-v31/colors.xml
+++ b/res/values-v31/colors.xml
@@ -42,6 +42,16 @@
 
     <color name="folder_dot_color">@android:color/system_accent2_50</color>
 
+    <color name="home_settings_header_accent">@android:color/system_accent1_600</color>
+    <color name="home_settings_header_collapsed">@android:color/system_neutral1_100</color>
+    <color name="home_settings_header_expanded">@android:color/system_neutral1_50</color>
+
+    <color name="home_settings_state_on_color">@android:color/system_accent1_100</color>
+    <color name="home_settings_state_off_color">@android:color/system_accent2_100</color>
+    <color name="home_settings_thumb_off_color">@android:color/system_neutral2_100</color>
+    <color name="home_settings_track_on_color">@android:color/system_accent1_600</color>
+    <color name="home_settings_track_off_color">@android:color/system_neutral2_600</color>
+
     <color name="workspace_accent_color_light">@android:color/system_accent2_700</color>
     <color name="workspace_accent_color_dark">@android:color/system_accent1_50</color>
 </resources>
diff --git a/res/values-v31/config.xml b/res/values-v31/config.xml
new file mode 100644
index 0000000..afb9e6d
--- /dev/null
+++ b/res/values-v31/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2021 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+  -->
+<resources>
+    <bool name="home_settings_icon_space_reserved">false</bool>
+    <bool name="home_settings_allow_divider">false</bool>
+</resources>
\ No newline at end of file
diff --git a/res/values-v31/styles.xml b/res/values-v31/styles.xml
new file mode 100644
index 0000000..0d2fce0
--- /dev/null
+++ b/res/values-v31/styles.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright (C) 2021 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*      http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+-->
+
+<resources>
+
+    <style name="HomeSettings.Theme" parent="@android:style/Theme.DeviceDefault.Settings">
+        <item name="android:listPreferredItemPaddingEnd">16dp</item>
+        <item name="android:listPreferredItemPaddingStart">24dp</item>
+        <item name="android:navigationBarColor">@android:color/transparent</item>
+        <item name="android:statusBarColor">@android:color/transparent</item>
+        <item name="android:switchStyle">@style/HomeSettings.SwitchStyle</item>
+        <item name="android:textAppearanceListItem">@style/HomeSettings.PreferenceTitle</item>
+        <item name="android:windowActionBar">false</item>
+        <item name="android:windowNoTitle">true</item>
+        <item name="preferenceTheme">@style/HomeSettings.PreferenceTheme</item>
+    </style>
+
+    <style name="HomeSettings.PreferenceTheme" parent="@style/PreferenceThemeOverlay">
+        <item name="preferenceCategoryStyle">@style/HomeSettings.CategoryStyle</item>
+        <item name="preferenceCategoryTitleTextAppearance">@style/HomeSettings.CategoryTitle</item>
+        <item name="preferenceFragmentCompatStyle">@style/HomeSettings.FragmentCompatStyle</item>
+        <item name="preferenceScreenStyle">@style/HomeSettings.PreferenceScreenStyle</item>
+        <item name="preferenceStyle">@style/HomeSettings.PreferenceStyle</item>
+        <item name="switchPreferenceStyle">@style/HomeSettings.SwitchPreferenceStyle</item>
+    </style>
+
+    <style name="HomeSettings.CategoryStyle" parent="@style/Preference.Category.Material">
+        <item name="allowDividerAbove">@bool/home_settings_allow_divider</item>
+        <item name="allowDividerBelow">@bool/home_settings_allow_divider</item>
+        <item name="iconSpaceReserved">@bool/home_settings_icon_space_reserved</item>
+    </style>
+
+    <style name="HomeSettings.PreferenceStyle" parent="@style/Preference.Material">
+        <item name="iconSpaceReserved">@bool/home_settings_icon_space_reserved</item>
+    </style>
+
+    <style name="HomeSettings.PreferenceScreenStyle"
+            parent="@style/Preference.PreferenceScreen.Material">
+        <item name="iconSpaceReserved">@bool/home_settings_icon_space_reserved</item>
+    </style>
+
+    <style name="HomeSettings.SwitchPreferenceStyle"
+            parent="@style/Preference.SwitchPreference.Material">
+        <item name="iconSpaceReserved">@bool/home_settings_icon_space_reserved</item>
+    </style>
+
+    <style name="HomeSettings.SwitchStyle"
+            parent="@android:style/Widget.Material.CompoundButton.Switch">
+        <item name="android:switchMinWidth">52dp</item>
+        <item name="android:thumb">@drawable/home_settings_switch_thumb</item>
+        <item name="android:track">@drawable/home_settings_switch_track</item>
+    </style>
+
+    <style name="HomeSettings.PreferenceTitle"
+            parent="@android:style/TextAppearance.Material.Subhead">
+        <item name="android:fontFamily">google-sans</item>
+        <item name="android:textSize">20sp</item>
+    </style>
+
+    <style name="HomeSettings.CategoryTitle" parent="@android:style/TextAppearance.Material.Body2">
+        <item name="android:fontFamily">google-sans-text-medium</item>
+    </style>
+
+    <style name="HomeSettings.CollapsingToolbar" parent="@style/Theme.MaterialComponents.DayNight">
+        <item name="colorAccent">@color/home_settings_header_accent</item>
+        <item name="colorPrimary">@color/home_settings_header_expanded</item>
+        <item name="elevationOverlayColor">?attr/colorPrimary</item>
+        <item name="elevationOverlayEnabled">true</item>
+    </style>
+
+    <style name="HomeSettings.CollapsedToolbarTitle"
+            parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
+        <item name="android:fontFamily">google-sans</item>
+    </style>
+
+    <style name="HomeSettings.ExpandedToolbarTitle" parent="HomeSettings.CollapsedToolbarTitle">
+        <item name="android:textSize">36sp</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index ee2ceb1..c9557ef 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -311,6 +311,10 @@
 
 <!-- Taskbar related (placeholders to compile in Launcher3 without Quickstep) -->
     <dimen name="taskbar_size">0dp</dimen>
+    <dimen name="qsb_widget_height">0dp</dimen>
+    <dimen name="taskbar_icon_size">44dp</dimen>
+    <!-- Note that this applies to both sides of all icons, so visible space is double this. -->
+    <dimen name="taskbar_icon_spacing">8dp</dimen>
 
     <!-- Size of the maximum radius for the enforced rounded rectangles. -->
     <dimen name="enforced_rounded_corner_max_radius">16dp</dimen>
diff --git a/res/values/id.xml b/res/values/id.xml
index 1709c59..0e2dff0 100644
--- a/res/values/id.xml
+++ b/res/values/id.xml
@@ -20,4 +20,13 @@
     <item type="id" name="view_type_widgets_list" />
     <item type="id" name="view_type_widgets_header" />
     <item type="id" name="view_type_widgets_search_header" />
+
+    <!--  Do not change, must be kept in sync with sysui navbar button IDs for tests!  -->
+    <item type="id" name="home" />
+    <item type="id" name="recent_apps" />
+    <item type="id" name="back" />
+    <item type="id" name="ime_switcher" />
+    <item type="id" name="accessibility_button" />
+    <item type="id" name="rotate_suggestion" />
+    <!--  /Do not change, must be kept in sync with sysui navbar button IDs for tests!  -->
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 24851f2..a926cb5 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -145,18 +145,18 @@
     <style name="AppTheme.Dark.DarkMainColor" parent="@style/LauncherTheme.Dark.DarkMainColor" />
     <style name="AppTheme.Dark.DarkText" parent="@style/LauncherTheme.Dark.DarkText" />
 
-    <style name="HomeSettingsTheme" parent="@android:style/Theme.DeviceDefault.Settings">
+    <style name="HomeSettings.Theme" parent="@android:style/Theme.DeviceDefault.Settings">
         <item name="android:navigationBarColor">?android:colorPrimaryDark</item>
         <item name="android:windowActionBar">false</item>
         <item name="android:windowNoTitle">true</item>
-        <item name="preferenceTheme">@style/HomeSettingsPreferenceTheme</item>
+        <item name="preferenceTheme">@style/HomeSettings.PreferenceTheme</item>
     </style>
 
-    <style name="HomeSettingsPreferenceTheme" parent="@style/PreferenceThemeOverlay.v14.Material">
-        <item name="preferenceFragmentCompatStyle">@style/HomeSettingsFragmentCompatStyle</item>
+    <style name="HomeSettings.PreferenceTheme" parent="@style/PreferenceThemeOverlay.v14.Material">
+        <item name="preferenceFragmentCompatStyle">@style/HomeSettings.FragmentCompatStyle</item>
     </style>
 
-    <style name="HomeSettingsFragmentCompatStyle" parent="@style/PreferenceFragment.Material">
+    <style name="HomeSettings.FragmentCompatStyle" parent="@style/PreferenceFragment.Material">
         <item name="android:layout">@layout/home_settings</item>
     </style>
 
diff --git a/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java b/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java
index a2abfd5..07351fe 100644
--- a/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java
@@ -26,16 +26,16 @@
 
 import android.os.Process;
 
-import com.android.launcher3.PagedView;
 import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.shadows.ShadowLooperExecutor;
 import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.LauncherLayoutBuilder;
 import com.android.launcher3.util.LauncherModelHelper;
 import com.android.launcher3.util.LooperExecutor;
-import com.android.launcher3.util.ViewOnDrawExecutor;
+import com.android.launcher3.util.RunnableList;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -92,7 +92,7 @@
         // Add a new callback
         cb1.reset();
         MyCallbacks cb2 = spy(MyCallbacks.class);
-        cb2.mPageToBindSync = 2;
+        cb2.mPageToBindSync = IntSet.wrap(2);
         mModelHelper.getModel().addCallbacksAndLoad(cb2);
 
         waitForLoaderAndTempMainThread();
@@ -106,14 +106,14 @@
         // No effect on callbacks when removing an callback
         mModelHelper.getModel().removeCallbacks(cb2);
         waitForLoaderAndTempMainThread();
-        assertNull(cb1.mDeferredExecutor);
-        assertNull(cb2.mDeferredExecutor);
+        assertNull(cb1.mPendingTasks);
+        assertNull(cb2.mPendingTasks);
 
         // Reloading only loads registered callbacks
         mModelHelper.getModel().startLoader();
         waitForLoaderAndTempMainThread();
         cb1.verifySynchronouslyBound(3);
-        assertNull(cb2.mDeferredExecutor);
+        assertNull(cb2.mPendingTasks);
     }
 
     @Test
@@ -178,21 +178,17 @@
     private abstract static class MyCallbacks implements Callbacks {
 
         final List<ItemInfo> mItems = new ArrayList<>();
-        int mPageToBindSync = 0;
-        int mPageBoundSync = PagedView.INVALID_PAGE;
-        ViewOnDrawExecutor mDeferredExecutor;
+        IntSet mPageToBindSync = IntSet.wrap(0);
+        IntSet mPageBoundSync = new IntSet();
+        RunnableList mPendingTasks;
         AppInfo[] mAppInfos;
 
         MyCallbacks() { }
 
         @Override
-        public void onPageBoundSynchronously(int page) {
-            mPageBoundSync = page;
-        }
-
-        @Override
-        public void executeOnNextDraw(ViewOnDrawExecutor executor) {
-            mDeferredExecutor = executor;
+        public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks) {
+            mPageBoundSync = boundPages;
+            mPendingTasks = pendingTasks;
         }
 
         @Override
@@ -206,26 +202,26 @@
         }
 
         @Override
-        public int getPageToBindSynchronously() {
+        public IntSet getPagesToBindSynchronously() {
             return mPageToBindSync;
         }
 
         public void reset() {
             mItems.clear();
-            mPageBoundSync = PagedView.INVALID_PAGE;
-            mDeferredExecutor = null;
+            mPageBoundSync = new IntSet();
+            mPendingTasks = null;
             mAppInfos = null;
         }
 
         public void verifySynchronouslyBound(int totalItems) {
             // Verify that the requested page is bound synchronously
-            assertEquals(mPageBoundSync, mPageToBindSync);
+            assertEquals(mPageToBindSync, mPageBoundSync);
             assertEquals(mItems.size(), 1);
-            assertEquals(mItems.get(0).screenId, mPageBoundSync);
-            assertNotNull(mDeferredExecutor);
+            assertEquals(IntSet.wrap(mItems.get(0).screenId), mPageBoundSync);
+            assertNotNull(mPendingTasks);
 
             // Verify that all other pages are bound properly
-            mDeferredExecutor.runAllTasks();
+            mPendingTasks.executeAllAndDestroy();
             assertEquals(mItems.size(), totalItems);
         }
 
diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherPageRestoreHelperTest.java b/robolectric_tests/src/com/android/launcher3/util/LauncherPageRestoreHelperTest.java
new file mode 100644
index 0000000..51f5851
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/util/LauncherPageRestoreHelperTest.java
@@ -0,0 +1,224 @@
+/**
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util;
+
+import android.os.Bundle;
+
+import com.android.launcher3.LauncherPageRestoreHelper;
+import com.android.launcher3.Workspace;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+
+@RunWith(RobolectricTestRunner.class)
+public class LauncherPageRestoreHelperTest {
+
+    // Type: int
+    private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
+    // Type: int
+    private static final String RUNTIME_STATE_CURRENT_SCREEN_COUNT =
+            "launcher.current_screen_count";
+
+    private LauncherPageRestoreHelper mPageRestoreHelper;
+    private Bundle mState;
+
+    @Mock
+    private Workspace mWorkspace;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mPageRestoreHelper = new LauncherPageRestoreHelper(mWorkspace);
+        mState = new Bundle();
+    }
+
+    @Test
+    public void givenNoChildrenInWorkspace_whenSavePages_thenNothingSaved() {
+        when(mWorkspace.getChildCount()).thenReturn(0);
+
+        mPageRestoreHelper.savePagesToRestore(mState);
+
+        assertFalse(mState.containsKey(RUNTIME_STATE_CURRENT_SCREEN_COUNT));
+        assertFalse(mState.containsKey(RUNTIME_STATE_CURRENT_SCREEN));
+    }
+
+    @Test
+    public void givenMultipleCurrentPages_whenSavePages_thenSavedCorrectly() {
+        when(mWorkspace.getChildCount()).thenReturn(5);
+        when(mWorkspace.getCurrentPage()).thenReturn(2);
+        givenPanelCount(2);
+
+        mPageRestoreHelper.savePagesToRestore(mState);
+
+        assertEquals(5, mState.getInt(RUNTIME_STATE_CURRENT_SCREEN_COUNT));
+        assertEquals(2, mState.getInt(RUNTIME_STATE_CURRENT_SCREEN));
+    }
+
+    @Test
+    public void givenNullSavedState_whenRestorePages_thenReturnEmptyIntSet() {
+        IntSet result = mPageRestoreHelper.getPagesToRestore(null);
+
+        assertTrue(result.isEmpty());
+    }
+
+    @Test
+    public void givenTotalPageCountMissing_whenRestorePages_thenReturnEmptyIntSet() {
+        givenSavedCurrentPage(1);
+        givenPanelCount(1);
+
+        IntSet result = mPageRestoreHelper.getPagesToRestore(mState);
+
+        assertTrue(result.isEmpty());
+    }
+
+    @Test
+    public void givenCurrentPageMissing_whenRestorePages_thenReturnEmptyIntSet() {
+        givenSavedPageCount(3);
+        givenPanelCount(2);
+
+        IntSet result = mPageRestoreHelper.getPagesToRestore(mState);
+
+        assertTrue(result.isEmpty());
+    }
+
+    @Test
+    public void givenOnePanel_whenRestorePages_thenReturnThatPage() {
+        givenSavedCurrentPage(2);
+        givenSavedPageCount(5);
+        givenPanelCount(1);
+
+        IntSet result = mPageRestoreHelper.getPagesToRestore(mState);
+
+        assertEquals(1, result.size());
+        assertEquals(2, result.getArray().get(0));
+    }
+
+    @Test
+    public void givenTwoPanelOnFirstPages_whenRestorePages_thenReturnThosePages() {
+        givenSavedCurrentPage(0, 1);
+        givenSavedPageCount(2);
+        givenPanelCount(2);
+
+        IntSet result = mPageRestoreHelper.getPagesToRestore(mState);
+
+        assertEquals(IntSet.wrap(0, 1), result);
+    }
+
+    @Test
+    public void givenTwoPanelOnMiddlePages_whenRestorePages_thenReturnThosePages() {
+        givenSavedCurrentPage(2, 3);
+        givenSavedPageCount(5);
+        givenPanelCount(2);
+
+        IntSet result = mPageRestoreHelper.getPagesToRestore(mState);
+
+        assertEquals(IntSet.wrap(2, 3), result);
+    }
+
+    @Test
+    public void givenTwoPanelOnLastPage_whenRestorePages_thenReturnOnlyLastPage() {
+        // The device has two panel home but the current page is the last page, so we don't have
+        // a right panel, only the left one.
+        givenSavedCurrentPage(2);
+        givenSavedPageCount(3);
+        givenPanelCount(2);
+
+        IntSet result = mPageRestoreHelper.getPagesToRestore(mState);
+
+        assertEquals(IntSet.wrap(2), result);
+    }
+
+    @Test
+    public void givenOnlyOnePageAndPhoneFolding_whenRestorePages_thenReturnOnlyOnePage() {
+        givenSavedCurrentPage(0);
+        givenSavedPageCount(1);
+        givenPanelCount(1);
+
+        IntSet result = mPageRestoreHelper.getPagesToRestore(mState);
+
+        assertEquals(IntSet.wrap(0), result);
+    }
+
+    @Test
+    public void givenPhoneFolding_whenRestorePages_thenReturnOnlyTheFirstCurrentPage() {
+        givenSavedCurrentPage(2, 3);
+        givenSavedPageCount(4);
+        givenPanelCount(1);
+
+        IntSet result = mPageRestoreHelper.getPagesToRestore(mState);
+
+        assertEquals(IntSet.wrap(2), result);
+    }
+
+    @Test
+    public void givenPhoneUnfolding_whenRestorePages_thenReturnCurrentPagePlusTheNextOne() {
+        givenSavedCurrentPage(2);
+        givenSavedPageCount(4);
+        givenPanelCount(2);
+
+        IntSet result = mPageRestoreHelper.getPagesToRestore(mState);
+
+        assertEquals(IntSet.wrap(2, 3), result);
+    }
+
+    @Test
+    public void givenPhoneUnfoldingOnLastPage_whenRestorePages_thenReturnOnlyLastPage() {
+        givenSavedCurrentPage(4);
+        givenSavedPageCount(5);
+        givenPanelCount(2);
+
+        IntSet result = mPageRestoreHelper.getPagesToRestore(mState);
+
+        assertEquals(IntSet.wrap(4), result);
+    }
+
+    @Test
+    public void givenOnlyOnePageAndPhoneUnfolding_whenRestorePages_thenReturnOnlyOnePage() {
+        givenSavedCurrentPage(0);
+        givenSavedPageCount(1);
+        givenPanelCount(2);
+
+        IntSet result = mPageRestoreHelper.getPagesToRestore(mState);
+
+        assertEquals(IntSet.wrap(0), result);
+    }
+
+    private void givenPanelCount(int panelCount) {
+        when(mWorkspace.getPanelCount()).thenReturn(panelCount);
+        when(mWorkspace.getLeftmostVisiblePageForIndex(anyInt())).thenAnswer(invocation -> {
+            int pageIndex = invocation.getArgument(0);
+            return pageIndex * panelCount / panelCount;
+        });
+    }
+
+    private void givenSavedPageCount(int pageCount) {
+        mState.putInt(RUNTIME_STATE_CURRENT_SCREEN_COUNT, pageCount);
+    }
+
+    private void givenSavedCurrentPage(int... pages) {
+        mState.putInt(RUNTIME_STATE_CURRENT_SCREEN, pages[0]);
+    }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherUIHelper.java b/robolectric_tests/src/com/android/launcher3/util/LauncherUIHelper.java
index fdddab4..caad40e 100644
--- a/robolectric_tests/src/com/android/launcher3/util/LauncherUIHelper.java
+++ b/robolectric_tests/src/com/android/launcher3/util/LauncherUIHelper.java
@@ -81,7 +81,7 @@
         doLayout(launcher);
         ViewOnDrawExecutor executor = ReflectionHelpers.getField(launcher, "mPendingExecutor");
         if (executor != null) {
-            executor.runAllTasks();
+            executor.markCompleted();
         }
         return launcher;
     }
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index ddbd425..f800cf6 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -407,7 +407,7 @@
         }
     }
 
-    void clearPressedBackground() {
+    public void clearPressedBackground() {
         setPressed(false);
         setStayPressed(false);
     }
@@ -853,8 +853,9 @@
         switch (display) {
             case DISPLAY_ALL_APPS:
                 return grid.allAppsIconSizePx;
-            case DISPLAY_WORKSPACE:
             case DISPLAY_FOLDER:
+                return grid.folderChildIconSizePx;
+            case DISPLAY_WORKSPACE:
             default:
                 return grid.iconSizePx;
         }
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index 61b5564..0d33b6f 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -211,11 +211,8 @@
         }
         final DragLayer dragLayer = mLauncher.getDragLayer();
         final DragView dragView = d.dragView;
-        final Rect from = new Rect();
-        dragLayer.getViewRectRelativeToSelf(d.dragView, from);
-
         final Rect to = getIconRect(d);
-        final float scale = (float) to.width() / from.width();
+        final float scale = (float) to.width() / dragView.getMeasuredWidth();
         dragView.disableColorExtraction();
         dragView.detachContentView(/* reattachToPreviousParent= */ true);
         mDropTargetBar.deferOnDragEnd();
@@ -229,9 +226,9 @@
             post(dragView::resumeColorExtraction);
         };
 
-        dragLayer.animateView(d.dragView, from, to, scale, 1f, 1f, 0.1f, 0.1f,
+        dragLayer.animateView(d.dragView, to, scale, 0.1f, 0.1f,
                 DRAG_VIEW_DROP_DURATION,
-                Interpolators.DEACCEL_2, Interpolators.LINEAR, onAnimationEndRunnable,
+                Interpolators.DEACCEL_2, onAnimationEndRunnable,
                 DragLayer.ANIMATION_END_DISAPPEAR, null);
     }
 
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 80ec192..ba55834 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -33,6 +33,7 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.util.IntSet;
 import com.android.launcher3.views.Snackbar;
 
 public class DeleteDropTarget extends ButtonDropTarget {
@@ -131,7 +132,7 @@
             onAccessibilityDrop(null, item);
             ModelWriter modelWriter = mLauncher.getModelWriter();
             Runnable onUndoClicked = () -> {
-                mLauncher.setPageToBindSynchronously(itemPage);
+                mLauncher.setPagesToBindSynchronously(IntSet.wrap(itemPage));
                 modelWriter.abortDelete();
                 mLauncher.getStatsLogManager().logger().log(LAUNCHER_UNDO);
             };
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 88e8171..624862d 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -44,6 +44,7 @@
 import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.icons.GraphicsUtils;
 import com.android.launcher3.icons.IconNormalizer;
+import com.android.launcher3.uioverrides.ApiWrapper;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.WindowBounds;
@@ -54,6 +55,8 @@
 public class DeviceProfile {
 
     private static final int DEFAULT_DOT_SIZE = 100;
+    // Ratio of empty space, qsb should take up to appear visually centered.
+    private static final float QSB_CENTER_FACTOR = .325f;
 
     public final InvariantDeviceProfile inv;
     private final Info mInfo;
@@ -158,6 +161,7 @@
     // Start is the side next to the nav bar, end is the side next to the workspace
     public final int hotseatBarSidePaddingStartPx;
     public final int hotseatBarSidePaddingEndPx;
+    public final int hotseatQsbHeight;
 
     public final float qsbBottomMarginOriginalPx;
     public int qsbBottomMarginPx;
@@ -245,6 +249,7 @@
         mMetrics = context.getResources().getDisplayMetrics();
         final Resources res = context.getResources();
 
+        hotseatQsbHeight = res.getDimensionPixelSize(R.dimen.qsb_widget_height);
         isTaskbarPresent = isTablet && FeatureFlags.ENABLE_TASKBAR.get();
         if (isTaskbarPresent) {
             // Taskbar will be added later, but provides bottom insets that we should subtract
@@ -755,7 +760,10 @@
         }
     }
 
-    public Rect getHotseatLayoutPadding() {
+    /**
+     * Returns the padding for hotseat view
+     */
+    public Rect getHotseatLayoutPadding(Context context) {
         if (isVerticalBarLayout()) {
             if (isSeascape()) {
                 mHotseatPadding.set(mInsets.left + hotseatBarSidePaddingStartPx,
@@ -764,6 +772,30 @@
                 mHotseatPadding.set(hotseatBarSidePaddingEndPx, mInsets.top,
                         mInsets.right + hotseatBarSidePaddingStartPx, mInsets.bottom);
             }
+        } else if (isTaskbarPresent) {
+            int hotseatHeight = workspacePadding.bottom + taskbarSize;
+            int taskbarOffset = getTaskbarOffsetY();
+            int hotseatTopDiff = hotseatHeight - taskbarSize - taskbarOffset;
+
+            int startOffset = ApiWrapper.getHotseatStartOffset(context);
+            int requiredWidth = iconSizePx * numShownHotseatIcons;
+
+            Resources res = context.getResources();
+            float taskbarIconSize = res.getDimension(R.dimen.taskbar_icon_size);
+            float taskbarIconSpacing = 2 * res.getDimension(R.dimen.taskbar_icon_spacing);
+            int maxSize = (int) (requiredWidth
+                    * (taskbarIconSize + taskbarIconSpacing) / taskbarIconSize);
+            int hotseatSize = Math.min(maxSize, availableWidthPx - startOffset);
+            int sideSpacing = (availableWidthPx - hotseatSize) / 2;
+            mHotseatPadding.set(sideSpacing, hotseatTopDiff, sideSpacing, taskbarOffset);
+
+            if (startOffset > sideSpacing) {
+                int diff = Utilities.isRtl(context.getResources())
+                        ? sideSpacing - startOffset
+                        : startOffset - sideSpacing;
+                mHotseatPadding.left += diff;
+                mHotseatPadding.right -= diff;
+            }
         } else {
             // We want the edges of the hotseat to line up with the edges of the workspace, but the
             // icons in the hotseat are a different size, and so don't line up perfectly. To account
@@ -785,6 +817,29 @@
     }
 
     /**
+     * Returns the number of pixels the QSB is translated from the bottom of the screen.
+     */
+    public int getQsbOffsetY() {
+        int freeSpace = isTaskbarPresent
+                ? workspacePadding.bottom
+                : hotseatBarSizePx - hotseatCellHeightPx - hotseatQsbHeight;
+
+        if (isScalableGrid && qsbBottomMarginPx <= freeSpace) {
+            return qsbBottomMarginPx;
+        } else {
+            return (int) (freeSpace * QSB_CENTER_FACTOR)
+                + (isTaskbarPresent ? taskbarSize : getInsets().bottom);
+        }
+    }
+
+    /**
+     * Returns the number of pixels the taskbar is translated from the bottom of the screen.
+     */
+    public int getTaskbarOffsetY() {
+        return (getQsbOffsetY() - taskbarSize) / 2;
+    }
+
+    /**
      * @return the bounds for which the open folders should be contained within
      */
     public Rect getAbsoluteOpenFolderBounds() {
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 42b4e89..b3ae15e 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -49,8 +49,6 @@
     private final View mQsb;
     private final int mQsbHeight;
 
-    private final int mTaskbarViewHeight;
-
     public Hotseat(Context context) {
         this(context, null);
     }
@@ -63,10 +61,9 @@
         super(context, attrs, defStyle);
 
         mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
-        mQsbHeight = mQsb.getLayoutParams().height;
         addView(mQsb);
 
-        mTaskbarViewHeight = context.getResources().getDimensionPixelSize(R.dimen.taskbar_size);
+        mQsbHeight = getResources().getDimensionPixelSize(R.dimen.qsb_widget_height);
     }
 
     /**
@@ -114,18 +111,13 @@
             lp.gravity = Gravity.BOTTOM;
             lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
             lp.height = (grid.isTaskbarPresent
-                        ? grid.workspacePadding.bottom
+                    ? grid.workspacePadding.bottom
                         : grid.hotseatBarSizePx)
                     + (grid.isTaskbarPresent ? grid.taskbarSize : insets.bottom);
         }
 
-        if (!grid.isTaskbarPresent) {
-            // When taskbar is present, we set the padding separately to ensure a seamless visual
-            // handoff between taskbar and hotseat during drag and drop.
-            Rect padding = grid.getHotseatLayoutPadding();
-            setPadding(padding.left, padding.top, padding.right, padding.bottom);
-        }
-
+        Rect padding = grid.getHotseatLayoutPadding(getContext());
+        setPadding(padding.left, padding.top, padding.right, padding.bottom);
         setLayoutParams(lp);
         InsettableFrameLayout.dispatchInsets(this, insets);
     }
@@ -193,37 +185,12 @@
         int left = (r - l - qsbWidth) / 2;
         int right = left + qsbWidth;
 
-        int bottom = b - t - getQsbOffsetY();
+        int bottom = b - t - mActivity.getDeviceProfile().getQsbOffsetY();
         int top = bottom - mQsbHeight;
         mQsb.layout(left, top, right, bottom);
     }
 
     /**
-     * Returns the number of pixels the QSB is translated from the bottom of the screen.
-     */
-    private int getQsbOffsetY() {
-        DeviceProfile dp = mActivity.getDeviceProfile();
-        int freeSpace = dp.isTaskbarPresent
-                ? dp.workspacePadding.bottom
-                : dp.hotseatBarSizePx - dp.hotseatCellHeightPx - mQsbHeight;
-
-        if (dp.isScalableGrid && dp.qsbBottomMarginPx <= freeSpace) {
-            return dp.qsbBottomMarginPx;
-        } else {
-            return (int) (freeSpace * QSB_CENTER_FACTOR) + (dp.isTaskbarPresent
-                    ? dp.taskbarSize
-                    : dp.getInsets().bottom);
-        }
-    }
-
-    /**
-     * Returns the number of pixels the taskbar is translated from the bottom of the screen.
-     */
-    public int getTaskbarOffsetY() {
-        return (getQsbOffsetY() - mTaskbarViewHeight) / 2;
-    }
-
-    /**
      * Sets the alpha value of just our ShortcutAndWidgetContainer.
      */
     public void setIconsAlpha(float alpha) {
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 29a0223..a2bd201 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -36,7 +36,6 @@
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
-import android.util.Log;
 import android.util.SparseArray;
 import android.util.TypedValue;
 import android.util.Xml;
@@ -45,7 +44,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.IntArray;
@@ -253,17 +251,10 @@
 
     private String initGrid(Context context, String gridName) {
         Info displayInfo = DisplayController.INSTANCE.get(context).getInfo();
-        // Determine if we have split display
-
-        boolean isTablet = false, isPhone = false;
-        for (WindowBounds bounds : displayInfo.supportedBounds) {
-            if (displayInfo.isTablet(bounds)) {
-                isTablet = true;
-            } else {
-                isPhone = true;
-            }
-        }
-        boolean isSplitDisplay = isPhone && isTablet && ENABLE_TWO_PANEL_HOME.get();
+        // Each screen has two profiles (portrait/landscape), so devices with four or more
+        // supported profiles implies two or more internal displays.
+        boolean isSplitDisplay =
+                displayInfo.supportedBounds.size() >= 4 && ENABLE_TWO_PANEL_HOME.get();
 
         ArrayList<DisplayOption> allOptions =
                 getPredefinedDeviceProfiles(context, gridName, isSplitDisplay);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 099f256..89b44a3 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -106,6 +106,7 @@
 import android.widget.Toast;
 
 import androidx.annotation.CallSuper;
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.StringRes;
 import androidx.annotation.VisibleForTesting;
@@ -117,6 +118,7 @@
 import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.allapps.DiscoveryBounce;
+import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
@@ -166,6 +168,7 @@
 import com.android.launcher3.util.ActivityTracker;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
@@ -173,6 +176,7 @@
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.PendingRequestArgs;
+import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.Themes;
@@ -247,8 +251,6 @@
     protected static final int REQUEST_LAST = 100;
 
     // Type: int
-    private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
-    // Type: int
     private static final String RUNTIME_STATE = "launcher.state";
     // Type: PendingRequestArgs
     private static final String RUNTIME_STATE_PENDING_REQUEST_ARGS = "launcher.request_args";
@@ -286,6 +288,8 @@
     private WidgetManagerHelper mAppWidgetManager;
     private LauncherAppWidgetHost mAppWidgetHost;
 
+    private LauncherPageRestoreHelper mPageRestoreHelper;
+
     private final int[] mTmpAddItemCellCoordinates = new int[2];
 
     @Thunk
@@ -321,8 +325,8 @@
 
     private PopupDataProvider mPopupDataProvider;
 
-    private int mSynchronouslyBoundPage = PagedView.INVALID_PAGE;
-    private int mPageToBindSynchronously = PagedView.INVALID_PAGE;
+    private IntSet mSynchronouslyBoundPages = new IntSet();
+    private IntSet mPagesToBindSynchronously = new IntSet();
 
     // We only want to get the SharedPreferences once since it does an FS stat each time we get
     // it from the context.
@@ -457,13 +461,10 @@
         restoreState(savedInstanceState);
         mStateManager.reapplyState();
 
-        // We only load the page synchronously if the user rotates (or triggers a
-        // configuration change) while launcher is in the foreground
-        int currentScreen = PagedView.INVALID_PAGE;
+        mPageRestoreHelper = new LauncherPageRestoreHelper(mWorkspace);
         if (savedInstanceState != null) {
-            currentScreen = savedInstanceState.getInt(RUNTIME_STATE_CURRENT_SCREEN, currentScreen);
+            mPagesToBindSynchronously = mPageRestoreHelper.getPagesToRestore(savedInstanceState);
         }
-        mPageToBindSynchronously = currentScreen;
 
         if (!mModel.addCallbacksAndLoad(this)) {
             if (!internalStateHandled) {
@@ -1188,6 +1189,7 @@
         // Until the workspace is bound, ensure that we keep the wallpaper offset locked to the
         // default state, otherwise we will update to the wrong offsets in RTL
         mWorkspace.lockWallpaperToDefaultPage();
+        mWorkspace.bindAndInitLeftPanel();
         mWorkspace.bindAndInitFirstWorkspaceScreen(null /* recycled qsb */);
         mDragController.addDragListener(mWorkspace);
 
@@ -1308,7 +1310,8 @@
         }
     }
 
-    public FolderIcon findFolderIcon(final int folderIconId) {
+    @Override
+    public @Nullable FolderIcon findFolderIcon(final int folderIconId) {
         return (FolderIcon) mWorkspace.getHomescreenIconByItemId(folderIconId);
     }
 
@@ -1583,18 +1586,17 @@
     @Override
     public void onRestoreInstanceState(Bundle state) {
         super.onRestoreInstanceState(state);
-        mWorkspace.restoreInstanceStateForChild(mSynchronouslyBoundPage);
+        if (mSynchronouslyBoundPages != null) {
+            mSynchronouslyBoundPages.forEach(page -> mWorkspace.restoreInstanceStateForChild(page));
+        }
     }
 
     @Override
     protected void onSaveInstanceState(Bundle outState) {
-        if (mWorkspace.getChildCount() > 0) {
-            outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getNextPage());
+        mPageRestoreHelper.savePagesToRestore(outState);
 
-        }
         outState.putInt(RUNTIME_STATE, mStateManager.getState().ordinal);
 
-
         AbstractFloatingView widgets = AbstractFloatingView
                 .getOpenView(this, AbstractFloatingView.TYPE_WIDGETS_FULL_SHEET);
         if (widgets != null) {
@@ -2073,24 +2075,24 @@
     }
 
     /**
-     * Sets the next page to bind synchronously on next bind.
-     * @param page
+     * Sets the next pages to bind synchronously on next bind.
+     * @param pages should not be null.
      */
-    public void setPageToBindSynchronously(int page) {
-        mPageToBindSynchronously = page;
+    public void setPagesToBindSynchronously(@NonNull IntSet pages) {
+        mPagesToBindSynchronously = pages;
     }
 
     /**
      * Implementation of the method from LauncherModel.Callbacks.
      */
     @Override
-    public int getPageToBindSynchronously() {
-        if (mPageToBindSynchronously != PagedView.INVALID_PAGE) {
-            return mPageToBindSynchronously;
-        } else  if (mWorkspace != null) {
-            return mWorkspace.getCurrentPage();
+    public IntSet getPagesToBindSynchronously() {
+        if (mPagesToBindSynchronously != null && !mPagesToBindSynchronously.isEmpty()) {
+            return mPagesToBindSynchronously;
+        } else if (mWorkspace != null) {
+            return mWorkspace.getVisiblePageIndices();
         } else {
-            return 0;
+            return new IntSet();
         }
     }
 
@@ -2101,7 +2103,7 @@
     @Override
     public void clearPendingBinds() {
         if (mPendingExecutor != null) {
-            mPendingExecutor.markCompleted();
+            mPendingExecutor.cancel();
             mPendingExecutor = null;
 
             // We might have set this flag previously and forgot to clear it.
@@ -2139,11 +2141,16 @@
 
     @Override
     public void bindScreens(IntArray orderedScreenIds) {
-        // Make sure the first screen is always at the start.
+        // Make sure the first screen is at the start if there's no widget panel,
+        // or on the second place if the first is the widget panel
+        boolean isLeftPanelShown =
+                mWorkspace.mWorkspaceScreens.containsKey(Workspace.LEFT_PANEL_ID);
+        int firstScreenPosition = isLeftPanelShown && orderedScreenIds.size() > 1 ? 1 : 0;
+
         if (FeatureFlags.QSB_ON_FIRST_SCREEN &&
-                orderedScreenIds.indexOf(Workspace.FIRST_SCREEN_ID) != 0) {
+                orderedScreenIds.indexOf(Workspace.FIRST_SCREEN_ID) != firstScreenPosition) {
             orderedScreenIds.removeValue(Workspace.FIRST_SCREEN_ID);
-            orderedScreenIds.add(0, Workspace.FIRST_SCREEN_ID);
+            orderedScreenIds.add(firstScreenPosition, Workspace.FIRST_SCREEN_ID);
         } else if (!FeatureFlags.QSB_ON_FIRST_SCREEN && orderedScreenIds.isEmpty()) {
             // If there are no screens, we need to have an empty screen
             mWorkspace.addExtraEmptyScreen();
@@ -2160,10 +2167,17 @@
         int count = orderedScreenIds.size();
         for (int i = 0; i < count; i++) {
             int screenId = orderedScreenIds.get(i);
-            if (!FeatureFlags.QSB_ON_FIRST_SCREEN || screenId != Workspace.FIRST_SCREEN_ID) {
+            if (FeatureFlags.QSB_ON_FIRST_SCREEN && screenId == Workspace.FIRST_SCREEN_ID) {
                 // No need to bind the first screen, as its always bound.
-                mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId);
+                continue;
             }
+
+            if (screenId == Workspace.LEFT_PANEL_ID) {
+                // No need to bind the left panel, as its always bound.
+                continue;
+            }
+
+            mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId);
         }
     }
 
@@ -2238,6 +2252,11 @@
                 continue;
             }
 
+            // Skip if the item is on the left widget panel but the panel is not shown
+            if (item.screenId == Workspace.LEFT_PANEL_ID && !getDeviceProfile().isTwoPanels) {
+                continue;
+            }
+
             final View view;
             switch (item.itemType) {
                 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
@@ -2528,25 +2547,6 @@
         return info;
     }
 
-    public void onPageBoundSynchronously(int page) {
-        mSynchronouslyBoundPage = page;
-        mWorkspace.setCurrentPage(page);
-        mPageToBindSynchronously = PagedView.INVALID_PAGE;
-    }
-
-    @Override
-    public void executeOnNextDraw(ViewOnDrawExecutor executor) {
-        clearPendingBinds();
-        mPendingExecutor = executor;
-        if (!isInState(ALL_APPS)) {
-            mAppsView.getAppsStore().enableDeferUpdates(AllAppsStore.DEFER_UPDATES_NEXT_DRAW);
-            mPendingExecutor.execute(() -> mAppsView.getAppsStore().disableDeferUpdates(
-                    AllAppsStore.DEFER_UPDATES_NEXT_DRAW));
-        }
-
-        executor.attachTo(this);
-    }
-
     public void clearPendingExecutor(ViewOnDrawExecutor executor) {
         if (mPendingExecutor == executor) {
             mPendingExecutor = null;
@@ -2554,22 +2554,31 @@
     }
 
     @Override
-    public void finishFirstPageBind(final ViewOnDrawExecutor executor) {
+    public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks) {
+        mSynchronouslyBoundPages = boundPages;
+        if (!boundPages.isEmpty()) {
+            mWorkspace.setCurrentPage(boundPages.getArray().get(0));
+        }
+        mPagesToBindSynchronously = new IntSet();
+
+        clearPendingBinds();
+        ViewOnDrawExecutor executor = new ViewOnDrawExecutor(pendingTasks);
+        mPendingExecutor = executor;
+        if (!isInState(ALL_APPS)) {
+            mAppsView.getAppsStore().enableDeferUpdates(AllAppsStore.DEFER_UPDATES_NEXT_DRAW);
+            pendingTasks.add(() -> mAppsView.getAppsStore().disableDeferUpdates(
+                    AllAppsStore.DEFER_UPDATES_NEXT_DRAW));
+        }
+
         AlphaProperty property = mDragLayer.getAlphaProperty(ALPHA_INDEX_LAUNCHER_LOAD);
         if (property.getValue() < 1) {
             ObjectAnimator anim = ObjectAnimator.ofFloat(property, MultiValueAlpha.VALUE, 1);
-            if (executor != null) {
-                anim.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        executor.onLoadAnimationCompleted();
-                    }
-                });
-            }
+            anim.addListener(AnimatorListeners.forEndCallback(executor::onLoadAnimationCompleted));
             anim.start();
-        } else if (executor != null) {
+        } else {
             executor.onLoadAnimationCompleted();
         }
+        executor.attachTo(this);
     }
 
     /**
@@ -2577,7 +2586,7 @@
      *
      * Implementation of the method from LauncherModel.Callbacks.
      */
-    public void finishBindingItems(int pageBoundFirst) {
+    public void finishBindingItems(IntSet pagesBoundFirst) {
         Object traceToken = TraceHelper.INSTANCE.beginSection("finishBindingItems");
         mWorkspace.restoreInstanceStateForRemainingPages();
 
@@ -2592,11 +2601,13 @@
         ItemInstallQueue.INSTANCE.get(this)
                 .resumeModelPush(FLAG_LOADER_RUNNING);
 
+        int currentPage = pagesBoundFirst != null && !pagesBoundFirst.isEmpty()
+                ? pagesBoundFirst.getArray().get(0) : PagedView.INVALID_PAGE;
         // When undoing the removal of the last item on a page, return to that page.
         // Since we are just resetting the current page without user interaction,
         // override the previous page so we don't log the page switch.
-        mWorkspace.setCurrentPage(pageBoundFirst, pageBoundFirst /* overridePrevPage */);
-        mPageToBindSynchronously = PagedView.INVALID_PAGE;
+        mWorkspace.setCurrentPage(currentPage, currentPage /* overridePrevPage */);
+        mPagesToBindSynchronously = new IntSet();
 
         // Cache one page worth of icons
         getViewCache().setCacheSize(R.layout.folder_application,
@@ -2880,13 +2891,6 @@
         return new float[] {NO_SCALE, NO_OFFSET};
     }
 
-    /**
-     * @see LauncherState#getTaskbarScale(Launcher)
-     */
-    public float getNormalTaskbarScale() {
-        return 1f;
-    }
-
     public static Launcher getLauncher(Context context) {
         return fromContext(context);
     }
diff --git a/src/com/android/launcher3/LauncherPageRestoreHelper.java b/src/com/android/launcher3/LauncherPageRestoreHelper.java
new file mode 100644
index 0000000..e679a12
--- /dev/null
+++ b/src/com/android/launcher3/LauncherPageRestoreHelper.java
@@ -0,0 +1,92 @@
+/**
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3;
+
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.launcher3.util.IntSet;
+
+import static androidx.annotation.VisibleForTesting.PACKAGE_PRIVATE;
+
+/**
+ * There's a logic which prioritizes the binding for the current page and defers the other pages'
+ * binding. If two panel home is enabled, we want to bind both pages together.
+ * LauncherPageRestoreHelper's purpose is to contain the logic for persisting, restoring and
+ * calculating which pages to load immediately.
+ */
+public class LauncherPageRestoreHelper {
+
+    public static final String TAG = "LauncherPageRestoreHelper";
+
+    // Type: int
+    private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
+    // Type: int
+    private static final String RUNTIME_STATE_CURRENT_SCREEN_COUNT =
+            "launcher.current_screen_count";
+
+    private Workspace mWorkspace;
+
+    public LauncherPageRestoreHelper(Workspace workspace) {
+        this.mWorkspace = workspace;
+    }
+
+    /**
+     * Some configuration changes trigger Launcher to recreate itself, and we want to give more
+     * priority to the currently active pages in the restoration process.
+     */
+    @VisibleForTesting(otherwise = PACKAGE_PRIVATE)
+    public IntSet getPagesToRestore(Bundle savedInstanceState) {
+        IntSet pagesToRestore = new IntSet();
+
+        if (savedInstanceState == null) {
+            return pagesToRestore;
+        }
+
+        int currentPage = savedInstanceState.getInt(RUNTIME_STATE_CURRENT_SCREEN, -1);
+        int totalPageCount = savedInstanceState.getInt(RUNTIME_STATE_CURRENT_SCREEN_COUNT, -1);
+        int panelCount = mWorkspace.getPanelCount();
+
+        if (totalPageCount <= 0 || currentPage < 0) {
+            Log.e(TAG, "getPagesToRestore: Invalid input: " + totalPageCount + ", " + currentPage);
+            return pagesToRestore;
+        }
+
+        int newCurrentPage = mWorkspace.getLeftmostVisiblePageForIndex(currentPage);
+        for (int page = newCurrentPage; page < newCurrentPage + panelCount
+                && page < totalPageCount; page++) {
+            pagesToRestore.add(page);
+        }
+
+        return pagesToRestore;
+    }
+
+    /**
+     * This should be called from Launcher's onSaveInstanceState method to persist everything that
+     * is necessary to calculate later which pages need to be initialized first after a
+     * configuration change.
+     */
+    @VisibleForTesting(otherwise = PACKAGE_PRIVATE)
+    public void savePagesToRestore(Bundle outState) {
+        int pageCount = mWorkspace.getChildCount();
+        if (pageCount > 0) {
+            outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getCurrentPage());
+            outState.putInt(RUNTIME_STATE_CURRENT_SCREEN_COUNT, pageCount);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 3399ce9..7985ab5 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -181,14 +181,6 @@
         return launcher.getNormalOverviewScaleAndOffset();
     }
 
-    public float getTaskbarScale(Launcher launcher) {
-        return launcher.getNormalTaskbarScale();
-    }
-
-    public float getTaskbarTranslationY(Launcher launcher) {
-        return -launcher.getHotseat().getTaskbarOffsetY();
-    }
-
     public float getOverviewFullscreenProgress() {
         return 0;
     }
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index b423871..1555e98 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3;
 
+import static androidx.annotation.VisibleForTesting.PACKAGE_PRIVATE;
+
 import static com.android.launcher3.anim.Interpolators.SCROLL;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType;
@@ -48,6 +50,7 @@
 import android.widget.ScrollView;
 
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
@@ -55,6 +58,7 @@
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.touch.PagedOrientationHandler.ChildBounds;
 import com.android.launcher3.util.EdgeEffectCompat;
+import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.views.ActivityContext;
 
@@ -100,6 +104,10 @@
 
     @ViewDebug.ExportedProperty(category = "launcher")
     protected int mCurrentPage;
+    // Difference between current scroll position and mCurrentPage's page scroll. Used to maintain
+    // relative scroll position unchanged in updateCurrentPageScroll. Cleared when snapping to a
+    // page.
+    protected int mCurrentPageScrollDiff;
 
     @ViewDebug.ExportedProperty(category = "launcher")
     protected int mNextPage = INVALID_PAGE;
@@ -243,7 +251,7 @@
         // If the current page is invalid, just reset the scroll position to zero
         int newPosition = 0;
         if (0 <= mCurrentPage && mCurrentPage < getPageCount()) {
-            newPosition = getScrollForPage(mCurrentPage);
+            newPosition = getScrollForPage(mCurrentPage) + mCurrentPageScrollDiff;
         }
         mOrientationHandler.set(this, VIEW_SCROLL_TO, newPosition);
         mScroller.startScroll(mScroller.getCurrX(), 0, newPosition - mScroller.getCurrX(), 0);
@@ -291,9 +299,15 @@
         return newPage;
     }
 
-    private int getLeftmostVisiblePageForIndex(int pageIndex) {
+    /**
+     * In most cases where panelCount is 1, this method will just return the page index that was
+     * passed in.
+     * But for example when two panel home is enabled we might need the leftmost visible page index
+     * because that page is the current page.
+     */
+    public int getLeftmostVisiblePageForIndex(int pageIndex) {
         int panelCount = getPanelCount();
-        return (pageIndex / panelCount) * panelCount;
+        return pageIndex - pageIndex % panelCount;
     }
 
     /**
@@ -304,16 +318,34 @@
     }
 
     /**
+     * Returns an IntSet with the indices of the currently visible pages
+     */
+    @VisibleForTesting(otherwise = PACKAGE_PRIVATE)
+    public IntSet getVisiblePageIndices() {
+        IntSet visiblePageIndices = new IntSet();
+        int panelCount = getPanelCount();
+        int pageCount = getPageCount();
+
+        // If a device goes from one panel to two panel (i.e. unfolding a foldable device) while
+        // an odd indexed page is the current page, then the new leftmost visible page will be
+        // different from the old mCurrentPage.
+        int currentPage = getLeftmostVisiblePageForIndex(mCurrentPage);
+        for (int page = currentPage; page < currentPage + panelCount && page < pageCount; page++) {
+            visiblePageIndices.add(page);
+        }
+        return visiblePageIndices;
+    }
+
+    /**
      * Executes the callback against each visible page
      */
     public void forEachVisiblePage(Consumer<View> callback) {
-        int panelCount = getPanelCount();
-        for (int i = mCurrentPage; i < mCurrentPage + panelCount; i++) {
-            View page = getPageAt(i);
+        getVisiblePageIndices().forEach(pageIndex -> {
+            View page = getPageAt(pageIndex);
             if (page != null) {
                 callback.accept(page);
             }
-        }
+        });
     }
 
     /**
@@ -424,6 +456,7 @@
      * to provide custom behavior during animation.
      */
     protected void onPageEndTransition() {
+        mCurrentPageScrollDiff = 0;
         AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext());
         AccessibilityManagerCompat.sendCustomAccessibilityEvent(getPageAt(mCurrentPage),
                 AccessibilityEvent.TYPE_VIEW_FOCUSED, null);
@@ -1120,6 +1153,10 @@
         mAllowOverScroll = enable;
     }
 
+    protected float getSignificantMoveThreshold() {
+        return SIGNIFICANT_MOVE_THRESHOLD;
+    }
+
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
         // Skip touch handling if there are no pages to swipe
@@ -1191,6 +1228,7 @@
                     }
                     delta -= consumed;
                 }
+                delta /= mOrientationHandler.getPrimaryScale(this);
 
                 // Only scroll and update mLastMotionX if we have moved some discrete amount.  We
                 // keep the remainder because we are actually testing if we've moved from the last
@@ -1243,11 +1281,12 @@
 
                 int velocity = (int) mOrientationHandler.getPrimaryVelocity(velocityTracker,
                     mActivePointerId);
-                int delta = (int) (primaryDirection - mDownMotionPrimary);
+                float delta = primaryDirection - mDownMotionPrimary;
+                delta /= mOrientationHandler.getPrimaryScale(this);
                 int pageOrientedSize = mOrientationHandler.getMeasuredSize(getPageAt(mCurrentPage));
 
-                boolean isSignificantMove = Math.abs(delta) > pageOrientedSize *
-                    SIGNIFICANT_MOVE_THRESHOLD;
+                boolean isSignificantMove = Math.abs(delta)
+                        > pageOrientedSize * getSignificantMoveThreshold();
 
                 mTotalMotion += Math.abs(mLastMotion + mLastMotionRemainder - primaryDirection);
                 boolean passedSlop = mAllowEasyFling || mTotalMotion > mPageSlop;
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index 2a5187d..dff8534 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -31,7 +31,7 @@
 import com.android.launcher3.CellLayout.ContainerType;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.views.ActivityContext;
-import com.android.launcher3.widget.LauncherAppWidgetHostView;
+import com.android.launcher3.widget.NavigableAppWidgetHostView;
 
 public class ShortcutAndWidgetContainer extends ViewGroup implements FolderIcon.FolderIconParent {
     static final String TAG = "ShortcutAndWidgetContainer";
@@ -104,9 +104,9 @@
 
     public void setupLp(View child) {
         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
-        if (child instanceof LauncherAppWidgetHostView) {
+        if (child instanceof NavigableAppWidgetHostView) {
             DeviceProfile profile = mActivity.getDeviceProfile();
-            ((LauncherAppWidgetHostView) child).getWidgetInset(profile, mTempRect);
+            ((NavigableAppWidgetHostView) child).getWidgetInset(profile, mTempRect);
             lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
                     profile.appWidgetScale.x, profile.appWidgetScale.y, mBorderSpacing, mTempRect);
         } else {
@@ -129,8 +129,8 @@
         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
         final DeviceProfile dp = mActivity.getDeviceProfile();
 
-        if (child instanceof LauncherAppWidgetHostView) {
-            ((LauncherAppWidgetHostView) child).getWidgetInset(dp, mTempRect);
+        if (child instanceof NavigableAppWidgetHostView) {
+            ((NavigableAppWidgetHostView) child).getWidgetInset(dp, mTempRect);
             lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
                     dp.appWidgetScale.x, dp.appWidgetScale.y, mBorderSpacing, mTempRect);
         } else {
@@ -178,16 +178,16 @@
      */
     public void layoutChild(View child) {
         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
-        if (child instanceof LauncherAppWidgetHostView) {
-            LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) child;
+        if (child instanceof NavigableAppWidgetHostView) {
+            NavigableAppWidgetHostView nahv = (NavigableAppWidgetHostView) child;
 
             // Scale and center the widget to fit within its cells.
             DeviceProfile profile = mActivity.getDeviceProfile();
             float scaleX = profile.appWidgetScale.x;
             float scaleY = profile.appWidgetScale.y;
 
-            lahv.setScaleToFit(Math.min(scaleX, scaleY));
-            lahv.setTranslationForCentering(-(lp.width - (lp.width * scaleX)) / 2.0f,
+            nahv.setScaleToFit(Math.min(scaleX, scaleY));
+            nahv.setTranslationForCentering(-(lp.width - (lp.width * scaleX)) / 2.0f,
                     -(lp.height - (lp.height * scaleY)) / 2.0f);
         }
 
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 75f6278..3cabc87 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -85,6 +85,7 @@
 import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 
@@ -659,25 +660,26 @@
      * @param outObj this is set to the internal data associated with {@param info},
      *               eg {@link LauncherActivityInfo} or {@link ShortcutInfo}.
      */
-    public static Drawable getFullDrawable(Launcher launcher, ItemInfo info, int width, int height,
+    public static Drawable getFullDrawable(Context context, ItemInfo info, int width, int height,
             Object[] outObj) {
-        Drawable icon = loadFullDrawableWithoutTheme(launcher, info, width, height, outObj);
+        Drawable icon = loadFullDrawableWithoutTheme(context, info, width, height, outObj);
         if (icon instanceof BitmapInfo.Extender) {
-            icon = ((BitmapInfo.Extender) icon).getThemedDrawable(launcher);
+            icon = ((BitmapInfo.Extender) icon).getThemedDrawable(context);
         }
         return icon;
     }
 
-    private static Drawable loadFullDrawableWithoutTheme(Launcher launcher, ItemInfo info,
+    private static Drawable loadFullDrawableWithoutTheme(Context context, ItemInfo info,
             int width, int height, Object[] outObj) {
-        LauncherAppState appState = LauncherAppState.getInstance(launcher);
+        ActivityContext activity = ActivityContext.lookupContext(context);
+        LauncherAppState appState = LauncherAppState.getInstance(context);
         if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
-            LauncherActivityInfo activityInfo = launcher.getSystemService(LauncherApps.class)
+            LauncherActivityInfo activityInfo = context.getSystemService(LauncherApps.class)
                     .resolveActivity(info.getIntent(), info.user);
             outObj[0] = activityInfo;
-            return activityInfo == null ? null : LauncherAppState.getInstance(launcher)
+            return activityInfo == null ? null : LauncherAppState.getInstance(context)
                     .getIconProvider().getIcon(
-                            activityInfo, launcher.getDeviceProfile().inv.fillResIconDpi);
+                            activityInfo, activity.getDeviceProfile().inv.fillResIconDpi);
         } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
             if (info instanceof PendingAddShortcutInfo) {
                 ShortcutConfigActivityInfo activityInfo =
@@ -686,18 +688,18 @@
                 return activityInfo.getFullResIcon(appState.getIconCache());
             }
             List<ShortcutInfo> si = ShortcutKey.fromItemInfo(info)
-                    .buildRequest(launcher)
+                    .buildRequest(context)
                     .query(ShortcutRequest.ALL);
             if (si.isEmpty()) {
                 return null;
             } else {
                 outObj[0] = si.get(0);
-                return ShortcutCachingLogic.getIcon(launcher, si.get(0),
+                return ShortcutCachingLogic.getIcon(context, si.get(0),
                         appState.getInvariantDeviceProfile().fillResIconDpi);
             }
         } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
             FolderAdaptiveIcon icon = FolderAdaptiveIcon.createFolderAdaptiveIcon(
-                    launcher, info.id, new Point(width, height));
+                    activity, info.id, new Point(width, height));
             if (icon == null) {
                 return null;
             }
@@ -715,8 +717,8 @@
      * badge. When dragged from workspace or folder, it may contain app AND/OR work profile badge
      **/
     @TargetApi(Build.VERSION_CODES.O)
-    public static Drawable getBadge(Launcher launcher, ItemInfo info, Object obj) {
-        LauncherAppState appState = LauncherAppState.getInstance(launcher);
+    public static Drawable getBadge(Context context, ItemInfo info, Object obj) {
+        LauncherAppState appState = LauncherAppState.getInstance(context);
         int iconSize = appState.getInvariantDeviceProfile().iconBitmapSize;
         if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
             boolean iconBadged = (info instanceof ItemInfoWithIcon)
@@ -736,7 +738,7 @@
         } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
             return ((FolderAdaptiveIcon) obj).getBadge();
         } else {
-            return launcher.getPackageManager()
+            return context.getPackageManager()
                     .getUserBadgedIcon(new FixedSizeEmptyDrawable(iconSize), info.user);
         }
     }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 5bdc402..5689394 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3;
 
+import static androidx.annotation.VisibleForTesting.PROTECTED;
+
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
 import static com.android.launcher3.LauncherState.ALL_APPS;
@@ -63,6 +65,8 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.Toast;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
 import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
 import com.android.launcher3.anim.Interpolators;
@@ -312,18 +316,34 @@
         // Increase our bottom insets so we don't overlap with the taskbar.
         mInsets.bottom += grid.nonOverlappingTaskbarInset;
 
-        if (isTwoPanelEnabled()) {
+        if (grid.isTwoPanels) {
             setPageSpacing(0); // we have two pages and we don't want any spacing
-        } else if (mWorkspaceFadeInAdjacentScreens) {
-            // In landscape mode the page spacing is set to the default.
-            setPageSpacing(grid.edgeMarginPx);
+
+            // Add left widget panel if it isn't already there
+            if (!mWorkspaceScreens.containsKey(LEFT_PANEL_ID)) {
+                int newCurrentPage = mCurrentPage + 1;
+                bindAndInitLeftPanel();
+                setCurrentPage(newCurrentPage);
+            }
         } else {
-            // In portrait, we want the pages spaced such that there is no
-            // overhang of the previous / next page into the current page viewport.
-            // We assume symmetrical padding in portrait mode.
-            int maxInsets = Math.max(insets.left, insets.right);
-            int maxPadding = Math.max(grid.edgeMarginPx, padding.left + 1);
-            setPageSpacing(Math.max(maxInsets, maxPadding));
+            if (mWorkspaceFadeInAdjacentScreens) {
+                // In landscape mode the page spacing is set to the default.
+                setPageSpacing(grid.edgeMarginPx);
+            } else {
+                // In portrait, we want the pages spaced such that there is no
+                // overhang of the previous / next page into the current page viewport.
+                // We assume symmetrical padding in portrait mode.
+                int maxInsets = Math.max(insets.left, insets.right);
+                int maxPadding = Math.max(grid.edgeMarginPx, padding.left + 1);
+                setPageSpacing(Math.max(maxInsets, maxPadding));
+            }
+
+            // Remove left widget panel if it is present
+            if (mWorkspaceScreens.containsKey(LEFT_PANEL_ID)) {
+                int newCurrentPage = mCurrentPage - 1;
+                removeLeftPanel();
+                setCurrentPage(newCurrentPage);
+            }
         }
 
         int paddingLeftRight = grid.cellLayoutPaddingLeftRightPx;
@@ -462,7 +482,8 @@
     }
 
     @Override
-    protected int getPanelCount() {
+    @VisibleForTesting(otherwise = PROTECTED)
+    public int getPanelCount() {
         return isTwoPanelEnabled() ? 2 : super.getPanelCount();
     }
 
@@ -553,7 +574,7 @@
             return;
         }
         // Add the first page
-        CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, 0);
+        CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, getChildCount());
         // Always add a QSB on the first screen.
         if (qsb == null) {
             // In transposed layout, we add the QSB in the Grid. As workspace does not touch the
@@ -572,6 +593,19 @@
         }
     }
 
+    /**
+     * Initializes and binds the left panel
+     */
+    public void bindAndInitLeftPanel() {
+        if (!FeatureFlags.QSB_ON_FIRST_SCREEN || !isTwoPanelEnabled()
+                || mWorkspaceScreens.containsKey(Workspace.LEFT_PANEL_ID)) {
+            return;
+        }
+
+        insertNewWorkspaceScreen(Workspace.LEFT_PANEL_ID, getChildCount());
+        mLauncher.getModelWriter().setLeftPanelShown(true);
+    }
+
     public void removeAllWorkspaceScreens() {
         // Disable all layout transitions before removing all pages to ensure that we don't get the
         // transition animations competing with us changing the scroll when we add pages
@@ -593,6 +627,7 @@
         mLauncher.mHandler.removeCallbacksAndMessages(DeferredWidgetRefresh.class);
 
         // Ensure that the first page is always present
+        bindAndInitLeftPanel();
         bindAndInitFirstWorkspaceScreen(qsb);
 
         // Re-enable the layout transitions
@@ -613,6 +648,18 @@
         insertNewWorkspaceScreen(screenId, getChildCount());
     }
 
+    private void removeLeftPanel() {
+        if (!mWorkspaceScreens.containsKey(LEFT_PANEL_ID)) {
+            return;
+        }
+        mLauncher.getModelWriter().setLeftPanelShown(false);
+        CellLayout leftPanel = mWorkspaceScreens.get(LEFT_PANEL_ID);
+        mWorkspaceScreens.remove(LEFT_PANEL_ID);
+        removeView(leftPanel);
+        mScreenOrder.removeValue(LEFT_PANEL_ID);
+        updatePageScrollValues();
+    }
+
     public CellLayout insertNewWorkspaceScreen(int screenId, int insertIndex) {
         if (mWorkspaceScreens.containsKey(screenId)) {
             throw new RuntimeException("Screen id " + screenId + " already exists!");
@@ -2690,9 +2737,6 @@
     public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, final DragView dragView,
             final Runnable onCompleteRunnable, int animationType, final View finalView,
             boolean external) {
-        Rect from = new Rect();
-        mLauncher.getDragLayer().getViewRectRelativeToSelf(dragView, from);
-
         int[] finalPos = new int[2];
         float scaleXY[] = new float[2];
         boolean scalePreview = !(info instanceof PendingAddShortcutInfo);
@@ -2736,8 +2780,8 @@
                     }
                 }
             };
-            dragLayer.animateViewIntoPosition(dragView, from.left, from.top, finalPos[0],
-                    finalPos[1], 1, 1, 1, scaleXY[0], scaleXY[1], onComplete, endStyle,
+            dragLayer.animateViewIntoPosition(dragView, finalPos[0],
+                    finalPos[1], 1, scaleXY[0], scaleXY[1], onComplete, endStyle,
                     duration, this);
         }
     }
diff --git a/src/com/android/launcher3/WorkspaceLayoutManager.java b/src/com/android/launcher3/WorkspaceLayoutManager.java
index d6302ce..326e3c3 100644
--- a/src/com/android/launcher3/WorkspaceLayoutManager.java
+++ b/src/com/android/launcher3/WorkspaceLayoutManager.java
@@ -32,6 +32,8 @@
     int EXTRA_EMPTY_SCREEN_ID = -201;
     // The is the first screen. It is always present, even if its empty.
     int FIRST_SCREEN_ID = 0;
+    // This panel is shown on the first page if the panel count is greater than 1.
+    int LEFT_PANEL_ID = -777;
 
     /**
      * At bind time, we use the rank (screenId) to compute x and y for hotseat items.
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index aab6cb2..1fe50f8 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -220,14 +220,14 @@
             + "predictions to be updated while they are visible to the user.");
 
     public static final BooleanFlag ENABLE_TASKBAR = getDebugFlag(
-            "ENABLE_TASKBAR", false, "Allows a system Taskbar to be shown on larger devices.");
+            "ENABLE_TASKBAR", true, "Allows a system Taskbar to be shown on larger devices.");
 
     public static final BooleanFlag ENABLE_OVERVIEW_GRID = getDebugFlag(
-            "ENABLE_OVERVIEW_GRID", false, "Uses grid overview layout. "
+            "ENABLE_OVERVIEW_GRID", true, "Uses grid overview layout. "
             + "Only applicable on large screen devices.");
 
     public static final BooleanFlag ENABLE_TWO_PANEL_HOME = getDebugFlag(
-            "ENABLE_TWO_PANEL_HOME", false,
+            "ENABLE_TWO_PANEL_HOME", true,
             "Uses two panel on home screen. Only applicable on large screen devices.");
 
     public static final BooleanFlag ENABLE_SCRIM_FOR_APP_LAUNCH = getDebugFlag(
@@ -254,6 +254,10 @@
             "ENABLE_WALLPAPER_SCRIM", false,
             "Enables scrim over wallpaper for text protection.");
 
+    public static final BooleanFlag WIDGETS_IN_LAUNCHER_PREVIEW = getDebugFlag(
+            "WIDGETS_IN_LAUNCHER_PREVIEW", true,
+            "Enables widgets in Launcher preview for the Wallpaper app.");
+
     public static void initialize(Context context) {
         synchronized (sDebugFlags) {
             for (DebugFlag flag : sDebugFlags) {
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 5731db4..1e0edac 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -74,7 +74,7 @@
     /** Coordinate for last touch event **/
     protected final Point mLastTouch = new Point();
 
-    private final Point mTmpPoint = new Point();
+    protected final Point mTmpPoint = new Point();
 
     protected DropTarget.DragObject mDragObject;
 
@@ -317,7 +317,7 @@
         mDragObject.dragView.animateTo(mMotionDown.x, mMotionDown.y, onCompleteRunnable, duration);
     }
 
-    private void callOnDragEnd() {
+    protected void callOnDragEnd() {
         if (mIsInPreDrag && mOptions.preDragCondition != null) {
             mOptions.preDragCondition.onPreDragEnd(mDragObject, false /* dragStarted*/);
         }
@@ -343,7 +343,7 @@
     /**
      * Clamps the position to the drag layer bounds.
      */
-    private Point getClampedDragLayerPos(float x, float y) {
+    protected Point getClampedDragLayerPos(float x, float y) {
         mActivity.getDragLayer().getLocalVisibleRect(mRectTemp);
         mTmpPoint.x = (int) Math.max(mRectTemp.left, Math.min(x, mRectTemp.right - 1));
         mTmpPoint.y = (int) Math.max(mRectTemp.top, Math.min(y, mRectTemp.bottom - 1));
@@ -390,7 +390,7 @@
             return false;
         }
 
-        Point dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
+        Point dragLayerPos = getClampedDragLayerPos(getX(ev), getY(ev));
         mLastTouch.set(dragLayerPos.x,  dragLayerPos.y);
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
             // Remember location of down touch
@@ -403,6 +403,14 @@
         return mDragDriver != null && mDragDriver.onInterceptTouchEvent(ev);
     }
 
+    protected float getX(MotionEvent ev) {
+        return ev.getX();
+    }
+
+    protected float getY(MotionEvent ev) {
+        return ev.getY();
+    }
+
     /**
      * Call this from a drag source view.
      */
diff --git a/src/com/android/launcher3/dragndrop/DragDriver.java b/src/com/android/launcher3/dragndrop/DragDriver.java
index d4ce308..72e47e5 100644
--- a/src/com/android/launcher3/dragndrop/DragDriver.java
+++ b/src/com/android/launcher3/dragndrop/DragDriver.java
@@ -165,8 +165,11 @@
      * Class for driving an internal (i.e. not using framework) drag/drop operation.
      */
     static class InternalDragDriver extends DragDriver {
+        private final DragController mDragController;
+
         InternalDragDriver(DragController dragController, Consumer<MotionEvent> sec) {
             super(dragController, sec);
+            mDragController = dragController;
         }
 
         @Override
@@ -176,11 +179,14 @@
 
             switch (action) {
                 case MotionEvent.ACTION_MOVE:
-                    mEventListener.onDriverDragMove(ev.getX(), ev.getY());
+                    mEventListener.onDriverDragMove(mDragController.getX(ev),
+                            mDragController.getY(ev));
                     break;
                 case MotionEvent.ACTION_UP:
-                    mEventListener.onDriverDragMove(ev.getX(), ev.getY());
-                    mEventListener.onDriverDragEnd(ev.getX(), ev.getY());
+                    mEventListener.onDriverDragMove(mDragController.getX(ev),
+                            mDragController.getY(ev));
+                    mEventListener.onDriverDragEnd(mDragController.getX(ev),
+                            mDragController.getY(ev));
                     break;
                 case MotionEvent.ACTION_CANCEL:
                     mEventListener.onDriverDragCancel();
@@ -197,7 +203,8 @@
 
             switch (action) {
                 case MotionEvent.ACTION_UP:
-                    mEventListener.onDriverDragEnd(ev.getX(), ev.getY());
+                    mEventListener.onDriverDragEnd(mDragController.getX(ev),
+                            mDragController.getY(ev));
                     break;
                 case MotionEvent.ACTION_CANCEL:
                     mEventListener.onDriverDragCancel();
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 011325d..5ee4203 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -17,14 +17,19 @@
 
 package com.android.launcher3.dragndrop;
 
+import static android.animation.ObjectAnimator.ofFloat;
+
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
+import static com.android.launcher3.Utilities.mapRange;
+import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
 
 import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.animation.TypeEvaluator;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Canvas;
@@ -44,10 +49,11 @@
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutAndWidgetContainer;
 import com.android.launcher3.Workspace;
+import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.anim.SpringProperty;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.graphics.Scrim;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
-import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.views.BaseDragLayer;
 
@@ -69,11 +75,9 @@
     private DragController mDragController;
 
     // Variables relating to animation of views after drop
-    private ValueAnimator mDropAnim = null;
+    private Animator mDropAnim = null;
 
-    @Thunk DragView mDropView = null;
-    @Thunk int mAnchorViewInitialScrollX = 0;
-    @Thunk View mAnchorView = null;
+    private DragView mDropView = null;
 
     private boolean mHoverPointClosesFolder = false;
 
@@ -220,12 +224,7 @@
     public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha,
             float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable,
             int duration) {
-        Rect r = new Rect();
-        getViewRectRelativeToSelf(dragView, r);
-        final int fromX = r.left;
-        final int fromY = r.top;
-
-        animateViewIntoPosition(dragView, fromX, fromY, pos[0], pos[1], alpha, 1, 1, scaleX, scaleY,
+        animateViewIntoPosition(dragView, pos[0], pos[1], alpha, scaleX, scaleY,
                 onFinishRunnable, animationEndStyle, duration, null);
     }
 
@@ -241,11 +240,6 @@
         parentChildren.measureChild(child);
         parentChildren.layoutChild(child);
 
-        Rect dragViewBounds = new Rect();
-        getViewRectRelativeToSelf(dragView, dragViewBounds);
-        final int fromX = dragViewBounds.left;
-        final int fromY = dragViewBounds.top;
-
         float coord[] = new float[2];
         float childScale = child.getScaleX();
 
@@ -288,51 +282,50 @@
 
         child.setVisibility(INVISIBLE);
         Runnable onCompleteRunnable = () -> child.setVisibility(VISIBLE);
-        animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, toScale, toScale,
+        animateViewIntoPosition(dragView, toX, toY, 1, toScale, toScale,
                 onCompleteRunnable, ANIMATION_END_DISAPPEAR, duration, anchorView);
     }
 
-    public void animateViewIntoPosition(final DragView view, final int fromX, final int fromY,
-            final int toX, final int toY, float finalAlpha, float initScaleX, float initScaleY,
-            float finalScaleX, float finalScaleY, Runnable onCompleteRunnable,
-            int animationEndStyle, int duration, View anchorView) {
-        Rect from = new Rect(fromX, fromY, fromX +
-                view.getMeasuredWidth(), fromY + view.getMeasuredHeight());
-        Rect to = new Rect(toX, toY, toX + view.getMeasuredWidth(), toY + view.getMeasuredHeight());
-        animateView(view, from, to, finalAlpha, initScaleX, initScaleY, finalScaleX, finalScaleY, duration,
-                null, null, onCompleteRunnable, animationEndStyle, anchorView);
-    }
-
     /**
      * This method animates a view at the end of a drag and drop animation.
-     *
+     */
+    public void animateViewIntoPosition(final DragView view,
+            final int toX, final int toY, float finalAlpha,
+            float finalScaleX, float finalScaleY, Runnable onCompleteRunnable,
+            int animationEndStyle, int duration, View anchorView) {
+        Rect to = new Rect(toX, toY, toX + view.getMeasuredWidth(), toY + view.getMeasuredHeight());
+        animateView(view, to, finalAlpha, finalScaleX, finalScaleY, duration,
+                null, onCompleteRunnable, animationEndStyle, anchorView);
+    }
+
+    /**
+     * This method animates a view at the end of a drag and drop animation.
      * @param view The view to be animated. This view is drawn directly into DragLayer, and so
      *        doesn't need to be a child of DragLayer.
-     * @param from The initial location of the view. Only the left and top parameters are used.
      * @param to The final location of the view. Only the left and top parameters are used. This
-     *        location doesn't account for scaling, and so should be centered about the desired
-     *        final location (including scaling).
+*        location doesn't account for scaling, and so should be centered about the desired
+*        final location (including scaling).
      * @param finalAlpha The final alpha of the view, in case we want it to fade as it animates.
      * @param finalScaleX The final scale of the view. The view is scaled about its center.
      * @param finalScaleY The final scale of the view. The view is scaled about its center.
      * @param duration The duration of the animation.
      * @param motionInterpolator The interpolator to use for the location of the view.
-     * @param alphaInterpolator The interpolator to use for the alpha of the view.
      * @param onCompleteRunnable Optional runnable to run on animation completion.
      * @param animationEndStyle Whether or not to fade out the view once the animation completes.
-     *        {@link #ANIMATION_END_DISAPPEAR} or {@link #ANIMATION_END_REMAIN_VISIBLE}.
+*        {@link #ANIMATION_END_DISAPPEAR} or {@link #ANIMATION_END_REMAIN_VISIBLE}.
      * @param anchorView If not null, this represents the view which the animated view stays
-     *        anchored to in case scrolling is currently taking place. Note: currently this is
-     *        only used for the X dimension for the case of the workspace.
      */
-    public void animateView(final DragView view, final Rect from, final Rect to,
-            final float finalAlpha, final float initScaleX, final float initScaleY,
-            final float finalScaleX, final float finalScaleY, int duration,
-            final Interpolator motionInterpolator, final Interpolator alphaInterpolator,
-            final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView) {
+    public void animateView(final DragView view, final Rect to,
+            final float finalAlpha, final float finalScaleX, final float finalScaleY, int duration,
+            final Interpolator motionInterpolator, final Runnable onCompleteRunnable,
+            final int animationEndStyle, View anchorView) {
+        view.cancelAnimation();
+        view.requestLayout();
+
+        final int[] from = getViewLocationRelativeToSelf(view);
 
         // Calculate the duration of the animation based on the object's distance
-        final float dist = (float) Math.hypot(to.left - from.left, to.top - from.top);
+        final float dist = (float) Math.hypot(to.left - from[0], to.top - from[1]);
         final Resources res = getResources();
         final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist);
 
@@ -346,93 +339,45 @@
         }
 
         // Fall back to cubic ease out interpolator for the animation if none is specified
-        TimeInterpolator interpolator = null;
-        if (alphaInterpolator == null || motionInterpolator == null) {
-            interpolator = DEACCEL_1_5;
-        }
+        TimeInterpolator interpolator =
+                motionInterpolator == null ? DEACCEL_1_5 : motionInterpolator;
 
         // Animate the view
-        final float initAlpha = view.getAlpha();
-        final float dropViewScale = view.getScaleX();
-        AnimatorUpdateListener updateCb = new AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                final float percent = (Float) animation.getAnimatedValue();
-                final int width = view.getMeasuredWidth();
-                final int height = view.getMeasuredHeight();
+        PendingAnimation anim = new PendingAnimation(duration);
+        anim.add(ofFloat(view, View.SCALE_X, finalScaleX), interpolator, SpringProperty.DEFAULT);
+        anim.add(ofFloat(view, View.SCALE_Y, finalScaleY), interpolator, SpringProperty.DEFAULT);
+        anim.setViewAlpha(view, finalAlpha, interpolator);
+        anim.setFloat(view, VIEW_TRANSLATE_Y, to.top, interpolator);
 
-                float alphaPercent = alphaInterpolator == null ? percent :
-                        alphaInterpolator.getInterpolation(percent);
-                float motionPercent = motionInterpolator == null ? percent :
-                        motionInterpolator.getInterpolation(percent);
-
-                float initialScaleX = initScaleX * dropViewScale;
-                float initialScaleY = initScaleY * dropViewScale;
-                float scaleX = finalScaleX * percent + initialScaleX * (1 - percent);
-                float scaleY = finalScaleY * percent + initialScaleY * (1 - percent);
-                float alpha = finalAlpha * alphaPercent + initAlpha * (1 - alphaPercent);
-
-                float fromLeft = from.left + (initialScaleX - 1f) * width / 2;
-                float fromTop = from.top + (initialScaleY - 1f) * height / 2;
-
-                int x = (int) (fromLeft + Math.round(((to.left - fromLeft) * motionPercent)));
-                int y = (int) (fromTop + Math.round(((to.top - fromTop) * motionPercent)));
-
-                int anchorAdjust = mAnchorView == null ? 0 : (int) (mAnchorView.getScaleX() *
-                    (mAnchorViewInitialScrollX - mAnchorView.getScrollX()));
-
-                int xPos = x - mDropView.getScrollX() + anchorAdjust;
-                int yPos = y - mDropView.getScrollY();
-
-                mDropView.setTranslationX(xPos);
-                mDropView.setTranslationY(yPos);
-                mDropView.setScaleX(scaleX);
-                mDropView.setScaleY(scaleY);
-                mDropView.setAlpha(alpha);
-            }
-        };
-        animateView(view, updateCb, duration, interpolator, onCompleteRunnable, animationEndStyle,
-                anchorView);
+        ObjectAnimator xMotion = ofFloat(view, VIEW_TRANSLATE_X, to.left);
+        if (anchorView != null) {
+            final int startScroll = anchorView.getScrollX();
+            TypeEvaluator<Float> evaluator = (f, s, e) -> mapRange(f, s, e)
+                    + (anchorView.getScaleX() * (startScroll - anchorView.getScrollX()));
+            xMotion.setEvaluator(evaluator);
+        }
+        anim.add(xMotion, interpolator, SpringProperty.DEFAULT);
+        if (onCompleteRunnable != null) {
+            anim.addListener(forEndCallback(onCompleteRunnable));
+        }
+        playDropAnimation(view, anim.buildAnim(), animationEndStyle);
     }
 
-    public void animateView(final DragView view, AnimatorUpdateListener updateCb, int duration,
-            TimeInterpolator interpolator, final Runnable onCompleteRunnable,
-            final int animationEndStyle, View anchorView) {
+    /**
+     * Runs a previously constructed drop animation
+     */
+    public void playDropAnimation(final DragView view, Animator animator, int animationEndStyle) {
         // Clean up the previous animations
         if (mDropAnim != null) mDropAnim.cancel();
 
         // Show the drop view if it was previously hidden
         mDropView = view;
-        mDropView.cancelAnimation();
-        mDropView.requestLayout();
-
-        // Set the anchor view if the page is scrolling
-        if (anchorView != null) {
-            mAnchorViewInitialScrollX = anchorView.getScrollX();
-        }
-        mAnchorView = anchorView;
-
         // Create and start the animation
-        mDropAnim = new ValueAnimator();
-        mDropAnim.setInterpolator(interpolator);
-        mDropAnim.setDuration(duration);
-        mDropAnim.setFloatValues(0f, 1f);
-        mDropAnim.addUpdateListener(updateCb);
-        mDropAnim.addListener(new AnimatorListenerAdapter() {
-            public void onAnimationEnd(Animator animation) {
-                if (onCompleteRunnable != null) {
-                    onCompleteRunnable.run();
-                }
-                switch (animationEndStyle) {
-                case ANIMATION_END_DISAPPEAR:
-                    clearAnimatedView();
-                    break;
-                case ANIMATION_END_REMAIN_VISIBLE:
-                    break;
-                }
-                mDropAnim = null;
-            }
-        });
+        mDropAnim = animator;
+        mDropAnim.addListener(forEndCallback(() -> mDropAnim = null));
+        if (animationEndStyle == ANIMATION_END_DISAPPEAR) {
+            mDropAnim.addListener(forEndCallback(this::clearAnimatedView));
+        }
         mDropAnim.start();
     }
 
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 3fdb256..1f93730 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -53,23 +53,20 @@
 import androidx.dynamicanimation.animation.SpringAnimation;
 import androidx.dynamicanimation.animation.SpringForce;
 
-import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.icons.FastBitmapDrawable;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.statemanager.StateManager.StateListener;
 import com.android.launcher3.util.RunnableList;
-import com.android.launcher3.util.Thunk;
+import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 
 /** A custom view for rendering an icon, folder, shortcut or widget during drag-n-drop. */
-public class DragView extends FrameLayout implements StateListener<LauncherState> {
+public abstract class DragView<T extends Context & ActivityContext> extends FrameLayout {
 
     public static final int VIEW_ZOOM_DURATION = 150;
 
@@ -82,19 +79,18 @@
     private final int mHeight;
 
     private final int mBlurSizeOutline;
-    private final int mRegistrationX;
-    private final int mRegistrationY;
+    protected final int mRegistrationX;
+    protected final int mRegistrationY;
     private final float mInitialScale;
-    private final float mScaleOnDrop;
-    private final int[] mTempLoc = new int[2];
+    protected final float mScaleOnDrop;
+    protected final int[] mTempLoc = new int[2];
 
     private final RunnableList mOnDragStartCallback = new RunnableList();
 
     private Point mDragVisualizeOffset = null;
     private Rect mDragRegion = null;
-    private final Launcher mLauncher;
-    private final DragLayer mDragLayer;
-    @Thunk final DragController mDragController;
+    protected final T mActivity;
+    private final BaseDragLayer<T> mDragLayer;
     private boolean mHasDrawn = false;
 
     final ValueAnimator mAnim;
@@ -110,7 +106,7 @@
     private Path mScaledMaskPath;
     private Drawable mBadge;
 
-    public DragView(Launcher launcher, Drawable drawable, int registrationX,
+    public DragView(T launcher, Drawable drawable, int registrationX,
             int registrationY, final float initialScale, final float scaleOnDrop,
             final float finalScaleDps) {
         this(launcher, getViewFromDrawable(launcher, drawable),
@@ -123,7 +119,7 @@
      * <p>
      * The registration point is the point inside our view that the touch events should
      * be centered upon.
-     * @param launcher The Launcher instance
+     * @param activity The Launcher instance/ActivityContext this DragView is in.
      * @param content the view content that is attached to the drag view.
      * @param width the width of the dragView
      * @param height the height of the dragView
@@ -133,13 +129,12 @@
      * @param scaleOnDrop the scale used in the drop animation.
      * @param finalScaleDps the scale used in the zoom out animation when the drag view is shown.
      */
-    public DragView(Launcher launcher, View content, int width, int height, int registrationX,
+    public DragView(T activity, View content, int width, int height, int registrationX,
             int registrationY, final float initialScale, final float scaleOnDrop,
             final float finalScaleDps) {
-        super(launcher);
-        mLauncher = launcher;
-        mDragLayer = launcher.getDragLayer();
-        mDragController = launcher.getDragController();
+        super(activity);
+        mActivity = activity;
+        mDragLayer = activity.getDragLayer();
 
         mContent = content;
         mWidth = width;
@@ -188,24 +183,6 @@
         setWillNotDraw(false);
     }
 
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        mLauncher.getStateManager().addStateListener(this);
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        mLauncher.getStateManager().removeStateListener(this);
-    }
-
-    @Override
-    public void onStateTransitionComplete(LauncherState finalState) {
-        setVisibility((finalState == LauncherState.NORMAL
-                || finalState == LauncherState.SPRING_LOADED) ? VISIBLE : INVISIBLE);
-    }
-
     /**
      * Initialize {@code #mIconDrawable} if the item can be represented using
      * an {@link AdaptiveIconDrawable} or {@link FolderAdaptiveIcon}.
@@ -222,10 +199,10 @@
             Object[] outObj = new Object[1];
             int w = mWidth;
             int h = mHeight;
-            Drawable dr = Utilities.getFullDrawable(mLauncher, info, w, h, outObj);
+            Drawable dr = Utilities.getFullDrawable(mActivity, info, w, h, outObj);
 
             if (dr instanceof AdaptiveIconDrawable) {
-                int blurMargin = (int) mLauncher.getResources()
+                int blurMargin = (int) mActivity.getResources()
                         .getDimension(R.dimen.blur_size_medium_outline) / 2;
 
                 Rect bounds = new Rect(0, 0, w, h);
@@ -233,13 +210,13 @@
                 // Badge is applied after icon normalization so the bounds for badge should not
                 // be scaled down due to icon normalization.
                 Rect badgeBounds = new Rect(bounds);
-                mBadge = getBadge(mLauncher, info, outObj[0]);
+                mBadge = getBadge(mActivity, info, outObj[0]);
                 mBadge.setBounds(badgeBounds);
 
                 // Do not draw the background in case of folder as its translucent
                 final boolean shouldDrawBackground = !(dr instanceof FolderAdaptiveIcon);
 
-                try (LauncherIcons li = LauncherIcons.obtain(mLauncher)) {
+                try (LauncherIcons li = LauncherIcons.obtain(mActivity)) {
                     Drawable nDr; // drawable to be normalized
                     if (shouldDrawBackground) {
                         nDr = dr;
@@ -430,12 +407,11 @@
         applyTranslation();
     }
 
-    public void animateTo(int toTouchX, int toTouchY, Runnable onCompleteRunnable, int duration) {
-        mTempLoc[0] = toTouchX - mRegistrationX;
-        mTempLoc[1] = toTouchY - mRegistrationY;
-        mDragLayer.animateViewIntoPosition(this, mTempLoc, 1f, mScaleOnDrop, mScaleOnDrop,
-                DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
-    }
+    /**
+     * Animate this DragView to the given DragLayer coordinates and then remove it.
+     */
+    public abstract void animateTo(int toTouchX, int toTouchY, Runnable onCompleteRunnable,
+            int duration);
 
     public void animateShift(final int shiftX, final int shiftY) {
         if (mAnim.isStarted()) {
@@ -471,7 +447,7 @@
             Picture picture = new Picture();
             mContent.draw(picture.beginRecording(mWidth, mHeight));
             picture.endRecording();
-            View view = new View(mLauncher);
+            View view = new View(mActivity);
             view.setBackground(new PictureDrawable(picture));
             view.measure(makeMeasureSpec(mWidth, EXACTLY), makeMeasureSpec(mHeight, EXACTLY));
             view.layout(mContent.getLeft(), mContent.getTop(),
diff --git a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
index ea1fbdb..6a6603c 100644
--- a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
+++ b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
@@ -32,12 +32,12 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.Launcher;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.folder.PreviewBackground;
 import com.android.launcher3.graphics.ShiftedBitmapDrawable;
 import com.android.launcher3.icons.BitmapRenderer;
 import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.views.ActivityContext;
 
 /**
  * {@link AdaptiveIconDrawable} representation of a {@link FolderIcon}
@@ -70,14 +70,14 @@
     }
 
     public static @Nullable FolderAdaptiveIcon createFolderAdaptiveIcon(
-            Launcher launcher, int folderId, Point dragViewSize) {
+            ActivityContext activity, int folderId, Point dragViewSize) {
         Preconditions.assertNonUiThread();
 
         // Create the actual drawable on the UI thread to avoid race conditions with
         // FolderIcon draw pass
         try {
             return MAIN_EXECUTOR.submit(() -> {
-                FolderIcon icon = launcher.findFolderIcon(folderId);
+                FolderIcon icon = activity.findFolderIcon(folderId);
                 return icon == null ? null : createDrawableOnUiThread(icon, dragViewSize);
 
             }).get();
diff --git a/src/com/android/launcher3/dragndrop/LauncherDragController.java b/src/com/android/launcher3/dragndrop/LauncherDragController.java
index a98d70c..0e8b0a5 100644
--- a/src/com/android/launcher3/dragndrop/LauncherDragController.java
+++ b/src/com/android/launcher3/dragndrop/LauncherDragController.java
@@ -96,7 +96,7 @@
         final float scaleDps = mIsInPreDrag
                 ? res.getDimensionPixelSize(R.dimen.pre_drag_view_scale) : 0f;
         final DragView dragView = mDragObject.dragView = drawable != null
-                ? new DragView(
+                ? new LauncherDragView(
                 mActivity,
                 drawable,
                 registrationX,
@@ -104,7 +104,7 @@
                 initialDragViewScale,
                 dragViewScaleOnDrop,
                 scaleDps)
-                : new DragView(
+                : new LauncherDragView(
                         mActivity,
                         view,
                         view.getMeasuredWidth(),
diff --git a/src/com/android/launcher3/dragndrop/LauncherDragView.java b/src/com/android/launcher3/dragndrop/LauncherDragView.java
new file mode 100644
index 0000000..cc68e2e
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/LauncherDragView.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.dragndrop;
+
+import android.graphics.drawable.Drawable;
+import android.view.View;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.statemanager.StateManager;
+
+/**
+ * A DragView drawn/used by the Launcher activity.
+ */
+public class LauncherDragView extends DragView<Launcher>
+        implements StateManager.StateListener<LauncherState> {
+
+
+    public LauncherDragView(Launcher launcher, Drawable drawable, int registrationX,
+            int registrationY, float initialScale, float scaleOnDrop, float finalScaleDps) {
+        super(launcher, drawable, registrationX, registrationY, initialScale, scaleOnDrop,
+                finalScaleDps);
+    }
+
+    public LauncherDragView(Launcher launcher, View content, int width, int height,
+            int registrationX, int registrationY, float initialScale, float scaleOnDrop,
+            float finalScaleDps) {
+        super(launcher, content, width, height, registrationX, registrationY, initialScale,
+                scaleOnDrop, finalScaleDps);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mActivity.getStateManager().addStateListener(this);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mActivity.getStateManager().removeStateListener(this);
+    }
+
+    @Override
+    public void onStateTransitionComplete(LauncherState finalState) {
+        setVisibility((finalState == LauncherState.NORMAL
+                || finalState == LauncherState.SPRING_LOADED) ? VISIBLE : INVISIBLE);
+    }
+
+    @Override
+    public void animateTo(int toTouchX, int toTouchY, Runnable onCompleteRunnable, int duration) {
+        mTempLoc[0] = toTouchX - mRegistrationX;
+        mTempLoc[1] = toTouchY - mRegistrationY;
+        mActivity.getDragLayer().animateViewIntoPosition(this, mTempLoc, 1f, mScaleOnDrop,
+                mScaleOnDrop, DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
+    }
+}
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index e704957..d076be6 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -333,8 +333,6 @@
         if (animateView != null && mActivity instanceof Launcher) {
             final Launcher launcher = (Launcher) mActivity;
             DragLayer dragLayer = launcher.getDragLayer();
-            Rect from = new Rect();
-            dragLayer.getViewRectRelativeToSelf(animateView, from);
             Rect to = finalRect;
             if (to == null) {
                 to = new Rect();
@@ -400,13 +398,14 @@
             }
 
             final int finalIndex = index;
-            dragLayer.animateView(animateView, from, to, finalAlpha,
-                    1, 1, finalScale, finalScale, DROP_IN_ANIMATION_DURATION,
-                    Interpolators.DEACCEL_2, Interpolators.ACCEL_2,
+            dragLayer.animateView(animateView, to, finalAlpha,
+                    finalScale, finalScale, DROP_IN_ANIMATION_DURATION,
+                    Interpolators.DEACCEL_2,
                     () -> {
                         mPreviewItemManager.hidePreviewItem(finalIndex, false);
                         mFolder.showItem(item);
-                    }, DragLayer.ANIMATION_END_DISAPPEAR, null);
+                    }, 
+                    DragLayer.ANIMATION_END_DISAPPEAR, null);
 
             mFolder.hideItem(item);
 
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index a549750..f027b33 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -32,13 +32,13 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.icons.BitmapRenderer;
 import com.android.launcher3.icons.FastBitmapDrawable;
 import com.android.launcher3.util.SafeCloseable;
+import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 
 import java.nio.ByteBuffer;
@@ -150,7 +150,7 @@
     }
 
     public float getScaleAndPosition(Drawable preview, int[] outPos) {
-        float scale = Launcher.getLauncher(mView.getContext())
+        float scale = ActivityContext.lookupContext(mView.getContext())
                 .getDragLayer().getLocationInDragLayer(mView, outPos);
         if (mView instanceof LauncherAppWidgetHostView) {
             // App widgets are technically scaled, but are drawn at their expected size -- so the
@@ -167,7 +167,7 @@
 
     /** Returns the scale and position of a given view for drag-n-drop. */
     public float getScaleAndPosition(View view, int[] outPos) {
-        float scale = Launcher.getLauncher(mView.getContext())
+        float scale = ActivityContext.lookupContext(mView.getContext())
                 .getDragLayer().getLocationInDragLayer(mView, outPos);
         if (mView instanceof LauncherAppWidgetHostView) {
             // App widgets are technically scaled, but are drawn at their expected size -- so the
@@ -201,7 +201,7 @@
         public void run() {
             Bitmap preview = convertPreviewToAlphaBitmap(mPreviewSnapshot);
             if (mIsIcon) {
-                int size = Launcher.getLauncher(mContext).getDeviceProfile().iconSizePx;
+                int size = ActivityContext.lookupContext(mContext).getDeviceProfile().iconSizePx;
                 preview = Bitmap.createScaledBitmap(preview, size, size, false);
             }
             //else case covers AppWidgetHost (doesn't drag/drop across different device profiles)
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index cf3da4b..1127ff9 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -26,6 +26,8 @@
 
 import android.annotation.TargetApi;
 import android.app.Fragment;
+import android.app.WallpaperColors;
+import android.appwidget.AppWidgetHost;
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
@@ -41,6 +43,7 @@
 import android.os.Looper;
 import android.os.Process;
 import android.util.AttributeSet;
+import android.util.SparseIntArray;
 import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
@@ -80,10 +83,15 @@
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.BaseDragLayer;
+import com.android.launcher3.widget.BaseLauncherAppWidgetHostView;
+import com.android.launcher3.widget.LauncherAppWidgetHost;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.widget.LocalColorExtractor;
+import com.android.launcher3.widget.NavigableAppWidgetHostView;
 import com.android.launcher3.widget.custom.CustomWidgetManager;
 
 import java.util.ArrayList;
@@ -201,8 +209,13 @@
     private final InsettableFrameLayout mRootView;
     private final Hotseat mHotseat;
     private final CellLayout mWorkspace;
+    private final AppWidgetHost mAppWidgetHost;
+    private final SparseIntArray mWallpaperColorResources;
 
-    public LauncherPreviewRenderer(Context context, InvariantDeviceProfile idp) {
+    public LauncherPreviewRenderer(Context context,
+            InvariantDeviceProfile idp,
+            WallpaperColors wallpaperColors) {
+
         super(context);
         mUiHandler = new Handler(Looper.getMainLooper());
         mContext = context;
@@ -254,6 +267,17 @@
                 mDp.workspacePadding.top,
                 mDp.workspacePadding.right + mDp.cellLayoutPaddingLeftRightPx,
                 mDp.workspacePadding.bottom);
+
+        if (FeatureFlags.WIDGETS_IN_LAUNCHER_PREVIEW.get()) {
+            mAppWidgetHost = new LauncherPreviewAppWidgetHost(context);
+            mWallpaperColorResources =  wallpaperColors != null
+                    ? LocalColorExtractor.newInstance(context)
+                            .generateColorsOverride(wallpaperColors)
+                    : null;
+        } else {
+            mAppWidgetHost = null;
+            mWallpaperColorResources = null;
+        }
     }
 
     /** Populate preview and render it. */
@@ -353,9 +377,20 @@
 
     private void inflateAndAddWidgets(
             LauncherAppWidgetInfo info, LauncherAppWidgetProviderInfo providerInfo) {
-        AppWidgetHostView view = new AppWidgetHostView(mContext);
-        view.setAppWidget(-1, providerInfo);
-        view.updateAppWidget(null);
+        AppWidgetHostView view;
+        if (FeatureFlags.WIDGETS_IN_LAUNCHER_PREVIEW.get()) {
+            view = mAppWidgetHost.createView(mContext, info.appWidgetId, providerInfo);
+        } else {
+            view = new NavigableAppWidgetHostView(this) {
+                @Override
+                protected boolean shouldAllowDirectClick() {
+                    return false;
+                }
+            };
+            view.setAppWidget(-1, providerInfo);
+            view.updateAppWidget(null);
+        }
+
         view.setTag(info);
         addInScreenFromBind(view, info);
     }
@@ -392,11 +427,14 @@
         ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
         ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
         ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
-        filterCurrentWorkspaceItems(0 /* currentScreenId */,
-                dataModel.workspaceItems, currentWorkspaceItems,
-                otherWorkspaceItems);
-        filterCurrentWorkspaceItems(0 /* currentScreenId */, dataModel.appWidgets,
-                currentAppWidgets, otherAppWidgets);
+
+        IntSet currentScreenIds = IntSet.wrap(0);
+        // TODO(b/185508060): support two panel preview.
+        filterCurrentWorkspaceItems(currentScreenIds, dataModel.workspaceItems,
+                currentWorkspaceItems, otherWorkspaceItems);
+        filterCurrentWorkspaceItems(currentScreenIds, dataModel.appWidgets, currentAppWidgets,
+                otherAppWidgets);
+
         sortWorkspaceItemsSpatially(mIdp, currentWorkspaceItems);
         for (ItemInfo itemInfo : currentWorkspaceItems) {
             switch (itemInfo.itemType) {
@@ -469,6 +507,35 @@
         view.layout(0, 0, width, height);
     }
 
+    private class LauncherPreviewAppWidgetHost extends AppWidgetHost {
+
+        private LauncherPreviewAppWidgetHost(Context context) {
+            super(context, LauncherAppWidgetHost.APPWIDGET_HOST_ID);
+        }
+
+        @Override
+        protected AppWidgetHostView onCreateView(
+                Context context,
+                int appWidgetId,
+                AppWidgetProviderInfo appWidget) {
+            return new LauncherPreviewAppWidgetHostView(LauncherPreviewRenderer.this);
+        }
+    }
+
+    private class LauncherPreviewAppWidgetHostView extends BaseLauncherAppWidgetHostView {
+        private LauncherPreviewAppWidgetHostView(Context context) {
+            super(context);
+            if (Utilities.ATLEAST_S && mWallpaperColorResources != null) {
+                setColorResources(mWallpaperColorResources);
+            }
+        }
+
+        @Override
+        protected boolean shouldAllowDirectClick() {
+            return false;
+        }
+    }
+
     /** Root layout for launcher preview that intercepts all touch events. */
     public static class LauncherPreviewLayout extends InsettableFrameLayout {
         public LauncherPreviewLayout(Context context, AttributeSet attrs) {
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index a8c3d15..df49359 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -209,7 +209,7 @@
         if (mDestroyed) {
             return;
         }
-        View view = new LauncherPreviewRenderer(inflationContext, mIdp)
+        View view = new LauncherPreviewRenderer(inflationContext, mIdp, mWallpaperColors)
                 .getRenderedView(dataModel, widgetProviderInfoMap);
         // This aspect scales the view to fit in the surface and centers it
         final float scale = Math.min(mWidth / (float) view.getMeasuredWidth(),
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index 01b3e6e..b2b0010 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -287,28 +287,19 @@
 
         // Find appropriate space for the item.
         int screenId = 0;
-        int[] cordinates = new int[2];
+        int[] coordinates = new int[2];
         boolean found = false;
 
         int screenCount = workspaceScreens.size();
-        // First check the preferred screen.
-        int preferredScreenIndex = workspaceScreens.isEmpty() ? 0 : 1;
-        if (preferredScreenIndex < screenCount) {
-            screenId = workspaceScreens.get(preferredScreenIndex);
-            found = findNextAvailableIconSpaceInScreen(
-                    app, screenItems.get(screenId), cordinates, spanX, spanY);
-        }
-
-        if (!found) {
-            // Search on any of the screens starting from the first screen.
-            for (int screen = 1; screen < screenCount; screen++) {
-                screenId = workspaceScreens.get(screen);
-                if (findNextAvailableIconSpaceInScreen(
-                        app, screenItems.get(screenId), cordinates, spanX, spanY)) {
-                    // We found a space for it
-                    found = true;
-                    break;
-                }
+        int firstScreenToCheck = dataModel.isLeftPanelShown ? 2 : 1;
+        // Search on the screens for empty space
+        for (int screen = firstScreenToCheck; screen < screenCount; screen++) {
+            screenId = workspaceScreens.get(screen);
+            if (findNextAvailableIconSpaceInScreen(
+                    app, screenItems.get(screenId), coordinates, spanX, spanY)) {
+                // We found a space for it
+                found = true;
+                break;
             }
         }
 
@@ -324,11 +315,11 @@
 
             // If we still can't find an empty space, then God help us all!!!
             if (!findNextAvailableIconSpaceInScreen(
-                    app, screenItems.get(screenId), cordinates, spanX, spanY)) {
+                    app, screenItems.get(screenId), coordinates, spanX, spanY)) {
                 throw new RuntimeException("Can't find space to add the item");
             }
         }
-        return new int[] {screenId, cordinates[0], cordinates[1]};
+        return new int[] {screenId, coordinates[0], coordinates[1]};
     }
 
     private boolean findNextAvailableIconSpaceInScreen(
diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java
index 5c85bab..30755e3 100644
--- a/src/com/android/launcher3/model/BaseLoaderResults.java
+++ b/src/com/android/launcher3/model/BaseLoaderResults.java
@@ -18,22 +18,24 @@
 
 import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
 import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
+import android.os.Process;
 import android.util.Log;
 
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.PagedView;
 import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.util.LooperIdleLock;
-import com.android.launcher3.util.ViewOnDrawExecutor;
+import com.android.launcher3.util.RunnableList;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -160,20 +162,25 @@
         }
 
         private void bind() {
-            final int currentScreen;
+            IntSet currentScreenIndices;
             {
                 // Create an anonymous scope to calculate currentScreen as it has to be a
                 // final variable.
-                int currScreen = mCallbacks.getPageToBindSynchronously();
-                if (currScreen >= mOrderedScreenIds.size()) {
-                    // There may be no workspace screens (just hotseat items and an empty page).
-                    currScreen = PagedView.INVALID_PAGE;
+                IntSet screenIndices = mCallbacks.getPagesToBindSynchronously();
+                if (screenIndices == null || screenIndices.isEmpty()
+                        || screenIndices.getArray().get(screenIndices.size() - 1)
+                        >= mOrderedScreenIds.size()) {
+                    // There maybe no workspace screens (just hotseat items and an empty page).
+                    // Also we want to prevent IndexOutOfBoundsExceptions.
+                    screenIndices = new IntSet();
                 }
-                currentScreen = currScreen;
+                currentScreenIndices = screenIndices;
             }
-            final boolean validFirstPage = currentScreen >= 0;
-            final int currentScreenId =
-                    validFirstPage ? mOrderedScreenIds.get(currentScreen) : INVALID_SCREEN_ID;
+
+
+            IntSet currentScreenIds  = new IntSet();
+            currentScreenIndices.forEach(
+                    index -> currentScreenIds.add(mOrderedScreenIds.get(index)));
 
             // Separate the items that are on the current screen, and all the other remaining items
             ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
@@ -181,9 +188,9 @@
             ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
             ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
 
-            filterCurrentWorkspaceItems(currentScreenId, mWorkspaceItems, currentWorkspaceItems,
+            filterCurrentWorkspaceItems(currentScreenIds, mWorkspaceItems, currentWorkspaceItems,
                     otherWorkspaceItems);
-            filterCurrentWorkspaceItems(currentScreenId, mAppWidgets, currentAppWidgets,
+            filterCurrentWorkspaceItems(currentScreenIds, mAppWidgets, currentAppWidgets,
                     otherAppWidgets);
             final InvariantDeviceProfile idp = mApp.getInvariantDeviceProfile();
             sortWorkspaceItemsSpatially(idp, currentWorkspaceItems);
@@ -198,40 +205,25 @@
             // Bind workspace screens
             executeCallbacksTask(c -> c.bindScreens(mOrderedScreenIds), mUiExecutor);
 
-            Executor mainExecutor = mUiExecutor;
             // Load items on the current page.
-            bindWorkspaceItems(currentWorkspaceItems, mainExecutor);
-            bindAppWidgets(currentAppWidgets, mainExecutor);
+            bindWorkspaceItems(currentWorkspaceItems, mUiExecutor);
+            bindAppWidgets(currentAppWidgets, mUiExecutor);
             mExtraItems.forEach(item ->
-                    executeCallbacksTask(c -> c.bindExtraContainerItems(item), mainExecutor));
+                    executeCallbacksTask(c -> c.bindExtraContainerItems(item), mUiExecutor));
 
-            // In case of validFirstPage, only bind the first screen, and defer binding the
-            // remaining screens after first onDraw (and an optional the fade animation whichever
-            // happens later).
-            // This ensures that the first screen is immediately visible (eg. during rotation)
-            // In case of !validFirstPage, bind all pages one after other.
+            RunnableList pendingTasks = new RunnableList();
+            Executor pendingExecutor = pendingTasks::add;
+            bindWorkspaceItems(otherWorkspaceItems, pendingExecutor);
+            bindAppWidgets(otherAppWidgets, pendingExecutor);
+            executeCallbacksTask(c -> c.finishBindingItems(currentScreenIndices), pendingExecutor);
+            pendingExecutor.execute(
+                    () -> MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT));
 
-            final Executor deferredExecutor =
-                    validFirstPage ? new ViewOnDrawExecutor() : mainExecutor;
-
-            executeCallbacksTask(c -> c.finishFirstPageBind(
-                    validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null), mainExecutor);
-
-            bindWorkspaceItems(otherWorkspaceItems, deferredExecutor);
-            bindAppWidgets(otherAppWidgets, deferredExecutor);
-            // Tell the workspace that we're done binding items
-            executeCallbacksTask(c -> c.finishBindingItems(currentScreen), deferredExecutor);
-
-            if (validFirstPage) {
-                executeCallbacksTask(c -> {
-                    // We are loading synchronously, which means, some of the pages will be
-                    // bound after first draw. Inform the mCallbacks that page binding is
-                    // not complete, and schedule the remaining pages.
-                    c.onPageBoundSynchronously(currentScreen);
-                    c.executeOnNextDraw((ViewOnDrawExecutor) deferredExecutor);
-
-                }, mUiExecutor);
-            }
+            executeCallbacksTask(
+                    c -> {
+                        MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+                        c.onInitialBindComplete(currentScreenIndices, pendingTasks);
+                    }, mUiExecutor);
         }
 
         private void bindWorkspaceItems(
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 1d7d1a2..ba825ca 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -49,7 +49,7 @@
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.IntSparseArrayMap;
 import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.ViewOnDrawExecutor;
+import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 
 import java.io.FileDescriptor;
@@ -117,6 +117,11 @@
     public int lastBindId = 0;
 
     /**
+     * Value that indicates if left widget panel is shown or not.
+     */
+    public boolean isLeftPanelShown = false;
+
+    /**
      * Clears all the data
      */
     public synchronized void clear() {
@@ -141,6 +146,14 @@
         if (FeatureFlags.QSB_ON_FIRST_SCREEN || screenSet.isEmpty()) {
             screenSet.add(Workspace.FIRST_SCREEN_ID);
         }
+
+        if (isLeftPanelShown) {
+            // We should add it even though there are no items on it.
+            screenSet.add(Workspace.LEFT_PANEL_ID);
+        } else {
+            // We should NOT add it even though there are items on it.
+            screenSet.remove(Workspace.LEFT_PANEL_ID);
+        }
         return screenSet.getArray();
     }
 
@@ -446,37 +459,44 @@
         int FLAG_QUIET_MODE_CHANGE_PERMISSION = 1 << 2;
 
         /**
-         * Returns the page number to bind first, synchronously if possible or -1
+         * Returns an IntSet of page numbers to bind first, synchronously if possible
+         * or an empty IntSet
          */
-        int getPageToBindSynchronously();
-        void clearPendingBinds();
-        void startBinding();
-        void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons);
-        void bindScreens(IntArray orderedScreenIds);
-        void finishFirstPageBind(ViewOnDrawExecutor executor);
-        void finishBindingItems(int pageBoundFirst);
-        void preAddApps();
-        void bindAppsAdded(IntArray newScreens,
-                ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated);
+        default IntSet getPagesToBindSynchronously() {
+            return new IntSet();
+        }
+
+        default void clearPendingBinds() { }
+        default void startBinding() { }
+
+        default void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) { }
+        default void bindScreens(IntArray orderedScreenIds) { }
+        default void finishBindingItems(IntSet pagesBoundFirst) { }
+        default void preAddApps() { }
+        default void bindAppsAdded(IntArray newScreens,
+                ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated) { }
 
         /**
          * Binds updated incremental download progress
          */
-        void bindIncrementalDownloadProgressUpdated(AppInfo app);
-        void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated);
-        void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
-        void bindRestoreItemsChange(HashSet<ItemInfo> updates);
-        void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher);
-        void bindAllWidgets(List<WidgetsListBaseEntry> widgets);
-        void onPageBoundSynchronously(int page);
-        void executeOnNextDraw(ViewOnDrawExecutor executor);
-        void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap);
+        default void bindIncrementalDownloadProgressUpdated(AppInfo app) { }
+        default void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) { }
+        default void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets) { }
+        default void bindRestoreItemsChange(HashSet<ItemInfo> updates) { }
+        default void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher) { }
+        default void bindAllWidgets(List<WidgetsListBaseEntry> widgets) { }
+
+        default void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks) {
+            pendingTasks.executeAllAndDestroy();
+        }
+
+        default void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap) { }
 
         /**
          * Binds extra item provided any external source
          */
         default void bindExtraContainerItems(FixedContainerItems item) { }
 
-        void bindAllApplications(AppInfo[] apps, int flags);
+        default void bindAllApplications(AppInfo[] apps, int flags) { }
     }
 }
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 28fd389..d5b5452 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -86,6 +86,7 @@
 import com.android.launcher3.shortcuts.ShortcutRequest.QueryResult;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.IOUtils;
+import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.LooperIdleLock;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.PackageUserKey;
@@ -176,8 +177,9 @@
         ArrayList<ItemInfo> allItems = mBgDataModel.getAllWorkspaceItems();
         // Screen set is never empty
         final int firstScreen = mBgDataModel.collectWorkspaceScreens().get(0);
+        // TODO(b/185515153): support two panel home.
 
-        filterCurrentWorkspaceItems(firstScreen, allItems, firstScreenItems,
+        filterCurrentWorkspaceItems(IntSet.wrap(firstScreen), allItems, firstScreenItems,
                 new ArrayList<>() /* otherScreenItems are ignored */);
         mFirstScreenBroadcast.sendBroadcasts(mApp.getContext(), firstScreenItems);
     }
diff --git a/src/com/android/launcher3/model/ModelUtils.java b/src/com/android/launcher3/model/ModelUtils.java
index 9b5fac8..58aa9e5 100644
--- a/src/com/android/launcher3/model/ModelUtils.java
+++ b/src/com/android/launcher3/model/ModelUtils.java
@@ -51,7 +51,8 @@
      * Filters the set of items who are directly or indirectly (via another container) on the
      * specified screen.
      */
-    public static <T extends ItemInfo> void filterCurrentWorkspaceItems(int currentScreenId,
+    public static <T extends ItemInfo> void filterCurrentWorkspaceItems(
+            IntSet currentScreenIds,
             ArrayList<T> allWorkspaceItems,
             ArrayList<T> currentScreenItems,
             ArrayList<T> otherScreenItems) {
@@ -65,7 +66,7 @@
                 (lhs, rhs) -> Integer.compare(lhs.container, rhs.container));
         for (T info : allWorkspaceItems) {
             if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
-                if (info.screenId == currentScreenId) {
+                if (currentScreenIds.contains(info.screenId)) {
                     currentScreenItems.add(info);
                     itemsOnScreen.add(info.id);
                 } else {
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index 080ce20..2b93118 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -112,6 +112,13 @@
         }
     }
 
+    /**
+     * Sets the value that indicates if left widget panel is shown or not.
+     */
+    public void setLeftPanelShown(boolean value) {
+        mBgDataModel.isLeftPanelShown = value;
+    }
+
     private void checkItemInfoLocked(int itemId, ItemInfo item, StackTraceElement[] stackTrace) {
         ItemInfo modelItem = mBgDataModel.itemsIdMap.get(itemId);
         if (modelItem != null && item != modelItem) {
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index d3f4909..e5424cf 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -50,8 +50,6 @@
      */
     private boolean isEnabled = true;
 
-    private boolean mHasFinishRecentsInAction = false;
-
     public SystemShortcut(int iconResId, int labelResId, T target, ItemInfo itemInfo) {
         mIconResId = iconResId;
         mLabelResId = labelResId;
@@ -102,14 +100,6 @@
         return mAccessibilityActionId == action;
     }
 
-    public void setHasFinishRecentsInAction(boolean hasFinishRecentsInAction) {
-        mHasFinishRecentsInAction = hasFinishRecentsInAction;
-    }
-
-    public boolean hasFinishRecentsInAction() {
-        return mHasFinishRecentsInAction;
-    }
-
     public interface Factory<T extends BaseDraggingActivity> {
 
         @Nullable SystemShortcut<T> getShortcut(T activity, ItemInfo itemInfo);
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index 5999091..1a96c23 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -35,22 +35,13 @@
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
-import com.android.launcher3.model.data.LauncherAppWidgetInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.Themes;
-import com.android.launcher3.util.ViewOnDrawExecutor;
 import com.android.launcher3.views.BaseDragLayer;
-import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 
-import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
 
 /**
  * Launcher activity for secondary displays
@@ -175,67 +166,10 @@
     }
 
     @Override
-    public int getPageToBindSynchronously() {
-        return 0;
-    }
-
-    @Override
-    public void clearPendingBinds() { }
-
-    @Override
-    public void startBinding() { }
-
-    @Override
-    public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) { }
-
-    @Override
-    public void bindScreens(IntArray orderedScreenIds) { }
-
-    @Override
-    public void finishFirstPageBind(ViewOnDrawExecutor executor) {
-        if (executor != null) {
-            executor.onLoadAnimationCompleted();
-        }
-    }
-
-    @Override
-    public void finishBindingItems(int pageBoundFirst) { }
-
-    @Override
-    public void preAddApps() { }
-
-    @Override
-    public void bindAppsAdded(IntArray newScreens, ArrayList<ItemInfo> addNotAnimated,
-            ArrayList<ItemInfo> addAnimated) { }
-
-    @Override
     public void bindIncrementalDownloadProgressUpdated(AppInfo app) {
         mAppsView.getAppsStore().updateProgressBar(app);
     }
 
-    @Override
-    public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) { }
-
-    @Override
-    public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets) { }
-
-    @Override
-    public void bindRestoreItemsChange(HashSet<ItemInfo> updates) { }
-
-    @Override
-    public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher) { }
-
-    @Override
-    public void bindAllWidgets(List<WidgetsListBaseEntry> widgets) { }
-
-    @Override
-    public void onPageBoundSynchronously(int page) { }
-
-    @Override
-    public void executeOnNextDraw(ViewOnDrawExecutor executor) {
-        executor.attachTo(getDragLayer(), false, null);
-    }
-
     /**
      * Called when apps-button is clicked
      */
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 4261d08..5cd3682 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -117,6 +117,10 @@
                 TestProtocol.sDisableSensorRotation = true;
                 return response;
 
+            case TestProtocol.REQUEST_IS_TABLET:
+                response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, mDeviceProfile.isTablet);
+                return response;
+
             default:
                 return null;
         }
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index b6da7fc..2f1f82d 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -94,6 +94,7 @@
     public static final String REQUEST_GET_TEST_EVENTS = "get-test-events";
     public static final String REQUEST_STOP_EVENT_LOGGING = "stop-event-logging";
     public static final String REQUEST_CLEAR_DATA = "clear-data";
+    public static final String REQUEST_IS_TABLET = "is-tablet";
 
     public static boolean sDebugTracing = false;
     public static final String REQUEST_ENABLE_DEBUG_TRACING = "enable-debug-tracing";
diff --git a/src/com/android/launcher3/util/FlingAnimation.java b/src/com/android/launcher3/util/FlingAnimation.java
index c9aa51c..ac864e9 100644
--- a/src/com/android/launcher3/util/FlingAnimation.java
+++ b/src/com/android/launcher3/util/FlingAnimation.java
@@ -1,12 +1,14 @@
 package com.android.launcher3.util;
 
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
 
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.graphics.PointF;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.view.animation.AnimationUtils;
 import android.view.animation.DecelerateInterpolator;
 
@@ -35,7 +37,7 @@
     protected final float mUX, mUY;
 
     protected Rect mIconRect;
-    protected Rect mFrom;
+    protected RectF mFrom;
     protected int mDuration;
     protected float mAnimationTimeFraction;
 
@@ -55,17 +57,17 @@
     @Override
     public void run() {
         mIconRect = mDropTarget.getIconRect(mDragObject);
+        mDragObject.dragView.cancelAnimation();
+        mDragObject.dragView.requestLayout();
 
         // Initiate from
-        mFrom = new Rect();
-        mDragLayer.getViewRectRelativeToSelf(mDragObject.dragView, mFrom);
-        float scale = mDragObject.dragView.getScaleX();
-        float xOffset = ((scale - 1f) * mDragObject.dragView.getMeasuredWidth()) / 2f;
-        float yOffset = ((scale - 1f) * mDragObject.dragView.getMeasuredHeight()) / 2f;
-        mFrom.left += xOffset;
-        mFrom.right -= xOffset;
-        mFrom.top += yOffset;
-        mFrom.bottom -= yOffset;
+        Rect from = new Rect();
+        mDragLayer.getViewRectRelativeToSelf(mDragObject.dragView, from);
+
+        mFrom = new RectF(from);
+        mFrom.inset(
+                ((1 - mDragObject.dragView.getScaleX()) * from.width()) / 2f,
+                ((1 - mDragObject.dragView.getScaleY()) * from.height()) / 2f);
         mDuration = Math.abs(mUY) > Math.abs(mUX) ? initFlingUpDuration() : initFlingLeftDuration();
 
         mAnimationTimeFraction = ((float) mDuration) / (mDuration + DRAG_END_DELAY);
@@ -95,17 +97,15 @@
             }
         };
 
-        Runnable onAnimationEndRunnable = new Runnable() {
-            @Override
-            public void run() {
-                mLauncher.getStateManager().goToState(NORMAL);
-                mDropTarget.completeDrop(mDragObject);
-            }
-        };
-
         mDropTarget.onDrop(mDragObject, mDragOptions);
-        mDragLayer.animateView(mDragObject.dragView, this, duration, tInterpolator,
-                onAnimationEndRunnable, DragLayer.ANIMATION_END_DISAPPEAR, null);
+        ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+        anim.setDuration(duration).setInterpolator(tInterpolator);
+        anim.addUpdateListener(this);
+        anim.addListener(forEndCallback(() -> {
+            mLauncher.getStateManager().goToState(NORMAL);
+            mDropTarget.completeDrop(mDragObject);
+        }));
+        mDragLayer.playDropAnimation(mDragObject.dragView, anim, DragLayer.ANIMATION_END_DISAPPEAR);
     }
 
     /**
@@ -129,7 +129,7 @@
         }
         double t = (-mUY - Math.sqrt(d)) / mAY;
 
-        float sX = -mFrom.exactCenterX() + mIconRect.exactCenterX();
+        float sX = -mFrom.centerX() + mIconRect.exactCenterX();
 
         // Find horizontal acceleration such that: u*t + a*t*t/2 = s
         mAX = (float) ((sX - t * mUX) * 2 / (t * t));
@@ -157,7 +157,7 @@
         }
         double t = (-mUX - Math.sqrt(d)) / mAX;
 
-        float sY = -mFrom.exactCenterY() + mIconRect.exactCenterY();
+        float sY = -mFrom.centerY() + mIconRect.exactCenterY();
 
         // Find vertical acceleration such that: u*t + a*t*t/2 = s
         mAY = (float) ((sY - t * mUY) * 2 / (t * t));
diff --git a/src/com/android/launcher3/util/IntArray.java b/src/com/android/launcher3/util/IntArray.java
index 7252f7a..e7235e7 100644
--- a/src/com/android/launcher3/util/IntArray.java
+++ b/src/com/android/launcher3/util/IntArray.java
@@ -17,13 +17,14 @@
 package com.android.launcher3.util;
 
 import java.util.Arrays;
+import java.util.Iterator;
 import java.util.StringTokenizer;
 
 /**
  * Copy of the platform hidden implementation of android.util.IntArray.
  * Implements a growing array of int primitives.
  */
-public class IntArray implements Cloneable {
+public class IntArray implements Cloneable, Iterable<Integer> {
     private static final int MIN_CAPACITY_INCREMENT = 12;
 
     private static final int[] EMPTY_INT = new int[0];
@@ -272,4 +273,30 @@
             throw new ArrayIndexOutOfBoundsException("length=" + len + "; index=" + index);
         }
     }
+
+    @Override
+    public Iterator<Integer> iterator() {
+        return new ValueIterator();
+    }
+
+    @Thunk
+    class ValueIterator implements Iterator<Integer> {
+
+        private int mNextIndex = 0;
+
+        @Override
+        public boolean hasNext() {
+            return mNextIndex < size();
+        }
+
+        @Override
+        public Integer next() {
+            return get(mNextIndex++);
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/util/IntSet.java b/src/com/android/launcher3/util/IntSet.java
index 851f129..0f4df62 100644
--- a/src/com/android/launcher3/util/IntSet.java
+++ b/src/com/android/launcher3/util/IntSet.java
@@ -16,11 +16,13 @@
 package com.android.launcher3.util;
 
 import java.util.Arrays;
+import java.util.Iterator;
 
 /**
  * A wrapper over IntArray implementing a growing set of int primitives.
+ * The elements in the array are sorted in ascending order.
  */
-public class IntSet {
+public class IntSet implements Iterable<Integer> {
 
     final IntArray mArray = new IntArray();
 
@@ -34,6 +36,16 @@
         }
     }
 
+    /**
+     * Removes the specified value from the set if it exist.
+     */
+    public void remove(int value) {
+        int index = Arrays.binarySearch(mArray.mValues, 0, mArray.mSize, value);
+        if (index >= 0) {
+            mArray.removeIndex(index);
+        }
+    }
+
     public boolean contains(int value) {
         return Arrays.binarySearch(mArray.mValues, 0, mArray.mSize, value) >= 0;
     }
@@ -61,6 +73,9 @@
         return (obj instanceof IntSet) && ((IntSet) obj).mArray.equals(mArray);
     }
 
+    /**
+     * Returns the wrapped IntArray. The elements in the array are sorted in ascending order.
+     */
     public IntArray getArray() {
         return mArray;
     }
@@ -78,4 +93,21 @@
         Arrays.sort(set.mArray.mValues, 0, set.mArray.mSize);
         return set;
     }
+
+    /**
+     * Returns an IntSet with the given values.
+     */
+    public static IntSet wrap(int... array) {
+        return wrap(IntArray.wrap(array));
+    }
+
+    @Override
+    public Iterator<Integer> iterator() {
+        return mArray.iterator();
+    }
+
+    @Override
+    public String toString() {
+        return "IntSet{" + mArray.toConcatString() + '}';
+    }
 }
diff --git a/src/com/android/launcher3/util/MultiValueAlpha.java b/src/com/android/launcher3/util/MultiValueAlpha.java
index 5be9529..8591872 100644
--- a/src/com/android/launcher3/util/MultiValueAlpha.java
+++ b/src/com/android/launcher3/util/MultiValueAlpha.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.util;
 
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
 import android.util.FloatProperty;
 import android.view.View;
 
@@ -121,5 +123,12 @@
         public String toString() {
             return Float.toString(mValue);
         }
+
+        /**
+         * Creates and returns an Animator from the current value to the given value.
+         */
+        public Animator animateToValue(float value) {
+            return ObjectAnimator.ofFloat(this, VALUE, value);
+        }
     }
 }
diff --git a/src/com/android/launcher3/util/ViewOnDrawExecutor.java b/src/com/android/launcher3/util/ViewOnDrawExecutor.java
index 82e24c2..5d90291 100644
--- a/src/com/android/launcher3/util/ViewOnDrawExecutor.java
+++ b/src/com/android/launcher3/util/ViewOnDrawExecutor.java
@@ -16,28 +16,21 @@
 
 package com.android.launcher3.util;
 
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-
-import android.os.Process;
 import android.view.View;
 import android.view.View.OnAttachStateChangeListener;
 import android.view.ViewTreeObserver.OnDrawListener;
 
-import androidx.annotation.VisibleForTesting;
-
 import com.android.launcher3.Launcher;
 
-import java.util.ArrayList;
-import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 /**
  * An executor which runs all the tasks after the first onDraw is called on the target view.
  */
-public class ViewOnDrawExecutor implements Executor, OnDrawListener, Runnable,
+public class ViewOnDrawExecutor implements OnDrawListener, Runnable,
         OnAttachStateChangeListener {
 
-    private final ArrayList<Runnable> mTasks = new ArrayList<>();
+    private final RunnableList mTasks;
 
     private Consumer<ViewOnDrawExecutor> mOnClearCallback;
     private View mAttachedView;
@@ -46,22 +39,16 @@
     private boolean mLoadAnimationCompleted;
     private boolean mFirstDrawCompleted;
 
-    public void attachTo(Launcher launcher) {
-        attachTo(launcher.getWorkspace(), true /* waitForLoadAnimation */,
-                launcher::clearPendingExecutor);
+    private boolean mCancelled;
+
+    public ViewOnDrawExecutor(RunnableList tasks) {
+        mTasks = tasks;
     }
 
-    /**
-     * Attached the executor to the existence of the view
-     */
-    public void attachTo(View attachedView, boolean waitForLoadAnimation,
-            Consumer<ViewOnDrawExecutor> onClearCallback) {
-        mOnClearCallback = onClearCallback;
-        mAttachedView = attachedView;
+    public void attachTo(Launcher launcher) {
+        mOnClearCallback = launcher::clearPendingExecutor;
+        mAttachedView = launcher.getWorkspace();
         mAttachedView.addOnAttachStateChangeListener(this);
-        if (!waitForLoadAnimation) {
-            mLoadAnimationCompleted = true;
-        }
 
         if (mAttachedView.isAttachedToWindow()) {
             attachObserver();
@@ -75,12 +62,6 @@
     }
 
     @Override
-    public void execute(Runnable command) {
-        mTasks.add(command);
-        MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
-    }
-
-    @Override
     public void onViewAttachedToWindow(View v) {
         attachObserver();
     }
@@ -105,12 +86,17 @@
     public void run() {
         // Post the pending tasks after both onDraw and onLoadAnimationCompleted have been called.
         if (mLoadAnimationCompleted && mFirstDrawCompleted && !mCompleted) {
-            runAllTasks();
+            markCompleted();
         }
     }
 
+    /**
+     * Executes all tasks immediately
+     */
     public void markCompleted() {
-        mTasks.clear();
+        if (!mCancelled) {
+            mTasks.executeAllAndDestroy();
+        }
         mCompleted = true;
         if (mAttachedView != null) {
             mAttachedView.getViewTreeObserver().removeOnDrawListener(this);
@@ -119,21 +105,10 @@
         if (mOnClearCallback != null) {
             mOnClearCallback.accept(this);
         }
-        MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
     }
 
-    protected boolean isCompleted() {
-        return mCompleted;
-    }
-
-    /**
-     * Executes all tasks immediately
-     */
-    @VisibleForTesting
-    public void runAllTasks() {
-        for (final Runnable r : mTasks) {
-            r.run();
-        }
+    public void cancel() {
+        mCancelled = true;
         markCompleted();
     }
 }
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index 646b669..b95904e 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -21,9 +21,12 @@
 import android.view.LayoutInflater;
 import android.view.View.AccessibilityDelegate;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.dot.DotInfo;
 import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.util.ViewCache;
 
@@ -100,6 +103,13 @@
     }
 
     /**
+     * Returns the FolderIcon with the given item id, if it exists.
+     */
+    default @Nullable FolderIcon findFolderIcon(final int folderIconId) {
+        return null;
+    }
+
+    /**
      * Returns the ActivityContext associated with the given Context.
      */
     static <T extends Context & ActivityContext> T lookupContext(Context context) {
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index 01c0b56..76dfb3c 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -430,18 +430,20 @@
     }
 
     public void getViewRectRelativeToSelf(View v, Rect r) {
+        int[] loc = getViewLocationRelativeToSelf(v);
+        r.set(loc[0], loc[1], loc[0] + v.getMeasuredWidth(), loc[1] + v.getMeasuredHeight());
+    }
+
+    protected int[] getViewLocationRelativeToSelf(View v) {
         int[] loc = new int[2];
         getLocationInWindow(loc);
         int x = loc[0];
         int y = loc[1];
 
         v.getLocationInWindow(loc);
-        int vX = loc[0];
-        int vY = loc[1];
-
-        int left = vX - x;
-        int top = vY - y;
-        r.set(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight());
+        loc[0] -= x;
+        loc[1] -= y;
+        return loc;
     }
 
     @Override
diff --git a/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java b/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java
index 1cc7f53..b92c476 100644
--- a/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java
@@ -18,6 +18,7 @@
 
 import static com.android.launcher3.Utilities.ATLEAST_R;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.launcher3.widget.BaseWidgetSheet.MAX_WIDTH_SCALE_FOR_LARGER_SCREEN;
 
 import android.animation.PropertyValuesHolder;
 import android.annotation.SuppressLint;
@@ -25,10 +26,12 @@
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewParent;
 import android.view.WindowInsets;
+import android.widget.ScrollView;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
@@ -44,6 +47,7 @@
     private static final int DEFAULT_CLOSE_DURATION = 200;
 
     private final Rect mInsets;
+    private ScrollView mWidgetPreviewScrollView;
 
     public AddItemWidgetsBottomSheet(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
@@ -68,6 +72,19 @@
     }
 
     @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            mNoIntercept = false;
+            // Suppress drag to dismiss gesture if the scroll view is being scrolled.
+            if (getPopupContainer().isEventOverView(mWidgetPreviewScrollView, ev)
+                    && mWidgetPreviewScrollView.getScrollY() > 0) {
+                mNoIntercept = true;
+            }
+        }
+        return super.onControllerInterceptTouchEvent(ev);
+    }
+
+    @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         int width = r - l;
         int height = b - t;
@@ -93,6 +110,15 @@
                     2 * (mInsets.left + mInsets.right));
         }
 
+        if (deviceProfile.isTablet || deviceProfile.isTwoPanels) {
+            // In large screen devices, we restrict the width of the widgets picker to show part of
+            // the home screen. Let's ensure the minimum width used is at least the minimum width
+            // that isn't taken by the widgets picker.
+            int minUsedWidth = (int) (deviceProfile.availableWidthPx
+                    * (1 - MAX_WIDTH_SCALE_FOR_LARGER_SCREEN));
+            widthUsed = Math.max(widthUsed, minUsedWidth);
+        }
+
         int heightUsed = mInsets.top + deviceProfile.edgeMarginPx;
         measureChildWithMargins(mContent, widthMeasureSpec,
                 widthUsed, heightMeasureSpec, heightUsed);
@@ -104,6 +130,7 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mContent = findViewById(R.id.add_item_bottom_sheet_content);
+        mWidgetPreviewScrollView = findViewById(R.id.widget_preview_scroll_view);
     }
 
     private void animateOpen() {
diff --git a/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java
new file mode 100644
index 0000000..2742882
--- /dev/null
+++ b/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget;
+
+import android.content.Context;
+import android.graphics.Outline;
+import android.graphics.Rect;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+import android.widget.RemoteViews;
+
+import androidx.annotation.UiThread;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.Executors;
+
+/**
+ * Launcher AppWidgetHostView with support for rounded corners and a fallback View.
+ */
+public abstract class BaseLauncherAppWidgetHostView extends NavigableAppWidgetHostView {
+
+    protected final LayoutInflater mInflater;
+
+    private final Rect mEnforcedRectangle = new Rect();
+    private final float mEnforcedCornerRadius;
+    private final ViewOutlineProvider mCornerRadiusEnforcementOutline = new ViewOutlineProvider() {
+        @Override
+        public void getOutline(View view, Outline outline) {
+            if (mEnforcedRectangle.isEmpty() || mEnforcedCornerRadius <= 0) {
+                outline.setEmpty();
+            } else {
+                outline.setRoundRect(mEnforcedRectangle, mEnforcedCornerRadius);
+            }
+        }
+    };
+
+    public BaseLauncherAppWidgetHostView(Context context) {
+        super(context);
+
+        setExecutor(Executors.THREAD_POOL_EXECUTOR);
+
+        mInflater = LayoutInflater.from(context);
+        mEnforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(getContext());
+    }
+
+    @Override
+    protected View getErrorView() {
+        return mInflater.inflate(R.layout.appwidget_error, this, false);
+    }
+
+    /**
+     * Fall back to error layout instead of showing widget.
+     */
+    public void switchToErrorView() {
+        // Update the widget with 0 Layout id, to reset the view to error view.
+        updateAppWidget(new RemoteViews(getAppWidgetInfo().provider.getPackageName(), 0));
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        try {
+            super.onLayout(changed, left, top, right, bottom);
+        } catch (final RuntimeException e) {
+            post(this::switchToErrorView);
+        }
+
+        enforceRoundedCorners();
+    }
+
+    @UiThread
+    private void resetRoundedCorners() {
+        setOutlineProvider(ViewOutlineProvider.BACKGROUND);
+        setClipToOutline(false);
+    }
+
+    @UiThread
+    private void enforceRoundedCorners() {
+        if (mEnforcedCornerRadius <= 0 || !RoundedCornerEnforcement.isRoundedCornerEnabled()) {
+            resetRoundedCorners();
+            return;
+        }
+        View background = RoundedCornerEnforcement.findBackground(this);
+        if (background == null
+                || RoundedCornerEnforcement.hasAppWidgetOptedOut(this, background)) {
+            resetRoundedCorners();
+            return;
+        }
+        RoundedCornerEnforcement.computeRoundedRectangle(this,
+                background,
+                mEnforcedRectangle);
+        setOutlineProvider(mCornerRadiusEnforcementOutline);
+        setClipToOutline(true);
+    }
+
+    /** Returns the corner radius currently enforced, in pixels. */
+    public float getEnforcedCornerRadius() {
+        return mEnforcedCornerRadius;
+    }
+
+    /** Returns true if the corner radius are enforced for this App Widget. */
+    public boolean hasEnforcedCornerRadius() {
+        return getClipToOutline();
+    }
+}
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 9f0b9d9..adc7ba0 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -51,6 +51,13 @@
 public abstract class BaseWidgetSheet extends AbstractSlideInView<Launcher>
         implements OnClickListener, OnLongClickListener, DragSource,
         PopupDataProvider.PopupDataChangeListener, Insettable {
+    /** The default number of cells that can fit horizontally in a widget sheet. */
+    protected static final int DEFAULT_MAX_HORIZONTAL_SPANS = 4;
+    /**
+     * The maximum scale, [0, 1], of the device screen width that the widgets picker can consume
+     * on large screen devices.
+     */
+    protected static final float MAX_WIDTH_SCALE_FOR_LARGER_SCREEN = 0.89f;
 
     protected static final String KEY_WIDGETS_EDUCATION_TIP_SEEN =
             "launcher.widgets_education_tip_seen";
@@ -131,6 +138,15 @@
                     2 * (mInsets.left + mInsets.right));
         }
 
+        if (deviceProfile.isTablet || deviceProfile.isTwoPanels) {
+            // In large screen devices, we restrict the width of the widgets picker to show part of
+            // the home screen. Let's ensure the minimum width used is at least the minimum width
+            // that isn't taken by the widgets picker.
+            int minUsedWidth = (int) (deviceProfile.availableWidthPx
+                    * (1 - MAX_WIDTH_SCALE_FOR_LARGER_SCREEN));
+            widthUsed = Math.max(widthUsed, minUsedWidth);
+        }
+
         int heightUsed = mInsets.top + deviceProfile.edgeMarginPx;
         measureChildWithMargins(mContent, widthMeasureSpec,
                 widthUsed, heightMeasureSpec, heightUsed);
@@ -138,6 +154,17 @@
                 MeasureSpec.getSize(heightMeasureSpec));
     }
 
+    /** Returns the number of cells that can fit horizontally in a given {@code content}. */
+    protected int computeMaxHorizontalSpans(View content, int contentHorizontalPaddingPx) {
+        DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
+        int availableWidth = content.getMeasuredWidth() - contentHorizontalPaddingPx;
+        Point cellSize = deviceProfile.getCellSize();
+        if (cellSize.x > 0) {
+            return availableWidth / cellSize.x;
+        }
+        return DEFAULT_MAX_HORIZONTAL_SPANS;
+    }
+
     private boolean beginDraggingWidget(WidgetCell v) {
         // Get the widget preview as the drag representation
         WidgetImageView image = v.getWidgetView();
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 63bc416..fb6de9f 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -20,19 +20,16 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Canvas;
-import android.graphics.Outline;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Handler;
 import android.os.SystemClock;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
-import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
-import android.view.ViewOutlineProvider;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.AdapterView;
 import android.widget.Advanceable;
@@ -40,7 +37,6 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
 
 import com.android.launcher3.CheckLongPressHelper;
 import com.android.launcher3.Launcher;
@@ -51,7 +47,6 @@
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
-import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.BaseDragLayer.TouchCompleteListener;
 import com.android.launcher3.widget.dragndrop.AppWidgetHostViewDragListener;
@@ -61,7 +56,7 @@
 /**
  * {@inheritDoc}
  */
-public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
+public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView
         implements TouchCompleteListener, View.OnLongClickListener,
         LocalColorExtractor.Listener {
 
@@ -76,8 +71,6 @@
     // Maximum duration for which updates can be deferred.
     private static final long UPDATE_LOCK_TIMEOUT_MILLIS = 1000;
 
-    protected final LayoutInflater mInflater;
-
     private final CheckLongPressHelper mLongPressHelper;
     protected final Launcher mLauncher;
     private final Workspace mWorkspace;
@@ -101,18 +94,6 @@
     private final Rect mWidgetSizeAtDrag = new Rect();
 
     private final RectF mTempRectF = new RectF();
-    private final Rect mEnforcedRectangle = new Rect();
-    private final float mEnforcedCornerRadius;
-    private final ViewOutlineProvider mCornerRadiusEnforcementOutline = new ViewOutlineProvider() {
-        @Override
-        public void getOutline(View view, Outline outline) {
-            if (mEnforcedRectangle.isEmpty() || mEnforcedCornerRadius <= 0) {
-                outline.setEmpty();
-            } else {
-                outline.setRoundRect(mEnforcedRectangle, mEnforcedCornerRadius);
-            }
-        }
-    };
     private final Object mUpdateLock = new Object();
     private final ViewGroupFocusHelper mDragLayerRelativeCoordinateHelper;
     private long mDeferUpdatesUntilMillis = 0;
@@ -126,18 +107,15 @@
         mLauncher = Launcher.getLauncher(context);
         mWorkspace = mLauncher.getWorkspace();
         mLongPressHelper = new CheckLongPressHelper(this, this);
-        mInflater = LayoutInflater.from(context);
         setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
         setBackgroundResource(R.drawable.widget_internal_focus_bg);
 
-        setExecutor(Executors.THREAD_POOL_EXECUTOR);
         if (Utilities.ATLEAST_Q && Themes.getAttrBoolean(mLauncher, R.attr.isWorkspaceDarkText)) {
             setOnLightBackground(true);
         }
         mColorExtractor = LocalColorExtractor.newInstance(getContext());
         mColorExtractor.setListener(this);
 
-        mEnforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(getContext());
         mDragLayerRelativeCoordinateHelper = new ViewGroupFocusHelper(mLauncher.getDragLayer());
     }
 
@@ -169,11 +147,6 @@
     }
 
     @Override
-    protected View getErrorView() {
-        return mInflater.inflate(R.layout.appwidget_error, this, false);
-    }
-
-    @Override
     public void updateAppWidget(RemoteViews remoteViews) {
         synchronized (mUpdateLock) {
             if (isDeferringUpdates()) {
@@ -329,28 +302,12 @@
         }
     }
 
-    public void switchToErrorView() {
-        // Update the widget with 0 Layout id, to reset the view to error view.
-        updateAppWidget(new RemoteViews(getAppWidgetInfo().provider.getPackageName(), 0));
-    }
-
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        try {
-            super.onLayout(changed, left, top, right, bottom);
-        } catch (final RuntimeException e) {
-            post(new Runnable() {
-                @Override
-                public void run() {
-                    switchToErrorView();
-                }
-            });
-        }
+        super.onLayout(changed, left, top, right, bottom);
 
         mIsScrollable = checkScrollableRecursively(this);
         updateColorExtraction();
-
-        enforceRoundedCorners();
     }
 
     /** Starts the drag mode. */
@@ -564,40 +521,4 @@
         }
         return false;
     }
-
-    @UiThread
-    private void resetRoundedCorners() {
-        setOutlineProvider(ViewOutlineProvider.BACKGROUND);
-        setClipToOutline(false);
-    }
-
-    @UiThread
-    private void enforceRoundedCorners() {
-        if (mEnforcedCornerRadius <= 0 || !RoundedCornerEnforcement.isRoundedCornerEnabled()) {
-            resetRoundedCorners();
-            return;
-        }
-        View background = RoundedCornerEnforcement.findBackground(this);
-        if (background == null
-                || RoundedCornerEnforcement.hasAppWidgetOptedOut(this, background)) {
-            resetRoundedCorners();
-            return;
-        }
-        RoundedCornerEnforcement.computeRoundedRectangle(this,
-                background,
-                mEnforcedRectangle);
-        setOutlineProvider(mCornerRadiusEnforcementOutline);
-        setClipToOutline(true);
-    }
-
-    /** Returns the corner radius currently enforced, in pixels. */
-    public float getEnforcedCornerRadius() {
-        return mEnforcedCornerRadius;
-    }
-
-    /** Returns true if the corner radius are enforced for this App Widget. */
-    public boolean hasEnforcedCornerRadius() {
-        return getClipToOutline();
-    }
-
 }
diff --git a/src/com/android/launcher3/widget/LocalColorExtractor.java b/src/com/android/launcher3/widget/LocalColorExtractor.java
index 8ae6b2e..23d9e15 100644
--- a/src/com/android/launcher3/widget/LocalColorExtractor.java
+++ b/src/com/android/launcher3/widget/LocalColorExtractor.java
@@ -76,6 +76,14 @@
     public void applyColorsOverride(Context base, WallpaperColors colors) { }
 
     /**
+     * Generates color resource overrides from {@link WallpaperColors}.
+     */
+    @Nullable
+    public SparseIntArray generateColorsOverride(WallpaperColors colors) {
+        return null;
+    }
+
+    /**
      * Takes a view and returns its rect that can be used by the wallpaper local color extractor.
      *
      * @param launcher Launcher class class.
diff --git a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
index 6163b51..d12fe74 100644
--- a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
@@ -26,7 +26,6 @@
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 
-import com.android.launcher3.BaseActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Reorderable;
 import com.android.launcher3.dragndrop.DraggableView;
@@ -59,7 +58,7 @@
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mChildrenFocused;
 
-    protected final BaseActivity mActivity;
+    protected final ActivityContext mActivity;
 
     public NavigableAppWidgetHostView(Context context) {
         super(context);
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index dedcc65..406de10 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -70,9 +70,11 @@
     private static final int DEFAULT_CLOSE_DURATION = 200;
     private static final long EDUCATION_TIP_DELAY_MS = 300;
 
+    private final int mWidgetSheetContentHorizontalPadding;
+
     private ItemInfo mOriginalItemInfo;
     private final int mMaxTableHeight;
-    private int mMaxHorizontalSpan = 4;
+    private int mMaxHorizontalSpan = DEFAULT_MAX_HORIZONTAL_SPANS;
 
     private final OnLayoutChangeListener mLayoutChangeListenerToShowTips =
             new OnLayoutChangeListener() {
@@ -117,6 +119,9 @@
         if (!hasSeenEducationTip()) {
             addOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
         }
+
+        mWidgetSheetContentHorizontalPadding = getResources().getDimensionPixelSize(
+                R.dimen.widget_list_horizontal_margin);
     }
 
     @Override
@@ -137,10 +142,8 @@
     private boolean updateMaxSpansPerRow() {
         if (getMeasuredWidth() == 0) return false;
 
-        int paddingPx = 2 * getResources().getDimensionPixelOffset(
-                R.dimen.widget_cell_horizontal_padding);
-        int maxHorizontalSpan = findViewById(R.id.widgets_table).getMeasuredWidth()
-                / (mActivityContext.getDeviceProfile().cellWidthPx + paddingPx);
+        int maxHorizontalSpan = computeMaxHorizontalSpans(mContent,
+                mWidgetSheetContentHorizontalPadding);
         if (mMaxHorizontalSpan != maxHorizontalSpan) {
             // Ensure the table layout is showing widgets in the right column after measure.
             mMaxHorizontalSpan = maxHorizontalSpan;
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 6c2cca6..5e1a534 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -25,6 +25,7 @@
 import android.content.Context;
 import android.content.pm.LauncherApps;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.graphics.Rect;
 import android.os.Process;
 import android.os.UserHandle;
@@ -148,13 +149,13 @@
     private final int mTabsHeight;
     private final int mViewPagerTopPadding;
     private final int mSearchAndRecommendationContainerBottomMargin;
-    private final int mWidgetCellHorizontalPadding;
+    private final int mWidgetSheetContentHorizontalPadding;
 
     @Nullable private WidgetsRecyclerView mCurrentWidgetsRecyclerView;
     @Nullable private PersonalWorkPagedView mViewPager;
     private boolean mIsInSearchMode;
     private boolean mIsNoWidgetsViewNeeded;
-    private int mMaxSpansPerRow = 4;
+    private int mMaxSpansPerRow = DEFAULT_MAX_HORIZONTAL_SPANS;
     private View mTabsView;
     private TextView mNoWidgetsView;
     private SearchAndRecommendationViewHolder mSearchAndRecommendationViewHolder;
@@ -166,19 +167,20 @@
         mAdapters.put(AdapterHolder.PRIMARY, new AdapterHolder(AdapterHolder.PRIMARY));
         mAdapters.put(AdapterHolder.WORK, new AdapterHolder(AdapterHolder.WORK));
         mAdapters.put(AdapterHolder.SEARCH, new AdapterHolder(AdapterHolder.SEARCH));
+
+        Resources resources = getResources();
         mTabsHeight = mHasWorkProfile
-                ? getContext().getResources()
-                        .getDimensionPixelSize(R.dimen.all_apps_header_pill_height)
+                ? resources.getDimensionPixelSize(R.dimen.all_apps_header_pill_height)
                 : 0;
         mViewPagerTopPadding = mHasWorkProfile
                 ? getContext().getResources()
                     .getDimensionPixelSize(R.dimen.widget_picker_view_pager_top_padding)
                 : 0;
-        mSearchAndRecommendationContainerBottomMargin = getContext().getResources()
-                .getDimensionPixelSize(mHasWorkProfile
+        mSearchAndRecommendationContainerBottomMargin = resources.getDimensionPixelSize(
+                mHasWorkProfile
                         ? R.dimen.search_and_recommended_widgets_container_small_bottom_margin
                         : R.dimen.search_and_recommended_widgets_container_bottom_margin);
-        mWidgetCellHorizontalPadding = 2 * getResources().getDimensionPixelOffset(
+        mWidgetSheetContentHorizontalPadding = 2 * resources.getDimensionPixelSize(
                 R.dimen.widget_cell_horizontal_padding);
     }
 
@@ -375,11 +377,10 @@
     private boolean updateMaxSpansPerRow() {
         if (getMeasuredWidth() == 0) return false;
 
-        int previousMaxSpansPerRow = mMaxSpansPerRow;
-        mMaxSpansPerRow = getMeasuredWidth()
-                / (mActivityContext.getDeviceProfile().cellWidthPx + mWidgetCellHorizontalPadding);
-
-        if (previousMaxSpansPerRow != mMaxSpansPerRow) {
+        int maxHorizontalSpans = computeMaxHorizontalSpans(mContent,
+                mWidgetSheetContentHorizontalPadding);
+        if (mMaxSpansPerRow != maxHorizontalSpans) {
+            mMaxSpansPerRow = maxHorizontalSpans;
             mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
                     mMaxSpansPerRow);
             mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
index 4407fe1..c606861 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.uioverrides;
 
 import android.app.Person;
+import android.content.Context;
 import android.content.pm.ShortcutInfo;
 import android.view.Display;
 
@@ -36,4 +37,11 @@
     public static boolean isInternalDisplay(Display display) {
         return display.getDisplayId() == Display.DEFAULT_DISPLAY;
     }
+
+    /**
+     * Returns the minimum space that should be left empty at the start of hotseat
+     */
+    public static int getHotseatStartOffset(Context context) {
+        return 0;
+    }
 }
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index bf4eba0..5e57df6 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -128,6 +128,9 @@
     }
 
     public static void checkDetectedLeaks(LauncherInstrumentation launcher) {
+        // TODO(b/191449914): Temporarily disable leak detection on tablets until bug is resolved.
+        if (launcher.isTablet()) return;
+
         if (sActivityLeakReported) return;
 
         if (sStrictmodeDetectedActivityLeak != null) {
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 4dd44f4..b36607a 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -80,8 +80,12 @@
         assertTrue(message, failed);
     }
 
+    private int pagesPerScreen() {
+        return mLauncher.isTablet() ? 2 : 1;
+    }
+
     private boolean isWorkspaceScrollable(Launcher launcher) {
-        return launcher.getWorkspace().getPageCount() > 1;
+        return launcher.getWorkspace().getPageCount() > pagesPerScreen();
     }
 
     private int getCurrentWorkspacePage(Launcher launcher) {
@@ -195,8 +199,9 @@
         workspace.ensureWorkspaceIsScrollable();
 
         executeOnLauncher(
-                launcher -> assertEquals("Ensuring workspace scrollable didn't switch to page #1",
-                        1, getCurrentWorkspacePage(launcher)));
+                launcher -> assertEquals(
+                        "Ensuring workspace scrollable didn't switch to next screen",
+                        pagesPerScreen(), getCurrentWorkspacePage(launcher)));
         executeOnLauncher(
                 launcher -> assertTrue("ensureScrollable didn't make workspace scrollable",
                         isWorkspaceScrollable(launcher)));
@@ -212,8 +217,8 @@
 
         workspace.flingForward();
         executeOnLauncher(
-                launcher -> assertEquals("Flinging forward didn't switch workspace to page #1",
-                        1, getCurrentWorkspacePage(launcher)));
+                launcher -> assertEquals("Flinging forward didn't switch workspace to next screen",
+                        pagesPerScreen(), getCurrentWorkspacePage(launcher)));
         assertTrue("Launcher internal state is not Home", isInState(() -> LauncherState.NORMAL));
 
         // Test starting a workspace app.
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index 4b1610e..75b87b7 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -68,7 +68,7 @@
     }
 
     protected boolean zeroButtonToOverviewGestureStartsInLauncher() {
-        return false;
+        return mLauncher.isTablet();
     }
 
     protected void goToOverviewUnchecked() {
@@ -134,9 +134,15 @@
             }
 
             case THREE_BUTTON:
+                if (mLauncher.isTablet()) {
+                    mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
+                            LauncherInstrumentation.EVENT_TOUCH_DOWN);
+                    mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
+                            LauncherInstrumentation.EVENT_TOUCH_UP);
+                }
                 mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
                 mLauncher.runToState(
-                        () -> mLauncher.waitForSystemUiObject("recent_apps").click(),
+                        () -> mLauncher.waitForNavigationUiObject("recent_apps").click(),
                         OVERVIEW_STATE_ORDINAL, "clicking Recents button");
                 break;
         }
@@ -224,11 +230,23 @@
 
                 case THREE_BUTTON:
                     // Double press the recents button.
-                    UiObject2 recentsButton = mLauncher.waitForSystemUiObject("recent_apps");
+                    UiObject2 recentsButton = mLauncher.waitForNavigationUiObject("recent_apps");
+                    if (mLauncher.isTablet()) {
+                        mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
+                                LauncherInstrumentation.EVENT_TOUCH_DOWN);
+                        mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
+                                LauncherInstrumentation.EVENT_TOUCH_UP);
+                    }
                     mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
                     mLauncher.runToState(() -> recentsButton.click(), OVERVIEW_STATE_ORDINAL,
                             "clicking Recents button for the first time");
                     mLauncher.getOverview();
+                    if (mLauncher.isTablet()) {
+                        mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
+                                LauncherInstrumentation.EVENT_TOUCH_DOWN);
+                        mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
+                                LauncherInstrumentation.EVENT_TOUCH_UP);
+                    }
                     mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
                     mLauncher.executeAndWaitForEvent(
                             () -> recentsButton.click(),
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 19094f8..9d6cb4e 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -97,8 +97,8 @@
     private static final int ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME = 20;
     private static final int GESTURE_STEP_MS = 16;
 
-    private static final Pattern EVENT_TOUCH_DOWN = getTouchEventPattern("ACTION_DOWN");
-    private static final Pattern EVENT_TOUCH_UP = getTouchEventPattern("ACTION_UP");
+    static final Pattern EVENT_TOUCH_DOWN = getTouchEventPattern("ACTION_DOWN");
+    static final Pattern EVENT_TOUCH_UP = getTouchEventPattern("ACTION_UP");
     private static final Pattern EVENT_TOUCH_CANCEL = getTouchEventPattern("ACTION_CANCEL");
     private static final Pattern EVENT_PILFER_POINTERS = Pattern.compile("pilferPointers");
     static final Pattern EVENT_START = Pattern.compile("start:");
@@ -283,6 +283,11 @@
                 .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
+    public boolean isTablet() {
+        return getTestInfo(TestProtocol.REQUEST_IS_TABLET)
+                .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+    }
+
     void setActiveContainer(VisibleContainer container) {
         sActiveContainer = new WeakReference<>(container);
     }
@@ -554,29 +559,35 @@
     public String getNavigationModeMismatchError(boolean waitForCorrectState) {
         final int waitTime = waitForCorrectState ? WAIT_TIME_MS : 0;
         final NavigationModel navigationModel = getNavigationModel();
-
+        String resPackage = getNavigationButtonResPackage();
         if (navigationModel == NavigationModel.THREE_BUTTON) {
-            if (!mDevice.wait(Until.hasObject(By.res(SYSTEMUI_PACKAGE, "recent_apps")), waitTime)) {
+            if (!mDevice.wait(Until.hasObject(By.res(resPackage, "recent_apps")), waitTime)) {
                 return "Recents button not present in 3-button mode";
             }
         } else {
-            if (!mDevice.wait(Until.gone(By.res(SYSTEMUI_PACKAGE, "recent_apps")), waitTime)) {
+            if (!mDevice.wait(Until.gone(By.res(resPackage, "recent_apps")), waitTime)) {
                 return "Recents button is present in non-3-button mode";
             }
         }
 
         if (navigationModel == NavigationModel.ZERO_BUTTON) {
-            if (!mDevice.wait(Until.gone(By.res(SYSTEMUI_PACKAGE, "home")), waitTime)) {
+            if (!mDevice.wait(Until.gone(By.res(resPackage, "home")), waitTime)) {
                 return "Home button is present in gestural mode";
             }
         } else {
-            if (!mDevice.wait(Until.hasObject(By.res(SYSTEMUI_PACKAGE, "home")), waitTime)) {
+            if (!mDevice.wait(Until.hasObject(By.res(resPackage, "home")), waitTime)) {
                 return "Home button not present in non-gestural mode";
             }
         }
         return null;
     }
 
+    private String getNavigationButtonResPackage() {
+        return isTablet() && getNavigationModel() == NavigationModel.THREE_BUTTON ?
+                getLauncherPackageName() :
+                SYSTEMUI_PACKAGE;
+    }
+
     private UiObject2 verifyContainerType(ContainerType containerType) {
         waitForLauncherInitialized();
 
@@ -740,9 +751,13 @@
                     expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_DOWN_TIS);
                     expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_UP_TIS);
                 }
+                if (isTablet()) {
+                    expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_DOWN);
+                    expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_UP);
+                }
 
                 runToState(
-                        waitForSystemUiObject("home")::click,
+                        waitForNavigationUiObject("home")::click,
                         NORMAL_STATE_ORDINAL,
                         !hasLauncherObject(WORKSPACE_RES_ID)
                                 && (hasLauncherObject(APPS_RES_ID)
@@ -892,6 +907,15 @@
         return object;
     }
 
+    @NonNull
+    UiObject2 waitForNavigationUiObject(String resId) {
+        String resPackage = getNavigationButtonResPackage();
+        final UiObject2 object = mDevice.wait(
+                Until.findObject(By.res(resPackage, resId)), WAIT_TIME_MS);
+        assertNotNull("Can't find a navigation UI object with id: " + resId, object);
+        return object;
+    }
+
     @Nullable
     UiObject2 findObjectInContainer(UiObject2 container, BySelector selector) {
         try {
@@ -930,7 +954,7 @@
     void waitForObjectEnabled(UiObject2 object, String waitReason) {
         try {
             assertTrue("Timed out waiting for object to be enabled for " + waitReason + " "
-                    + object.getResourceName(),
+                            + object.getResourceName(),
                     object.wait(Until.enabled(true), WAIT_TIME_MS));
         } catch (StaleObjectException e) {
             fail("The object disappeared from screen");
@@ -1049,7 +1073,7 @@
                 () -> "Failed to receive an event for the state change: expected ["
                         + TestProtocol.stateOrdinalToString(expectedState)
                         + "], actual: " + eventListToString(actualEvents),
-                        actionName);
+                actionName);
     }
 
     private boolean isSwitchToStateEvent(
@@ -1176,7 +1200,7 @@
                 event -> TestProtocol.SCROLL_FINISHED_MESSAGE.equals(event.getClassName()),
                 () -> "Didn't receive a scroll end message: " + startX + ", " + startY
                         + ", " + endX + ", " + endY,
-                        "scrolling");
+                "scrolling");
     }
 
     // Inject a swipe gesture. Inject exactly 'steps' motion points, incrementing event time by a
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 43134d9..73bd2c7 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -63,7 +63,7 @@
     /**
      * Swipes up to All Apps.
      *
-     * @return the App Apps object.
+     * @return the All Apps object.
      */
     @NonNull
     public AllApps switchToAllApps() {
@@ -163,7 +163,7 @@
     }
 
     private boolean isWorkspaceScrollable(UiObject2 workspace) {
-        return workspace.getChildCount() > 1;
+        return workspace.getChildCount() > (mLauncher.isTablet() ? 2 : 1);
     }
 
     @NonNull