Merge "Avoid duplicate accessibility announcement upon switching to -1st page" into ub-launcher3-master
diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto
index 3b983d2..4013429 100644
--- a/protos/launcher_log.proto
+++ b/protos/launcher_log.proto
@@ -69,6 +69,7 @@
   EDITTEXT = 7;
   NOTIFICATION = 8;
   TASK = 9;         // Each page of Recents UI (QuickStep)
+  WEB_APP = 10;
 }
 
 // Used to define what type of container a Target would represent.
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
index e414fa0..b75a193 100644
--- a/quickstep/libs/sysui_shared.jar
+++ b/quickstep/libs/sysui_shared.jar
Binary files differ
diff --git a/quickstep/res/values-af/strings.xml b/quickstep/res/values-af/strings.xml
index 6196894..fa8f9dc 100644
--- a/quickstep/res/values-af/strings.xml
+++ b/quickstep/res/values-af/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Swiep van onder af op om programme te wissel"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Oorsig"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Geen onlangse items nie"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-am/strings.xml b/quickstep/res/values-am/strings.xml
index 5a892ad..d14e06c 100644
--- a/quickstep/res/values-am/strings.xml
+++ b/quickstep/res/values-am/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"መተግበሪያዎችን ለመቀያየር ከግርጌ ወደ ላይ በጣት ጠረግ ያድርጉ"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"ማጠቃለያ"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"ምንም የቅርብ ጊዜ ንጥሎች የሉም"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-ar/strings.xml b/quickstep/res/values-ar/strings.xml
index 3d83fcb..8efffd2 100644
--- a/quickstep/res/values-ar/strings.xml
+++ b/quickstep/res/values-ar/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"التمرير سريعًا لأعلى من أسفل للتبديل بين التطبيقات"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"نظرة عامة"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"ليست هناك عناصر تم استخدامها مؤخرًا"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-az/strings.xml b/quickstep/res/values-az/strings.xml
index bede24b..a832f9a 100644
--- a/quickstep/res/values-az/strings.xml
+++ b/quickstep/res/values-az/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Tətbiqləri dəyişmək üçün aşağıdan yuxarı doğru sürüşdürün"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"İcmal"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Son elementlər yoxdur"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-b+sr+Latn/strings.xml b/quickstep/res/values-b+sr+Latn/strings.xml
index b2f8b7a..ba44830 100644
--- a/quickstep/res/values-b+sr+Latn/strings.xml
+++ b/quickstep/res/values-b+sr+Latn/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Prevucite nagore da biste prešli na drugu aplikaciju"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Pregled"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Nema nedavnih stavki"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-be/strings.xml b/quickstep/res/values-be/strings.xml
index b03f4a1..df55803 100644
--- a/quickstep/res/values-be/strings.xml
+++ b/quickstep/res/values-be/strings.xml
@@ -22,8 +22,8 @@
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"Падзяліць экран"</string>
     <string name="recent_task_option_pin" msgid="7929860679018978258">"Замацаваць"</string>
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Для пераключэння праграм правядзіце па экране пальцам знізу ўверх"</string>
-    <!-- no translation found for accessibility_desc_recent_apps (1444379410873162882) -->
-    <skip />
-    <!-- no translation found for recents_empty_message (7040467240571714191) -->
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Агляд"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"Няма новых элементаў"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
     <skip />
 </resources>
diff --git a/quickstep/res/values-bg/strings.xml b/quickstep/res/values-bg/strings.xml
index bbf73c0..c46245c 100644
--- a/quickstep/res/values-bg/strings.xml
+++ b/quickstep/res/values-bg/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Прекарайте пръст нагоре от долната част, за да превключите между приложенията"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Общ преглед"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Няма скорошни елементи"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-bn/strings.xml b/quickstep/res/values-bn/strings.xml
index 4c7e79b..9080072 100644
--- a/quickstep/res/values-bn/strings.xml
+++ b/quickstep/res/values-bn/strings.xml
@@ -26,4 +26,6 @@
     <skip />
     <!-- no translation found for recents_empty_message (7040467240571714191) -->
     <skip />
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-bs/strings.xml b/quickstep/res/values-bs/strings.xml
index a6bbc24..7e61277 100644
--- a/quickstep/res/values-bs/strings.xml
+++ b/quickstep/res/values-bs/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Prevucite od dolje prema gore za promjenu aplikacije"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Pregled"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Nema nedavnih stavki"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-ca/strings.xml b/quickstep/res/values-ca/strings.xml
index dc4aa93..ac77992 100644
--- a/quickstep/res/values-ca/strings.xml
+++ b/quickstep/res/values-ca/strings.xml
@@ -26,4 +26,6 @@
     <skip />
     <!-- no translation found for recents_empty_message (7040467240571714191) -->
     <skip />
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-cs/strings.xml b/quickstep/res/values-cs/strings.xml
index b6f99db..e8c0cb0 100644
--- a/quickstep/res/values-cs/strings.xml
+++ b/quickstep/res/values-cs/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Aplikace můžete přepínat přejetím zdola nahoru"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Přehled"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Žádné nedávné položky"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-da/strings.xml b/quickstep/res/values-da/strings.xml
index f69bea5..6ddb31b 100644
--- a/quickstep/res/values-da/strings.xml
+++ b/quickstep/res/values-da/strings.xml
@@ -22,8 +22,8 @@
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"Delt skærm"</string>
     <string name="recent_task_option_pin" msgid="7929860679018978258">"Fastgør"</string>
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Stryg opad fra bunden for at skifte apps"</string>
-    <!-- no translation found for accessibility_desc_recent_apps (1444379410873162882) -->
-    <skip />
-    <!-- no translation found for recents_empty_message (7040467240571714191) -->
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Oversigt"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"Ingen nye elementer"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
     <skip />
 </resources>
diff --git a/quickstep/res/values-de/strings.xml b/quickstep/res/values-de/strings.xml
index 6b9ffbb..01c785e 100644
--- a/quickstep/res/values-de/strings.xml
+++ b/quickstep/res/values-de/strings.xml
@@ -22,8 +22,8 @@
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"Bildschirm teilen"</string>
     <string name="recent_task_option_pin" msgid="7929860679018978258">"Fixieren"</string>
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Zum Wechseln zwischen Apps vom unteren Bildschirmrand nach oben wischen"</string>
-    <!-- no translation found for accessibility_desc_recent_apps (1444379410873162882) -->
-    <skip />
-    <!-- no translation found for recents_empty_message (7040467240571714191) -->
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Übersicht"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"Keine kürzlich verwendeten Elemente"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
     <skip />
 </resources>
diff --git a/quickstep/res/values-el/strings.xml b/quickstep/res/values-el/strings.xml
index bdee73b..6b2a25f 100644
--- a/quickstep/res/values-el/strings.xml
+++ b/quickstep/res/values-el/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Σύρετε από κάτω προς τα επάνω για εναλλαγή εφαρμογών"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Επισκόπηση"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Δεν υπάρχουν πρόσφατα στοιχεία"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-en-rAU/strings.xml b/quickstep/res/values-en-rAU/strings.xml
index 0a65f2f..402499e 100644
--- a/quickstep/res/values-en-rAU/strings.xml
+++ b/quickstep/res/values-en-rAU/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Swipe up from the bottom to switch apps"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Overview"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"No recent items"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-en-rGB/strings.xml b/quickstep/res/values-en-rGB/strings.xml
index 0a65f2f..402499e 100644
--- a/quickstep/res/values-en-rGB/strings.xml
+++ b/quickstep/res/values-en-rGB/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Swipe up from the bottom to switch apps"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Overview"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"No recent items"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-en-rIN/strings.xml b/quickstep/res/values-en-rIN/strings.xml
index 0a65f2f..402499e 100644
--- a/quickstep/res/values-en-rIN/strings.xml
+++ b/quickstep/res/values-en-rIN/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Swipe up from the bottom to switch apps"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Overview"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"No recent items"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-es-rUS/strings.xml b/quickstep/res/values-es-rUS/strings.xml
index 5cb541b..1b9f926 100644
--- a/quickstep/res/values-es-rUS/strings.xml
+++ b/quickstep/res/values-es-rUS/strings.xml
@@ -22,8 +22,8 @@
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"Pantalla dividida"</string>
     <string name="recent_task_option_pin" msgid="7929860679018978258">"Fijar"</string>
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Desliza el dedo hacia arriba para cambiar de app"</string>
-    <!-- no translation found for accessibility_desc_recent_apps (1444379410873162882) -->
-    <skip />
-    <!-- no translation found for recents_empty_message (7040467240571714191) -->
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Recientes"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"No hay elementos recientes"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
     <skip />
 </resources>
diff --git a/quickstep/res/values-es/strings.xml b/quickstep/res/values-es/strings.xml
index 1b526fd..c63f1d3 100644
--- a/quickstep/res/values-es/strings.xml
+++ b/quickstep/res/values-es/strings.xml
@@ -22,8 +22,8 @@
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"Dividir pantalla"</string>
     <string name="recent_task_option_pin" msgid="7929860679018978258">"Fijar"</string>
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Desliza el dedo hacia arriba desde la parte inferior para cambiar de aplicación"</string>
-    <!-- no translation found for accessibility_desc_recent_apps (1444379410873162882) -->
-    <skip />
-    <!-- no translation found for recents_empty_message (7040467240571714191) -->
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Aplicaciones recientes"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"No hay elementos recientes"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
     <skip />
 </resources>
diff --git a/quickstep/res/values-et/strings.xml b/quickstep/res/values-et/strings.xml
index 5fc1c31..30199b9 100644
--- a/quickstep/res/values-et/strings.xml
+++ b/quickstep/res/values-et/strings.xml
@@ -22,8 +22,8 @@
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"Jagatud ekraan"</string>
     <string name="recent_task_option_pin" msgid="7929860679018978258">"Kinnita"</string>
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Rakenduste vahetamiseks pühkige alaosast üles"</string>
-    <!-- no translation found for accessibility_desc_recent_apps (1444379410873162882) -->
-    <skip />
-    <!-- no translation found for recents_empty_message (7040467240571714191) -->
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Ülevaade"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"Hiljutisi üksusi pole"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
     <skip />
 </resources>
diff --git a/quickstep/res/values-eu/strings.xml b/quickstep/res/values-eu/strings.xml
index ed10439..b6386cd 100644
--- a/quickstep/res/values-eu/strings.xml
+++ b/quickstep/res/values-eu/strings.xml
@@ -22,8 +22,8 @@
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"Zatitu pantaila"</string>
     <string name="recent_task_option_pin" msgid="7929860679018978258">"Ainguratu"</string>
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Aplikazioak aldatzeko, pasatu hatza pantailako behealdetik gora"</string>
-    <!-- no translation found for accessibility_desc_recent_apps (1444379410873162882) -->
-    <skip />
-    <!-- no translation found for recents_empty_message (7040467240571714191) -->
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Ikuspegi orokorra"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"Ez dago azkenaldi honetako ezer"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
     <skip />
 </resources>
diff --git a/quickstep/res/values-fa/strings.xml b/quickstep/res/values-fa/strings.xml
index 75d2e7e..52beadd 100644
--- a/quickstep/res/values-fa/strings.xml
+++ b/quickstep/res/values-fa/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"برای تغییر برنامه‌ها،‌ از پایین تند به بالا بکشید"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"نمای کلی"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"بدون موارد اخیر"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-fi/strings.xml b/quickstep/res/values-fi/strings.xml
index 824d9b8..a27a9cb 100644
--- a/quickstep/res/values-fi/strings.xml
+++ b/quickstep/res/values-fi/strings.xml
@@ -22,8 +22,8 @@
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"Jaettu näyttö"</string>
     <string name="recent_task_option_pin" msgid="7929860679018978258">"Kiinnitä"</string>
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Vaihda sovellusta pyyhkäisemällä alareunasta ylös."</string>
-    <!-- no translation found for accessibility_desc_recent_apps (1444379410873162882) -->
-    <skip />
-    <!-- no translation found for recents_empty_message (7040467240571714191) -->
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Viimeisimmät"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"Ei viimeaikaisia kohteita"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
     <skip />
 </resources>
diff --git a/quickstep/res/values-fr-rCA/strings.xml b/quickstep/res/values-fr-rCA/strings.xml
index 94a09eb..8a603e9 100644
--- a/quickstep/res/values-fr-rCA/strings.xml
+++ b/quickstep/res/values-fr-rCA/strings.xml
@@ -22,8 +22,8 @@
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"Écran divisé"</string>
     <string name="recent_task_option_pin" msgid="7929860679018978258">"Épingler"</string>
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Balayez l\'écran du bas vers le haut pour changer d\'application"</string>
-    <!-- no translation found for accessibility_desc_recent_apps (1444379410873162882) -->
-    <skip />
-    <!-- no translation found for recents_empty_message (7040467240571714191) -->
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Aperçu"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"Aucun élément récent"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
     <skip />
 </resources>
diff --git a/quickstep/res/values-fr/strings.xml b/quickstep/res/values-fr/strings.xml
index 1c88e33..9192287 100644
--- a/quickstep/res/values-fr/strings.xml
+++ b/quickstep/res/values-fr/strings.xml
@@ -22,8 +22,8 @@
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"Écran partagé"</string>
     <string name="recent_task_option_pin" msgid="7929860679018978258">"Épingler"</string>
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Balayer l\'écran de bas en haut pour changer d\'application"</string>
-    <!-- no translation found for accessibility_desc_recent_apps (1444379410873162882) -->
-    <skip />
-    <!-- no translation found for recents_empty_message (7040467240571714191) -->
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Aperçu"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"Aucun élément récent"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
     <skip />
 </resources>
diff --git a/quickstep/res/values-gl/strings.xml b/quickstep/res/values-gl/strings.xml
index 9c5c0ee..25d3796 100644
--- a/quickstep/res/values-gl/strings.xml
+++ b/quickstep/res/values-gl/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Pasa o dedo cara arriba desde a parte inferior para cambiar de aplicacións"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Visión xeral"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Non hai elementos recentes"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-gu/strings.xml b/quickstep/res/values-gu/strings.xml
index 9d3da8c..f463e13 100644
--- a/quickstep/res/values-gu/strings.xml
+++ b/quickstep/res/values-gu/strings.xml
@@ -26,4 +26,6 @@
     <skip />
     <!-- no translation found for recents_empty_message (7040467240571714191) -->
     <skip />
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-hi/strings.xml b/quickstep/res/values-hi/strings.xml
index de1d086..ee933d1 100644
--- a/quickstep/res/values-hi/strings.xml
+++ b/quickstep/res/values-hi/strings.xml
@@ -26,4 +26,6 @@
     <skip />
     <!-- no translation found for recents_empty_message (7040467240571714191) -->
     <skip />
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-hr/strings.xml b/quickstep/res/values-hr/strings.xml
index ab53361..a0b734f 100644
--- a/quickstep/res/values-hr/strings.xml
+++ b/quickstep/res/values-hr/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Prijeđite prstom od dna prema gore da biste promijenili aplikaciju"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Pregled"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Nema nedavnih stavki"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-hu/strings.xml b/quickstep/res/values-hu/strings.xml
index 5209dde..8a465e2 100644
--- a/quickstep/res/values-hu/strings.xml
+++ b/quickstep/res/values-hu/strings.xml
@@ -22,8 +22,8 @@
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"Osztott képernyő"</string>
     <string name="recent_task_option_pin" msgid="7929860679018978258">"Rögzítés"</string>
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Ha váltani szeretne az alkalmazások között, csúsztassa gyorsan az ujját a képernyő aljától felfelé"</string>
-    <!-- no translation found for accessibility_desc_recent_apps (1444379410873162882) -->
-    <skip />
-    <!-- no translation found for recents_empty_message (7040467240571714191) -->
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Áttekintés"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"Nincsenek mostanában használt elemek"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
     <skip />
 </resources>
diff --git a/quickstep/res/values-hy/strings.xml b/quickstep/res/values-hy/strings.xml
index fb03f67..fdfe818 100644
--- a/quickstep/res/values-hy/strings.xml
+++ b/quickstep/res/values-hy/strings.xml
@@ -22,8 +22,8 @@
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"Տրոհել էկրանը"</string>
     <string name="recent_task_option_pin" msgid="7929860679018978258">"Ամրացնել"</string>
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Սահեցրեք ներքևից վերև՝ մյուս հավելվածին անցնելու համար"</string>
-    <!-- no translation found for accessibility_desc_recent_apps (1444379410873162882) -->
-    <skip />
-    <!-- no translation found for recents_empty_message (7040467240571714191) -->
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Ընդհանուր տեղեկություններ"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"Վերջին տարրեր չկան"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
     <skip />
 </resources>
diff --git a/quickstep/res/values-in/strings.xml b/quickstep/res/values-in/strings.xml
index 603ec5a..786a10c 100644
--- a/quickstep/res/values-in/strings.xml
+++ b/quickstep/res/values-in/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Geser dari bawah ke atas untuk beralih aplikasi"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Ringkasan"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Tidak ada item baru-baru ini"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-it/strings.xml b/quickstep/res/values-it/strings.xml
index b1064d7..0da2251 100644
--- a/quickstep/res/values-it/strings.xml
+++ b/quickstep/res/values-it/strings.xml
@@ -22,8 +22,8 @@
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"Schermo diviso"</string>
     <string name="recent_task_option_pin" msgid="7929860679018978258">"Blocca"</string>
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Scorri verso l\'alto dalla parte inferiore per cambiare app"</string>
-    <!-- no translation found for accessibility_desc_recent_apps (1444379410873162882) -->
-    <skip />
-    <!-- no translation found for recents_empty_message (7040467240571714191) -->
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Panoramica"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"Nessun elemento recente"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
     <skip />
 </resources>
diff --git a/quickstep/res/values-iw/strings.xml b/quickstep/res/values-iw/strings.xml
index ab55dfc..f7e8338 100644
--- a/quickstep/res/values-iw/strings.xml
+++ b/quickstep/res/values-iw/strings.xml
@@ -22,8 +22,8 @@
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"מסך מפוצל"</string>
     <string name="recent_task_option_pin" msgid="7929860679018978258">"הצמדה"</string>
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"יש להחליק כלפי מעלה מהחלק התחתון כדי לעבור בין אפליקציות"</string>
-    <!-- no translation found for accessibility_desc_recent_apps (1444379410873162882) -->
-    <skip />
-    <!-- no translation found for recents_empty_message (7040467240571714191) -->
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"מסכים אחרונים"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"אין פריטים אחרונים"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
     <skip />
 </resources>
diff --git a/quickstep/res/values-ja/strings.xml b/quickstep/res/values-ja/strings.xml
index a5ed530..7e14d2c 100644
--- a/quickstep/res/values-ja/strings.xml
+++ b/quickstep/res/values-ja/strings.xml
@@ -22,8 +22,8 @@
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"分割画面"</string>
     <string name="recent_task_option_pin" msgid="7929860679018978258">"固定"</string>
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"アプリを切り替えるには、下から上にスワイプします"</string>
-    <!-- no translation found for accessibility_desc_recent_apps (1444379410873162882) -->
-    <skip />
-    <!-- no translation found for recents_empty_message (7040467240571714191) -->
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"概要"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"最近のアイテムはありません"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
     <skip />
 </resources>
diff --git a/quickstep/res/values-ka/strings.xml b/quickstep/res/values-ka/strings.xml
index e845c88..cf4c661 100644
--- a/quickstep/res/values-ka/strings.xml
+++ b/quickstep/res/values-ka/strings.xml
@@ -22,8 +22,8 @@
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"ეკრანის გაყოფა"</string>
     <string name="recent_task_option_pin" msgid="7929860679018978258">"ჩამაგრება"</string>
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"აპების გადასართავად გადაფურცლეთ ქვედა კიდედან ზემოთ"</string>
-    <!-- no translation found for accessibility_desc_recent_apps (1444379410873162882) -->
-    <skip />
-    <!-- no translation found for recents_empty_message (7040467240571714191) -->
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"მიმოხილვა"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"ბოლოს გამოყენებული ერთეულები არ არის"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
     <skip />
 </resources>
diff --git a/quickstep/res/values-kk/strings.xml b/quickstep/res/values-kk/strings.xml
index f816fec..f865a04 100644
--- a/quickstep/res/values-kk/strings.xml
+++ b/quickstep/res/values-kk/strings.xml
@@ -22,8 +22,8 @@
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"Экранды бөлу"</string>
     <string name="recent_task_option_pin" msgid="7929860679018978258">"Бекіту"</string>
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Қолданбалар арасында ауысу үшін төменнен жоғары қарай саусақпен сырғытыңыз"</string>
-    <!-- no translation found for accessibility_desc_recent_apps (1444379410873162882) -->
-    <skip />
-    <!-- no translation found for recents_empty_message (7040467240571714191) -->
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Шолу"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"Соңғы элементтер жоқ"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
     <skip />
 </resources>
diff --git a/quickstep/res/values-km/strings.xml b/quickstep/res/values-km/strings.xml
index 5b1c55c..a35ab26 100644
--- a/quickstep/res/values-km/strings.xml
+++ b/quickstep/res/values-km/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"អូស​ពី​ក្រោម​ឡើង​លើ ដើម្បី​ប្ដូរ​កម្មវិធី"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"ទិដ្ឋភាពរួម"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"មិនមានធាតុថ្មីៗទេ"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-kn/strings.xml b/quickstep/res/values-kn/strings.xml
index 3039491..dc57df1 100644
--- a/quickstep/res/values-kn/strings.xml
+++ b/quickstep/res/values-kn/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಬದಲಿಸಲು ಕೆಳಗಿನಿಂದ ಮೇಲಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"ಅವಲೋಕನ"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"ಯಾವುದೇ ಇತ್ತೀಚಿನ ಐಟಂಗಳಿಲ್ಲ"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-ko/strings.xml b/quickstep/res/values-ko/strings.xml
index b428770..36fd122 100644
--- a/quickstep/res/values-ko/strings.xml
+++ b/quickstep/res/values-ko/strings.xml
@@ -22,8 +22,8 @@
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"화면 분할"</string>
     <string name="recent_task_option_pin" msgid="7929860679018978258">"고정"</string>
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"아래에서 위로 스와이프하여 앱을 전환합니다."</string>
-    <!-- no translation found for accessibility_desc_recent_apps (1444379410873162882) -->
-    <skip />
-    <!-- no translation found for recents_empty_message (7040467240571714191) -->
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"최근 사용"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"최근 항목이 없습니다."</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
     <skip />
 </resources>
diff --git a/quickstep/res/values-ky/strings.xml b/quickstep/res/values-ky/strings.xml
index e04d2ed..060a8dd 100644
--- a/quickstep/res/values-ky/strings.xml
+++ b/quickstep/res/values-ky/strings.xml
@@ -22,8 +22,8 @@
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"Экранды бөлүү"</string>
     <string name="recent_task_option_pin" msgid="7929860679018978258">"Кадап коюу"</string>
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Колдонмолорду которуштуруу үчүн экранды төмөндөн жогору карай сүрүңүз"</string>
-    <!-- no translation found for accessibility_desc_recent_apps (1444379410873162882) -->
-    <skip />
-    <!-- no translation found for recents_empty_message (7040467240571714191) -->
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Сереп салуу"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"Акыркы колдонмолор жок"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
     <skip />
 </resources>
diff --git a/quickstep/res/values-lo/strings.xml b/quickstep/res/values-lo/strings.xml
index 2411871..1abb856 100644
--- a/quickstep/res/values-lo/strings.xml
+++ b/quickstep/res/values-lo/strings.xml
@@ -22,8 +22,8 @@
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"ແບ່ງໜ້າຈໍ"</string>
     <string name="recent_task_option_pin" msgid="7929860679018978258">"ປັກໝຸດ"</string>
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"ປັດຂຶ້ນຈາກລຸ່ມສຸດເພື່ອສະຫຼັບແອັບ"</string>
-    <!-- no translation found for accessibility_desc_recent_apps (1444379410873162882) -->
-    <skip />
-    <!-- no translation found for recents_empty_message (7040467240571714191) -->
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"ພາບຮວມ"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"ບໍ່ມີລາຍການຫຼ້າສຸດ"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
     <skip />
 </resources>
diff --git a/quickstep/res/values-lt/strings.xml b/quickstep/res/values-lt/strings.xml
index 06eeb5a..0c09a94 100644
--- a/quickstep/res/values-lt/strings.xml
+++ b/quickstep/res/values-lt/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Perbraukite aukštyn iš apačios, kad perjungtumėte programas"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Apžvalga"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Nėra jokių naujausių elementų"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-lv/strings.xml b/quickstep/res/values-lv/strings.xml
index 6b86991..72d1cb5 100644
--- a/quickstep/res/values-lv/strings.xml
+++ b/quickstep/res/values-lv/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Lai pārslēgtu lietotnes, velciet augšup no apakšdaļas."</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Pārskats"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Nav nesenu vienumu."</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-mk/strings.xml b/quickstep/res/values-mk/strings.xml
index a86a2a2..06bf5d0 100644
--- a/quickstep/res/values-mk/strings.xml
+++ b/quickstep/res/values-mk/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Повлечете нагоре од дното за да ги смените апликациите"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Преглед"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Нема неодамнешни ставки"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-ml/strings.xml b/quickstep/res/values-ml/strings.xml
index 4ff880c..b936906 100644
--- a/quickstep/res/values-ml/strings.xml
+++ b/quickstep/res/values-ml/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"ആപ്പുകൾ മാറാൻ താഴെ നിന്ന് മുകളിലേക്ക് സ്വൈപ്പ് ചെയ്യുക"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"അവലോകനം"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"സമീപകാല ഇനങ്ങൾ ഒന്നുമില്ല"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-mn/strings.xml b/quickstep/res/values-mn/strings.xml
index e4253f6..8b92214 100644
--- a/quickstep/res/values-mn/strings.xml
+++ b/quickstep/res/values-mn/strings.xml
@@ -22,8 +22,8 @@
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"Дэлгэцийг хуваах"</string>
     <string name="recent_task_option_pin" msgid="7929860679018978258">"Тогтоох"</string>
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Аппыг сэлгэхийн тулд доороос дээш шударна уу"</string>
-    <!-- no translation found for accessibility_desc_recent_apps (1444379410873162882) -->
-    <skip />
-    <!-- no translation found for recents_empty_message (7040467240571714191) -->
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Тойм"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"Сүүлийн үеийн зүйл алга"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
     <skip />
 </resources>
diff --git a/quickstep/res/values-mr/strings.xml b/quickstep/res/values-mr/strings.xml
index 5b9a621..596792d 100644
--- a/quickstep/res/values-mr/strings.xml
+++ b/quickstep/res/values-mr/strings.xml
@@ -22,8 +22,8 @@
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"विभाजित स्क्रीन"</string>
     <string name="recent_task_option_pin" msgid="7929860679018978258">"पिन करा"</string>
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"अ‍ॅप्स स्विच करण्यासाठी तळापासून वर स्वाइप करा"</string>
-    <!-- no translation found for accessibility_desc_recent_apps (1444379410873162882) -->
-    <skip />
-    <!-- no translation found for recents_empty_message (7040467240571714191) -->
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"अवलोकन"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"कोणतेही अलीकडील आयटम नाहीत"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
     <skip />
 </resources>
diff --git a/quickstep/res/values-ms/strings.xml b/quickstep/res/values-ms/strings.xml
index a13b7bb..336aaf6 100644
--- a/quickstep/res/values-ms/strings.xml
+++ b/quickstep/res/values-ms/strings.xml
@@ -22,8 +22,8 @@
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"Skrin pisah"</string>
     <string name="recent_task_option_pin" msgid="7929860679018978258">"Semat"</string>
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Leret ke atas dari bawah untuk menukar apl"</string>
-    <!-- no translation found for accessibility_desc_recent_apps (1444379410873162882) -->
-    <skip />
-    <!-- no translation found for recents_empty_message (7040467240571714191) -->
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Ikhtisar"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"Tiada item terbaharu"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
     <skip />
 </resources>
diff --git a/quickstep/res/values-my/strings.xml b/quickstep/res/values-my/strings.xml
index c7662a8..d71e5fc 100644
--- a/quickstep/res/values-my/strings.xml
+++ b/quickstep/res/values-my/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"အက်ပ်များပြောင်းရန် အောက်ခြေမှ အပေါ်သို့ပွတ်ဆွဲပါ"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"အနှစ်ချုပ်"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"မကြာမီကဖွင့်ထားသည်များ မရှိပါ"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-nb/strings.xml b/quickstep/res/values-nb/strings.xml
index 3547ba4..504f43a 100644
--- a/quickstep/res/values-nb/strings.xml
+++ b/quickstep/res/values-nb/strings.xml
@@ -22,8 +22,8 @@
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"Delt skjerm"</string>
     <string name="recent_task_option_pin" msgid="7929860679018978258">"Fest"</string>
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Sveip opp fra bunnen for å bytte app"</string>
-    <!-- no translation found for accessibility_desc_recent_apps (1444379410873162882) -->
-    <skip />
-    <!-- no translation found for recents_empty_message (7040467240571714191) -->
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Oversikt"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"Ingen nylige elementer"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
     <skip />
 </resources>
diff --git a/quickstep/res/values-ne/strings.xml b/quickstep/res/values-ne/strings.xml
index 73738f5..7500213 100644
--- a/quickstep/res/values-ne/strings.xml
+++ b/quickstep/res/values-ne/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"अनुप्रयोगहरू बदल्न तलबाट माथितिर स्वाइप गर्नुहोस्"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"परिदृश्य"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"हालसालैको कुनै पनि वस्तु छैन"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-nl/strings.xml b/quickstep/res/values-nl/strings.xml
index a8e092a..2ba24a6 100644
--- a/quickstep/res/values-nl/strings.xml
+++ b/quickstep/res/values-nl/strings.xml
@@ -22,8 +22,8 @@
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"Gesplitst scherm"</string>
     <string name="recent_task_option_pin" msgid="7929860679018978258">"Vastzetten"</string>
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Veeg omhoog vanaf de onderkant om tussen apps te wisselen"</string>
-    <!-- no translation found for accessibility_desc_recent_apps (1444379410873162882) -->
-    <skip />
-    <!-- no translation found for recents_empty_message (7040467240571714191) -->
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Overzicht"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"Geen recente items"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
     <skip />
 </resources>
diff --git a/quickstep/res/values-pa/strings.xml b/quickstep/res/values-pa/strings.xml
index 38d7499..fbcb60c 100644
--- a/quickstep/res/values-pa/strings.xml
+++ b/quickstep/res/values-pa/strings.xml
@@ -22,8 +22,8 @@
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ"</string>
     <string name="recent_task_option_pin" msgid="7929860679018978258">"ਪਿੰਨ ਕਰੋ"</string>
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"ਐਪਾਂ ਵਿੱਚ ਅਦਲਾ-ਬਦਲੀ ਕਰਨ ਲਈ ਹੇਠਾਂ ਤੋਂ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ"</string>
-    <!-- no translation found for accessibility_desc_recent_apps (1444379410873162882) -->
-    <skip />
-    <!-- no translation found for recents_empty_message (7040467240571714191) -->
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"ਰੂਪ-ਰੇਖਾ"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"ਕੋਈ ਹਾਲੀਆ ਆਈਟਮਾਂ ਨਹੀਂ"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
     <skip />
 </resources>
diff --git a/quickstep/res/values-pl/strings.xml b/quickstep/res/values-pl/strings.xml
index 5a730ac..1ad7070 100644
--- a/quickstep/res/values-pl/strings.xml
+++ b/quickstep/res/values-pl/strings.xml
@@ -22,8 +22,8 @@
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"Podziel ekran"</string>
     <string name="recent_task_option_pin" msgid="7929860679018978258">"Przypnij"</string>
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Przesuń palcem z dołu ekranu, by przełączać aplikacje"</string>
-    <!-- no translation found for accessibility_desc_recent_apps (1444379410873162882) -->
-    <skip />
-    <!-- no translation found for recents_empty_message (7040467240571714191) -->
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Przegląd"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"Brak ostatnich elementów"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
     <skip />
 </resources>
diff --git a/quickstep/res/values-pt-rPT/strings.xml b/quickstep/res/values-pt-rPT/strings.xml
index f2496a0..a63d329 100644
--- a/quickstep/res/values-pt-rPT/strings.xml
+++ b/quickstep/res/values-pt-rPT/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Deslize rapidamente para cima a partir da parte inferior para alternar entre aplicações."</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Vista geral"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Nenhum item recente"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-pt/strings.xml b/quickstep/res/values-pt/strings.xml
index cc54aa7..05d20e0 100644
--- a/quickstep/res/values-pt/strings.xml
+++ b/quickstep/res/values-pt/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Deslize de baixo para cima para alternar entre apps"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Visão geral"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Nenhum item recente"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-ro/strings.xml b/quickstep/res/values-ro/strings.xml
index c4d2338..4264370 100644
--- a/quickstep/res/values-ro/strings.xml
+++ b/quickstep/res/values-ro/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Glisați de jos în sus pentru a schimba aplicațiile"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Recente"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Niciun element recent"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-ru/strings.xml b/quickstep/res/values-ru/strings.xml
index a0de61a..47ddff5 100644
--- a/quickstep/res/values-ru/strings.xml
+++ b/quickstep/res/values-ru/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Чтобы переключить приложение, проведите по экрану снизу вверх"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Обзор"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Недавних приложений нет."</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-si/strings.xml b/quickstep/res/values-si/strings.xml
index ad29116..a9b1493 100644
--- a/quickstep/res/values-si/strings.xml
+++ b/quickstep/res/values-si/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"යෙදුම් මාරු කිරීම සඳහා පහළ සිට ස්වයිප් කරන්න"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"දළ විශ්ලේෂණය"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"මෑත අයිතම නැත"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-sk/strings.xml b/quickstep/res/values-sk/strings.xml
index 4e7467f..fe02855 100644
--- a/quickstep/res/values-sk/strings.xml
+++ b/quickstep/res/values-sk/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Aplikácie môžete prepínať potiahnutím prstom zdola nahor"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Prehľad"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Žiadne nedávne položky"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-sl/strings.xml b/quickstep/res/values-sl/strings.xml
index d19b69f..72d52a5 100644
--- a/quickstep/res/values-sl/strings.xml
+++ b/quickstep/res/values-sl/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Če želite preklopiti med aplikacijami, z dna zaslona s prstom povlecite navzgor"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Pregled"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Ni nedavnih elementov"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-sq/strings.xml b/quickstep/res/values-sq/strings.xml
index e9dc776..954342c 100644
--- a/quickstep/res/values-sq/strings.xml
+++ b/quickstep/res/values-sq/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Rrëshqit larg nga poshtë për të ndryshuar aplikacionet"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Përmbledhja"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Nuk ka asnjë artikull të fundit"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-sr/strings.xml b/quickstep/res/values-sr/strings.xml
index ad10d4c..51a9586 100644
--- a/quickstep/res/values-sr/strings.xml
+++ b/quickstep/res/values-sr/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Превуците нагоре да бисте прешли на другу апликацију"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Преглед"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Нема недавних ставки"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-sv/strings.xml b/quickstep/res/values-sv/strings.xml
index e7bdf19..266cf60 100644
--- a/quickstep/res/values-sv/strings.xml
+++ b/quickstep/res/values-sv/strings.xml
@@ -22,8 +22,8 @@
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"Delad skärm"</string>
     <string name="recent_task_option_pin" msgid="7929860679018978258">"Fäst"</string>
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Växla mellan appar genom att svepa uppåt från nederkanten"</string>
-    <!-- no translation found for accessibility_desc_recent_apps (1444379410873162882) -->
-    <skip />
-    <!-- no translation found for recents_empty_message (7040467240571714191) -->
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Översikt"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"Listan med de senaste åtgärderna är tom"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
     <skip />
 </resources>
diff --git a/quickstep/res/values-sw/strings.xml b/quickstep/res/values-sw/strings.xml
index 7f453c6..e85fa45 100644
--- a/quickstep/res/values-sw/strings.xml
+++ b/quickstep/res/values-sw/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Telezesha kidole juu kuanzia chini ili ubadilishe programu"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Muhtasari"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Hakuna vipengee vya hivi karibuni"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-te/strings.xml b/quickstep/res/values-te/strings.xml
index 543b225..108f350 100644
--- a/quickstep/res/values-te/strings.xml
+++ b/quickstep/res/values-te/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"యాప్‌లను మార్చడానికి దిగువ నుండి పైకి స్వైప్ చేయండి"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"అవలోకనం"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"ఇటీవలి అంశాలు ఏవీ లేవు"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-th/strings.xml b/quickstep/res/values-th/strings.xml
index 92fa179..80f91b0 100644
--- a/quickstep/res/values-th/strings.xml
+++ b/quickstep/res/values-th/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"เลื่อนขึ้นจากด้านล่างเพื่อสลับแอป"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"ภาพรวม"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"ไม่มีรายการล่าสุด"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-tl/strings.xml b/quickstep/res/values-tl/strings.xml
index 136cee4..b28e04e 100644
--- a/quickstep/res/values-tl/strings.xml
+++ b/quickstep/res/values-tl/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Mag-swipe pataas mula sa ibaba para lumipat ng app"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Overview"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Walang kamakailang item"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-tr/strings.xml b/quickstep/res/values-tr/strings.xml
index 542f262..1399353 100644
--- a/quickstep/res/values-tr/strings.xml
+++ b/quickstep/res/values-tr/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Uygulamaları değiştirmek için alttan yukarı kaydırın"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Genel bakış"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Yeni öğe yok"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-uk/strings.xml b/quickstep/res/values-uk/strings.xml
index 44c04c3..929bbe7 100644
--- a/quickstep/res/values-uk/strings.xml
+++ b/quickstep/res/values-uk/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Щоб переходити між додатками, проводьте пальцем знизу вгору"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Огляд"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Немає нещодавніх додатків"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-ur/strings.xml b/quickstep/res/values-ur/strings.xml
index 341ab0d..0271fe4 100644
--- a/quickstep/res/values-ur/strings.xml
+++ b/quickstep/res/values-ur/strings.xml
@@ -26,4 +26,6 @@
     <skip />
     <!-- no translation found for recents_empty_message (7040467240571714191) -->
     <skip />
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-uz/strings.xml b/quickstep/res/values-uz/strings.xml
index bb6edb7..91e11d3 100644
--- a/quickstep/res/values-uz/strings.xml
+++ b/quickstep/res/values-uz/strings.xml
@@ -22,8 +22,8 @@
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"Ekranni ikkiga ajratish"</string>
     <string name="recent_task_option_pin" msgid="7929860679018978258">"Mahkamlash"</string>
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Ilovalarni almashtirish uchun pastdan yuqoriga suring"</string>
-    <!-- no translation found for accessibility_desc_recent_apps (1444379410873162882) -->
-    <skip />
-    <!-- no translation found for recents_empty_message (7040467240571714191) -->
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Nazar"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"Yaqinda ishlatilgan ilovalar yo‘q"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
     <skip />
 </resources>
diff --git a/quickstep/res/values-vi/strings.xml b/quickstep/res/values-vi/strings.xml
index 2241f02..809517a 100644
--- a/quickstep/res/values-vi/strings.xml
+++ b/quickstep/res/values-vi/strings.xml
@@ -22,8 +22,8 @@
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"Chia đôi màn hình"</string>
     <string name="recent_task_option_pin" msgid="7929860679018978258">"Ghim"</string>
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Vuốt từ dưới lên để chuyển đổi ứng dụng"</string>
-    <!-- no translation found for accessibility_desc_recent_apps (1444379410873162882) -->
-    <skip />
-    <!-- no translation found for recents_empty_message (7040467240571714191) -->
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Tổng quan"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"Không có mục gần đây nào"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
     <skip />
 </resources>
diff --git a/quickstep/res/values-zh-rCN/strings.xml b/quickstep/res/values-zh-rCN/strings.xml
index 77b6432..a44dd2d 100644
--- a/quickstep/res/values-zh-rCN/strings.xml
+++ b/quickstep/res/values-zh-rCN/strings.xml
@@ -22,8 +22,8 @@
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"分屏"</string>
     <string name="recent_task_option_pin" msgid="7929860679018978258">"固定"</string>
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"从屏幕底部向上滑动即可切换应用"</string>
-    <!-- no translation found for accessibility_desc_recent_apps (1444379410873162882) -->
-    <skip />
-    <!-- no translation found for recents_empty_message (7040467240571714191) -->
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"概览"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"近期没有任何内容"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
     <skip />
 </resources>
diff --git a/quickstep/res/values-zh-rHK/strings.xml b/quickstep/res/values-zh-rHK/strings.xml
index bdf27ed..3879bc5 100644
--- a/quickstep/res/values-zh-rHK/strings.xml
+++ b/quickstep/res/values-zh-rHK/strings.xml
@@ -22,6 +22,8 @@
     <string name="recent_task_option_split_screen" msgid="5353188922202653570">"分割畫面"</string>
     <string name="recent_task_option_pin" msgid="7929860679018978258">"固定"</string>
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"從螢幕底部向上快速滑動,即可切換應用程式"</string>
-    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"總覽"</string>
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"概覽"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"最近沒有任何項目"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-zh-rTW/strings.xml b/quickstep/res/values-zh-rTW/strings.xml
index b7528ca..f275168 100644
--- a/quickstep/res/values-zh-rTW/strings.xml
+++ b/quickstep/res/values-zh-rTW/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"從畫面底部向上滑動以切換應用程式"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"總覽"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"最近沒有任何項目"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-zu/strings.xml b/quickstep/res/values-zu/strings.xml
index e43e986..206718e 100644
--- a/quickstep/res/values-zu/strings.xml
+++ b/quickstep/res/values-zu/strings.xml
@@ -24,4 +24,6 @@
     <string name="recents_swipe_up_onboarding" msgid="1025535041275136564">"Swayiphela phezulu kusukela phansi ukuze ushintshe izinhlelo zokusebenza"</string>
     <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Buka konke"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Azikho izinto zakamuva"</string>
+    <!-- no translation found for accessibility_close_task (5354563209433803643) -->
+    <skip />
 </resources>
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index 8f2e104..af81a59 100644
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -106,6 +106,8 @@
     private DeviceProfile mDeviceProfile;
     private View mFloatingView;
 
+    private RemoteAnimationRunnerCompat mRemoteAnimationOverride;
+
     private final AnimatorListenerAdapter mReapplyStateListener = new AnimatorListenerAdapter() {
         @Override
         public void onAnimationEnd(Animator animation) {
@@ -146,15 +148,20 @@
 
                     @Override
                     public AnimatorSet getAnimator(RemoteAnimationTargetCompat[] targetCompats) {
-                        Animator[] anims = composeRecentsLaunchAnimator(v, targetCompats);
                         AnimatorSet anim = new AnimatorSet();
-                        if (anims != null) {
-                            anim.playTogether(anims);
-                        } else {
-                            anim.play(getLauncherAnimators(v, targetCompats));
+
+
+                        if (!composeRecentsLaunchAnimator(v, targetCompats, anim)) {
+                            // Set the state animation first so that any state listeners are called
+                            // before our internal listeners.
+                            mLauncher.getStateManager().setCurrentAnimation(anim);
+
+                            anim.play(getIconAnimator(v));
+                            if (launcherIsATargetWithMode(targetCompats, MODE_CLOSING)) {
+                                anim.play(getLauncherContentAnimator(false /* show */));
+                            }
                             anim.play(getWindowAnimators(v, targetCompats));
                         }
-                        mLauncher.getStateManager().setCurrentAnimation(anim);
                         return anim;
                     }
                 };
@@ -172,6 +179,10 @@
         return getDefaultActivityLaunchOptions(launcher, v);
     }
 
+    public void setRemoteAnimationOverride(RemoteAnimationRunnerCompat remoteAnimationOverride) {
+        mRemoteAnimationOverride = remoteAnimationOverride;
+    }
+
     /**
      * Try to find a TaskView that corresponds with the component of the launched view.
      *
@@ -233,11 +244,11 @@
     /**
      * Composes the animations for a launch from the recents list if possible.
      */
-    private Animator[] composeRecentsLaunchAnimator(View v,
-            RemoteAnimationTargetCompat[] targets) {
+    private boolean composeRecentsLaunchAnimator(View v,
+            RemoteAnimationTargetCompat[] targets, AnimatorSet target) {
         // Ensure recents is actually visible
         if (!mLauncher.getStateManager().getState().overviewUi) {
-            return null;
+            return false;
         }
 
         RecentsView recentsView = mLauncher.getOverviewPanel();
@@ -246,7 +257,7 @@
 
         TaskView taskView = findTaskViewToLaunch(mLauncher, v, targets);
         if (taskView == null) {
-            return null;
+            return false;
         }
 
         // Found a visible recents task that matches the opening app, lets launch the app from there
@@ -273,9 +284,15 @@
             };
         }
 
-        Animator windowAnim = getRecentsWindowAnimator(taskView, skipLauncherChanges, targets);
-        windowAnim.addListener(windowAnimEndListener);
-        return new Animator[] {launcherAnim, windowAnim};
+        target.play(getRecentsWindowAnimator(taskView, skipLauncherChanges, targets));
+        target.play(launcherAnim);
+
+        // Set the current animation first, before adding windowAnimEndListener. Setting current
+        // animation adds some listeners which need to be called before windowAnimEndListener
+        // (the ordering of listeners matter in this case).
+        mLauncher.getStateManager().setCurrentAnimation(target);
+        target.addListener(windowAnimEndListener);
+        return true;
     }
 
     /**
@@ -355,18 +372,6 @@
     }
 
     /**
-     * @return Animators that control the movements of the Launcher and icon of the opening target.
-     */
-    private AnimatorSet getLauncherAnimators(View v, RemoteAnimationTargetCompat[] targets) {
-        AnimatorSet launcherAnimators = new AnimatorSet();
-        launcherAnimators.play(getIconAnimator(v));
-        if (launcherIsATargetWithMode(targets, MODE_CLOSING)) {
-            launcherAnimators.play(getLauncherContentAnimator(false /* show */));
-        }
-        return launcherAnimators;
-    }
-
-    /**
      * Content is everything on screen except the background and the floating view (if any).
      *
      * @param show If true: Animate the content so that it moves upwards and fades in.
@@ -643,6 +648,7 @@
      * Registers remote animations used when closing apps to home screen.
      */
     private void registerRemoteAnimations() {
+        // Unregister this
         if (hasControlRemoteAppTransitionPermission()) {
             try {
                 RemoteAnimationDefinitionCompat definition = new RemoteAnimationDefinitionCompat();
@@ -677,22 +683,53 @@
     private RemoteAnimationRunnerCompat getWallpaperOpenRunner() {
         return new LauncherAnimationRunner(mHandler) {
             @Override
-            public AnimatorSet getAnimator(RemoteAnimationTargetCompat[] targetCompats) {
-                if (mLauncher.getStateManager().getState().overviewUi) {
-                    // We use a separate transition for Overview mode.
-                    return null;
+            public void onAnimationStart(RemoteAnimationTargetCompat[] targetCompats,
+                    Runnable runnable) {
+                if (mLauncher.getStateManager().getState().overviewUi
+                        && mRemoteAnimationOverride != null) {
+                    // This transition is only used for the fallback activity and should not be
+                    // managed here (but necessary to implement here since the defined remote
+                    // animation currently takes precendence over the one defined in the activity
+                    // options).
+                    mRemoteAnimationOverride.onAnimationStart(targetCompats, runnable);
+                    return;
                 }
+                super.onAnimationStart(targetCompats, runnable);
+            }
 
+            @Override
+            public void onAnimationCancelled() {
+                if (mLauncher.getStateManager().getState().overviewUi
+                        && mRemoteAnimationOverride != null) {
+                    // This transition is only used for the fallback activity and should not be
+                    // managed here (but necessary to implement here since the defined remote
+                    // animation currently takes precendence over the one defined in the activity
+                    // options).
+                    mRemoteAnimationOverride.onAnimationCancelled();
+                    return;
+                }
+                super.onAnimationCancelled();
+            }
+
+            @Override
+            public AnimatorSet getAnimator(RemoteAnimationTargetCompat[] targetCompats) {
                 AnimatorSet anim = new AnimatorSet();
                 anim.play(getClosingWindowAnimators(targetCompats));
 
-                if (launcherIsATargetWithMode(targetCompats, MODE_OPENING)) {
-                    AnimatorSet contentAnimation = getLauncherResumeAnimation();
-                    anim.play(contentAnimation);
-
+                // Normally, we run the launcher content animation when we are transitioning home,
+                // but if home is already visible, then we don't want to animate the contents of
+                // launcher unless we know that we are animating home as a result of the home button
+                // press with quickstep, which will result in launcher being started on touch down,
+                // prior to the animation home (and won't be in the targets list because it is
+                // already visible). In that case, we force invisibility on touch down, and only
+                // reset it after the animation to home is initialized.
+                if (launcherIsATargetWithMode(targetCompats, MODE_OPENING)
+                        || mLauncher.isForceInvisible()) {
                     // Only register the content animation for cancellation when state changes
-                    mLauncher.getStateManager().setCurrentAnimation(contentAnimation);
+                    mLauncher.getStateManager().setCurrentAnimation(anim);
+                    createLauncherResumeAnimation(anim);
                 }
+                mLauncher.setForceInvisible(false);
                 return anim;
             }
         };
@@ -754,14 +791,14 @@
     }
 
     /**
-     * @return Animator that modifies Launcher as a result from {@link #getWallpaperOpenRunner}.
+     * Creates an animator that modifies Launcher as a result from {@link #getWallpaperOpenRunner}.
      */
-    private AnimatorSet getLauncherResumeAnimation() {
+    private void createLauncherResumeAnimation(AnimatorSet anim) {
         if (mLauncher.isInState(LauncherState.ALL_APPS)
                 || mLauncher.getDeviceProfile().isVerticalBarLayout()) {
             AnimatorSet contentAnimator = getLauncherContentAnimator(true /* show */);
             contentAnimator.setStartDelay(LAUNCHER_RESUME_START_DELAY);
-            return contentAnimator;
+            anim.play(contentAnimator);
         } else {
             AnimatorSet workspaceAnimator = new AnimatorSet();
 
@@ -799,12 +836,10 @@
             allAppsOvershoot.setDuration(153);
             allAppsOvershoot.setInterpolator(Interpolators.OVERSHOOT_0);
 
-            AnimatorSet resumeLauncherAnimation = new AnimatorSet();
-            resumeLauncherAnimation.play(workspaceAnimator);
-            resumeLauncherAnimation.playSequentially(allAppsSlideIn, allAppsOvershoot);
 
-            resumeLauncherAnimation.addListener(mReapplyStateListener);
-            return resumeLauncherAnimation;
+            anim.play(workspaceAnimator);
+            anim.playSequentially(allAppsSlideIn, allAppsOvershoot);
+            anim.addListener(mReapplyStateListener);
         }
     }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
index 2626e7c..0e3d2a4 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
@@ -88,10 +88,10 @@
     }
 
     @Override
-    public float[] getOverviewTranslationFactor(Launcher launcher) {
-        // Keep the same translation as in overview, so that we don't slide around when
+    public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
+        // Keep the same transition properties as overview, so that we don't move around when
         // transitioning to All Apps.
-        return LauncherState.OVERVIEW.getOverviewTranslationFactor(launcher);
+        return LauncherState.OVERVIEW.getOverviewScaleAndTranslationYFactor(launcher);
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
index 99bf264..f98f7a5 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
@@ -50,7 +50,7 @@
     }
 
     @Override
-    public float[] getOverviewTranslationFactor(Launcher launcher) {
-        return new float[] {0f, 0.5f};
+    public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
+        return new float[] {1f, 0.5f};
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java
index 23add95..3622fc4 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java
@@ -37,12 +37,16 @@
 
     @Override
     protected int getSwipeDirection(MotionEvent ev) {
-        mFromState = NORMAL;
-        mToState = OVERVIEW;
         return SwipeDetector.DIRECTION_BOTH;
     }
 
     @Override
+    protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
+        boolean draggingFromNav = mLauncher.getDeviceProfile().isSeascape() != isDragTowardPositive;
+        return draggingFromNav ? OVERVIEW : NORMAL;
+    }
+
+    @Override
     protected float getShiftRange() {
         return mLauncher.getDragLayer().getWidth();
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/LandscapeStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/LandscapeStatesTouchController.java
index 720b20a..355b88d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/LandscapeStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/LandscapeStatesTouchController.java
@@ -58,8 +58,9 @@
         }
     }
 
-    protected LauncherState getTargetState() {
-        if (mLauncher.isInState(ALL_APPS)) {
+    @Override
+    protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
+        if (fromState == ALL_APPS) {
             // Should swipe down go to OVERVIEW instead?
             return TouchInteractionService.isConnected() ?
                     mLauncher.getStateManager().getLastState() : NORMAL;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
index 4b2763b..d97b7b2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -58,8 +58,8 @@
     }
 
     @Override
-    public float[] getOverviewTranslationFactor(Launcher launcher) {
-        return new float[] {0f, 0f};
+    public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
+        return new float[] {1f, 0f};
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
index 7d9cce4..1b65ca0 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
@@ -32,22 +32,26 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.touch.AbstractStateChangeTouchController;
 import com.android.launcher3.touch.SwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.quickstep.TouchInteractionService;
 import com.android.quickstep.util.SysuiEventLogger;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
 
 /**
  * Touch controller for handling various state transitions in portrait UI.
  */
 public class PortraitStatesTouchController extends AbstractStateChangeTouchController {
 
-    private static final float TOTAL_DISTANCE_MULTIPLIER = 2f;
+    private static final float TOTAL_DISTANCE_MULTIPLIER = 3f;
     private static final float LINEAR_SCALE_LIMIT = 1 / TOTAL_DISTANCE_MULTIPLIER;
 
-    // Much be greater than LINEAR_SCALE_LIMIT;
+    // Must be greater than LINEAR_SCALE_LIMIT;
     private static final float MAXIMUM_DISTANCE_FACTOR = 0.9f;
 
     // Maximum amount to overshoot.
@@ -57,9 +61,6 @@
 
     private InterpolatorWrapper mAllAppsInterpolatorWrapper = new InterpolatorWrapper();
 
-    // If > 0, the animation progress is clamped at that value as long as user is dragging.
-    private float mClampProgressUpdate = -1;
-
     // If true, we will finish the current animation instantly on second touch.
     private boolean mFinishFastOnSecondTouch;
 
@@ -129,29 +130,26 @@
             directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
             mStartContainerType = ContainerType.HOTSEAT;
         } else if (mLauncher.isInState(OVERVIEW)) {
-            directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
+            directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
             mStartContainerType = ContainerType.TASKSWITCHER;
         } else {
             return 0;
         }
-        mFromState = mLauncher.getStateManager().getState();
-        mToState = getTargetState();
-        if (mFromState == mToState) {
-            return 0;
-        }
         return directionsToDetectScroll;
     }
 
-    protected LauncherState getTargetState() {
-        if (mLauncher.isInState(ALL_APPS)) {
+    @Override
+    protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
+        if (fromState == ALL_APPS) {
             // Should swipe down go to OVERVIEW instead?
             return TouchInteractionService.isConnected() ?
                     mLauncher.getStateManager().getLastState() : NORMAL;
-        } else if (mLauncher.isInState(OVERVIEW)) {
-            return ALL_APPS;
-        } else {
+        } else if (fromState == OVERVIEW) {
+            return isDragTowardPositive ? ALL_APPS : NORMAL;
+        } else if (isDragTowardPositive) {
             return TouchInteractionService.isConnected() ? OVERVIEW : ALL_APPS;
         }
+        return fromState;
     }
 
     private AnimatorSetBuilder getNormalToOverviewAnimation() {
@@ -165,15 +163,6 @@
     }
 
     @Override
-    protected void updateProgress(float fraction) {
-        if (mClampProgressUpdate > 0) {
-            mCurrentAnimation.setPlayFraction(Math.min(fraction, mClampProgressUpdate));
-        } else {
-            super.updateProgress(fraction);
-        }
-    }
-
-    @Override
     protected float initCurrentAnimation() {
         float range = getShiftRange();
         long maxAccuracy = (long) (2 * range);
@@ -188,14 +177,27 @@
         if (mFromState == NORMAL && mToState == OVERVIEW && totalShift != 0) {
             builder = getNormalToOverviewAnimation();
             totalShift = totalShift * TOTAL_DISTANCE_MULTIPLIER;
-            mClampProgressUpdate = MAXIMUM_DISTANCE_FACTOR;
         } else {
             builder = new AnimatorSetBuilder();
-            mClampProgressUpdate = -1;
         }
 
-        mCurrentAnimation = mLauncher.getStateManager()
-                .createAnimationToNewWorkspace(mToState, builder, maxAccuracy);
+        if (mPendingAnimation != null) {
+            mPendingAnimation.finish(false);
+            mPendingAnimation = null;
+        }
+
+        RecentsView recentsView = mLauncher.getOverviewPanel();
+        TaskView taskView = (TaskView) recentsView.getChildAt(recentsView.getNextPage());
+        if (recentsView.shouldSwipeDownLaunchApp() && mFromState == OVERVIEW && mToState == NORMAL
+                && taskView != null) {
+            mPendingAnimation = recentsView.createTaskLauncherAnimation(taskView, maxAccuracy);
+            mPendingAnimation.anim.setInterpolator(Interpolators.ZOOM_IN);
+
+            mCurrentAnimation = AnimatorPlaybackController.wrap(mPendingAnimation.anim, maxAccuracy);
+        } else {
+            mCurrentAnimation = mLauncher.getStateManager()
+                    .createAnimationToNewWorkspace(mToState, builder, maxAccuracy);
+        }
 
         if (totalShift == 0) {
             totalShift = Math.signum(mFromState.ordinal - mToState.ordinal)
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 9f7fef3..124ec20 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -15,11 +15,11 @@
  */
 package com.android.launcher3.uioverrides;
 
-import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATION;
+import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.quickstep.views.LauncherRecentsView.TRANSLATION_X_FACTOR;
 import static com.android.quickstep.views.LauncherRecentsView.TRANSLATION_Y_FACTOR;
+import static com.android.quickstep.views.RecentsView.ADJACENT_SCALE;
 import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
 
 import android.animation.ValueAnimator;
@@ -30,7 +30,6 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.LauncherStateManager.StateHandler;
-import com.android.launcher3.PagedView;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.PropertySetter;
 import com.android.quickstep.views.LauncherRecentsView;
@@ -49,9 +48,9 @@
     @Override
     public void setState(LauncherState state) {
         mRecentsView.setContentAlpha(state.overviewUi ? 1 : 0);
-        float[] translationFactor = state.getOverviewTranslationFactor(mLauncher);
-        mRecentsView.setTranslationXFactor(translationFactor[0]);
-        mRecentsView.setTranslationYFactor(translationFactor[1]);
+        float[] scaleTranslationYFactor = state.getOverviewScaleAndTranslationYFactor(mLauncher);
+        mRecentsView.setAdjacentScale(scaleTranslationYFactor[0]);
+        mRecentsView.setTranslationYFactor(scaleTranslationYFactor[1]);
         if (state.overviewUi) {
             mRecentsView.updateEmptyMessage();
             mRecentsView.resetTaskVisuals();
@@ -61,28 +60,18 @@
     @Override
     public void setStateWithAnimation(final LauncherState toState,
             AnimatorSetBuilder builder, AnimationConfig config) {
-
-        // Scroll to the workspace card before changing to the NORMAL state.
-        LauncherState fromState = mLauncher.getStateManager().getState();
-        int currPage = mRecentsView.getCurrentPage();
-        if (fromState.overviewUi && toState == NORMAL && currPage != 0 && !config.userControlled) {
-            int maxSnapDuration = PagedView.SLOW_PAGE_SNAP_ANIMATION_DURATION;
-            int durationPerPage = maxSnapDuration / 10;
-            int snapDuration = Math.min(maxSnapDuration, durationPerPage * currPage);
-            mRecentsView.snapToPage(0, snapDuration);
-            // Let the snapping animation play for a bit before we translate off screen.
-            builder.setStartDelay(snapDuration / 4);
-        }
-
         PropertySetter setter = config.getProperSetter(builder);
-        float[] translationFactor = toState.getOverviewTranslationFactor(mLauncher);
-        setter.setFloat(mRecentsView, TRANSLATION_X_FACTOR,
-                translationFactor[0],
+        float[] scaleTranslationYFactor = toState.getOverviewScaleAndTranslationYFactor(mLauncher);
+        setter.setFloat(mRecentsView, ADJACENT_SCALE, scaleTranslationYFactor[0],
                 builder.getInterpolator(ANIM_OVERVIEW_TRANSLATION, LINEAR));
-        setter.setFloat(mRecentsView, TRANSLATION_Y_FACTOR,
-                translationFactor[1],
+        setter.setFloat(mRecentsView, TRANSLATION_Y_FACTOR, scaleTranslationYFactor[1],
                 builder.getInterpolator(ANIM_OVERVIEW_TRANSLATION, LINEAR));
-        setter.setFloat(mRecentsView, CONTENT_ALPHA, toState.overviewUi ? 1 : 0, LINEAR);
+        setter.setFloat(mRecentsView, CONTENT_ALPHA, toState.overviewUi ? 1 : 0,
+                AGGRESSIVE_EASE_IN_OUT);
+
+        if (!toState.overviewUi) {
+            builder.addOnFinishRunnable(mRecentsView::resetTaskVisuals);
+        }
 
         if (toState.overviewUi) {
             ValueAnimator updateAnim = ValueAnimator.ofFloat(0, 1);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
index 7b2487a..e73b219 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
@@ -34,7 +34,7 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.PendingAnimation;
+import com.android.launcher3.util.PendingAnimation;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index 3cae371..c1590f6 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.uioverrides;
 
+import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.Utilities.getPrefs;
 import static com.android.quickstep.OverviewInteractionState.KEY_SWIPE_UP_ENABLED;
@@ -25,7 +26,9 @@
 import android.content.SharedPreferences;
 
 import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager.StateHandler;
 import com.android.launcher3.util.TouchController;
 import com.android.quickstep.OverviewInteractionState;
@@ -89,11 +92,15 @@
     }
 
     public static void onLauncherStateOrResumeChanged(Launcher launcher) {
+        LauncherState state = launcher.getStateManager().getState();
+        DeviceProfile profile = launcher.getDeviceProfile();
         WindowManagerWrapper.getInstance().setShelfHeight(
-                launcher.getStateManager().getState() != ALL_APPS &&
-                        launcher.isUserActive() &&
-                        !launcher.getDeviceProfile().isVerticalBarLayout(),
-                launcher.getDeviceProfile().hotseatBarSizePx);
+                state != ALL_APPS && launcher.isUserActive() && !profile.isVerticalBarLayout(),
+                profile.hotseatBarSizePx);
+
+        if (state == NORMAL) {
+            launcher.<RecentsView>getOverviewPanel().setSwipeDownShouldLaunchApp(false);
+        }
     }
 
     public static void onTrimMemory(Context context, int level) {
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index 4e3528c..3e96c44 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -36,8 +36,10 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppTransitionManagerImpl;
 import com.android.launcher3.LauncherInitListener;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.util.ViewOnDrawExecutor;
@@ -80,7 +82,10 @@
 
     ActivityInitListener createActivityInitListener(BiPredicate<T, Boolean> onInitListener);
 
-    void startRecents(Context context, Intent intent, AssistDataReceiver assistDataReceiver,
+    void startRecentsFromSwipe(Intent intent, AssistDataReceiver assistDataReceiver,
+            final RecentsAnimationListener remoteAnimationListener);
+
+    void startRecentsFromButton(Context context, Intent intent,
             RecentsAnimationListener remoteAnimationListener);
 
     @UiThread
@@ -203,21 +208,44 @@
         }
 
         @Override
-        public void startRecents(Context context, Intent intent,
-                AssistDataReceiver assistDataReceiver,
-                RecentsAnimationListener remoteAnimationListener) {
+        public void startRecentsFromSwipe(Intent intent, AssistDataReceiver assistDataReceiver,
+                final RecentsAnimationListener remoteAnimationListener) {
             ActivityManagerWrapper.getInstance().startRecentsActivity(
                     intent, assistDataReceiver, remoteAnimationListener, null, null);
         }
 
+        @Override
+        public void startRecentsFromButton(Context context, Intent intent,
+                RecentsAnimationListener remoteAnimationListener) {
+            // We should use the remove animation for the fallback activity recents button case,
+            // it works better with PiP.  In Launcher, we have already registered the remote
+            // animation definition, which takes priority over explicitly defined remote
+            // animations in the provided activity options when starting the activity, so we
+            // just register a remote animation factory to get a callback to handle this.
+            LauncherAppTransitionManagerImpl appTransitionManager =
+                    (LauncherAppTransitionManagerImpl) getLauncher().getAppTransitionManager();
+            appTransitionManager.setRemoteAnimationOverride(new RecentsAnimationActivityOptions(
+                    remoteAnimationListener, () -> {
+                        // Once the controller is finished, also reset the remote animation override
+                        appTransitionManager.setRemoteAnimationOverride(null);
+                    }));
+            context.startActivity(intent);
+        }
+
+        @Nullable
+        @UiThread
+        private Launcher getLauncher() {
+            LauncherAppState app = LauncherAppState.getInstanceNoCreate();
+            if (app == null) {
+                return null;
+            }
+            return (Launcher) app.getModel().getCallback();
+        }
+
         @Nullable
         @UiThread
         private Launcher getVisibleLaucher() {
-            LauncherAppState app = LauncherAppState.getInstanceNoCreate();
-            if (app == null) {
-                return null;
-            }
-            Launcher launcher = (Launcher) app.getModel().getCallback();
+            Launcher launcher = getLauncher();
             return (launcher != null) && launcher.isStarted() && launcher.hasWindowFocus() ?
                     launcher : null;
         }
@@ -325,12 +353,23 @@
         }
 
         @Override
-        public void startRecents(Context context, Intent intent,
-                AssistDataReceiver assistDataReceiver,
+        public void startRecentsFromSwipe(Intent intent, AssistDataReceiver assistDataReceiver,
                 final RecentsAnimationListener remoteAnimationListener) {
-            ActivityOptions options =
-                    ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat(
-                            new FallbackActivityOptions(remoteAnimationListener), 10000, 10000));
+            // We can use the normal recents animation for swipe up
+            ActivityManagerWrapper.getInstance().startRecentsActivity(
+                    intent, assistDataReceiver, remoteAnimationListener, null, null);
+        }
+
+        @Override
+        public void startRecentsFromButton(Context context, Intent intent,
+                RecentsAnimationListener remoteAnimationListener) {
+            // We should use the remove animation for the fallback activity recents button case,
+            // it works better with PiP. For the fallback activity, we should not have registered
+            // the launcher app transition manager, so we should just start the remote animation here.
+            ActivityOptions options = ActivityOptionsCompat.makeRemoteAnimation(
+                    new RemoteAnimationAdapterCompat(
+                            new RecentsAnimationActivityOptions(remoteAnimationListener, null),
+                            10000, 10000));
             context.startActivity(intent, options.toBundle());
         }
 
diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
index bcc986d..4d695de 100644
--- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
@@ -23,6 +23,7 @@
 import static android.view.MotionEvent.INVALID_POINTER_ID;
 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_BACK;
 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_OVERVIEW;
+import static com.android.systemui.shared.system.NavigationBarCompat.QUICK_STEP_DRAG_SLOP_PX;
 
 import android.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
@@ -79,8 +80,7 @@
     private final PointF mDownPos = new PointF();
     private final PointF mLastPos = new PointF();
     private int mActivePointerId = INVALID_POINTER_ID;
-    private boolean mGestureStarted;
-    private int mTouchSlop;
+    private boolean mPassedInitialSlop;
     private float mStartDisplacement;
     private WindowTransformSwipeHandler mInteractionHandler;
     private int mDisplayRotation;
@@ -121,8 +121,7 @@
                 mActivePointerId = ev.getPointerId(0);
                 mDownPos.set(ev.getX(), ev.getY());
                 mLastPos.set(mDownPos);
-                mTouchSlop = ViewConfiguration.get(this).getScaledPagingTouchSlop();
-                mGestureStarted = false;
+                mPassedInitialSlop = false;
 
                 // Start the window animation on down to give more time for launcher to draw if the
                 // user didn't start the gesture over the back button
@@ -154,11 +153,20 @@
                     break;
                 }
                 mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
+                float displacement = getDisplacement(ev);
+                if (!mPassedInitialSlop && Math.abs(displacement) > QUICK_STEP_DRAG_SLOP_PX) {
+                    mPassedInitialSlop = true;
+                    mStartDisplacement = displacement;
 
-                if (mGestureStarted && mInteractionHandler != null) {
+                    // If we deferred starting the window animation on touch down, then
+                    // start tracking now
+                    if (mIsDeferredDownTarget) {
+                        startTouchTrackingForWindowAnimation(ev.getEventTime());
+                    }
+                }
+
+                if (mPassedInitialSlop && mInteractionHandler != null) {
                     // Move
-                    float displacement = getDisplacement(ev.getX(pointerIndex),
-                            ev.getY(pointerIndex));
                     mInteractionHandler.updateDisplacement(displacement - mStartDisplacement);
                 }
                 break;
@@ -179,7 +187,6 @@
             return;
         }
         // Notify the handler that the gesture has actually started
-        mGestureStarted = true;
         mInteractionHandler.onGestureStarted();
     }
 
@@ -211,7 +218,7 @@
         handler.initWhenReady();
 
         TraceHelper.beginSection("RecentsController");
-        Runnable startActivity = () -> mActivityControlHelper.startRecents(this, mHomeIntent,
+        Runnable startActivity = () -> mActivityControlHelper.startRecentsFromSwipe(mHomeIntent,
                 new AssistDataReceiver() {
                     @Override
                     public void onHandleAssistData(Bundle bundle) {
@@ -244,10 +251,12 @@
 
         if (Looper.myLooper() != Looper.getMainLooper()) {
             startActivity.run();
-            try {
-                drawWaitLock.await(LAUNCHER_DRAW_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-            } catch (Exception e) {
-                // We have waited long enough for launcher to draw
+            if (!mIsDeferredDownTarget) {
+                try {
+                    drawWaitLock.await(LAUNCHER_DRAW_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+                } catch (Exception e) {
+                    // We have waited long enough for launcher to draw
+                }
             }
         } else {
             // We should almost always get touch-town on background thread. This is an edge case
@@ -261,7 +270,7 @@
      * the animation can still be running.
      */
     private void finishTouchTracking() {
-        if (mGestureStarted && mInteractionHandler != null) {
+        if (mPassedInitialSlop && mInteractionHandler != null) {
             mVelocityTracker.computeCurrentVelocity(1000,
                     ViewConfiguration.get(this).getScaledMaximumFlingVelocity());
 
@@ -276,7 +285,8 @@
 
             // Also clean up in case the system has handled the UP and canceled the animation before
             // we had a chance to start the recents animation. In such a case, we will not receive
-            ActivityManagerWrapper.getInstance().cancelRecentsAnimation();
+            ActivityManagerWrapper.getInstance().cancelRecentsAnimation(
+                    true /* restoreHomeStackPosition */);
         }
         mVelocityTracker.recycle();
         mVelocityTracker = null;
@@ -323,17 +333,12 @@
 
     @Override
     public void onQuickStep(float eventX, float eventY, long eventTime) {
-        float displacement = getDisplacement(eventX, eventY);
-        mStartDisplacement = Math.signum(displacement) * mTouchSlop;
-        if (mIsDeferredDownTarget) {
-            // If we deferred starting the window animation on touch down, then
-            // start tracking now
-            startTouchTrackingForWindowAnimation(eventTime);
-        }
         notifyGestureStarted();
     }
 
-    private float getDisplacement(float eventX, float eventY) {
+    private float getDisplacement(MotionEvent ev) {
+        float eventX = ev.getX();
+        float eventY = ev.getY();
         float displacement = eventY - mDownPos.y;
         if (isNavBarOnRight()) {
             displacement = eventX - mDownPos.x;
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 958feb7..8e59578 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -53,6 +53,7 @@
 import com.android.quickstep.util.SysuiEventLogger;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
 import com.android.systemui.shared.recents.view.RecentsTransition;
@@ -77,7 +78,7 @@
     private static final int RID_CANCEL_CONTROLLER = 1;
     private static final int RID_CANCEL_ZOOM_OUT_ANIMATION = 2;
 
-    private static final long RECENTS_LAUNCH_DURATION = 150;
+    private static final long RECENTS_LAUNCH_DURATION = 200;
 
     private static final String TAG = "OverviewCommandHelper";
     private static final boolean DEBUG_START_FALLBACK_ACTIVITY = false;
@@ -151,12 +152,13 @@
     private void initSwipeHandler(ActivityControlHelper helper, long time,
             Consumer<WindowTransformSwipeHandler> onAnimationInitCallback) {
         final int commandId = mCurrentCommandId;
-        RunningTaskInfo taskInfo = ActivityManagerWrapper.getInstance().getRunningTask();
+        final RunningTaskInfo runningTask = ActivityManagerWrapper.getInstance().getRunningTask();
+        final int runningTaskId = runningTask.id;
         final WindowTransformSwipeHandler handler =
-                new WindowTransformSwipeHandler(taskInfo, mContext, time, helper);
+                new WindowTransformSwipeHandler(runningTask, mContext, time, helper);
 
         // Preload the plan
-        mRecentsModel.loadTasks(taskInfo.id, null);
+        mRecentsModel.loadTasks(runningTaskId, null);
         mWindowTransformSwipeHandler = handler;
 
         mTempTaskTargetRect.setEmpty();
@@ -172,13 +174,8 @@
         addFinishCommand(commandId, RID_RESET_SWIPE_HANDLER, handler::reset);
 
         TraceHelper.beginSection(TAG);
-        Runnable startActivity = () -> helper.startRecents(mContext, homeIntent,
-                new AssistDataReceiver() {
-                    @Override
-                    public void onHandleAssistData(Bundle bundle) {
-                        mRecentsModel.preloadAssistData(taskInfo.id, bundle);
-                    }
-                },
+        Runnable startActivity = () -> helper.startRecentsFromButton(mContext,
+                addToIntent(homeIntent),
                 new RecentsAnimationListener() {
                     public void onAnimationStart(
                             RecentsAnimationControllerCompat controller,
@@ -190,11 +187,17 @@
                                     minimizedHomeBounds);
                             mTempTaskTargetRect.set(handler.getTargetRect(mWindowSize));
 
+                            ThumbnailData thumbnail = mAM.getTaskThumbnail(runningTaskId,
+                                    true /* reducedResolution */);
                             mMainThreadExecutor.execute(() -> {
                                 addFinishCommand(commandId,
                                         RID_CANCEL_CONTROLLER, () -> controller.finish(true));
                                 if (commandId == mCurrentCommandId) {
                                     onAnimationInitCallback.accept(handler);
+
+                                    // The animation has started, which means the other activity
+                                    // should be paused, lets update the thumbnail
+                                    handler.switchToScreenshotImmediate(thumbnail);
                                 }
                             });
                         } else {
@@ -230,12 +233,18 @@
         });
         handler.onGestureStarted();
         anim.setDuration(RECENTS_LAUNCH_DURATION);
-        anim.setInterpolator(Interpolators.AGGRESSIVE_EASE);
+        anim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
         anim.start();
         addFinishCommand(commandId, RID_CANCEL_ZOOM_OUT_ANIMATION, anim::cancel);
     }
 
     public void onOverviewToggle() {
+        // If currently screen pinning, do not enter overview
+        if (ActivityManagerWrapper.getInstance().isScreenPinningActive()) {
+            return;
+        }
+
+        ActivityManagerWrapper.getInstance().closeSystemWindows("recentapps");
         long time = SystemClock.elapsedRealtime();
         mMainThreadExecutor.execute(() -> {
             long elapsedTime = time - mLastToggleTime;
@@ -243,40 +252,37 @@
 
             mCurrentCommandId++;
             mTempTaskTargetRect.round(mTaskTargetRect);
-            boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout();
             int runnableCount = mCurrentCommandFinishRunnables.size();
             if (runnableCount > 0) {
                 for (int i = 0; i < runnableCount; i++) {
                     mCurrentCommandFinishRunnables.valueAt(i).run();
                 }
                 mCurrentCommandFinishRunnables.clear();
-                isQuickTap = true;
             }
 
+            // TODO: We need to fix this case with PIP, when an activity first enters PIP, it shows
+            //       the menu activity which takes window focus, prevening the right condition from
+            //       being run below
             ActivityControlHelper helper = getActivityControlHelper();
             RecentsView recents = helper.getVisibleRecentsView();
             if (recents != null) {
-                int childCount = recents.getChildCount();
-                if (childCount != 0) {
-                    ((TaskView) recents.getChildAt(childCount >= 2 ? 1 : 0)).launchTask(true);
+                // Launch the next task
+                recents.showNextTask();
+            } else {
+                if (elapsedTime < ViewConfiguration.getDoubleTapTimeout()) {
+                    // The user tried to launch back into overview too quickly, either after
+                    // launching an app, or before overview has actually shown, just ignore for now
+                    return;
                 }
 
-                // There are not enough tasks. Skip
-                return;
+                // Start overview
+                if (helper.switchToRecentsIfVisible()) {
+                    SysuiEventLogger.writeDummyRecentsTransition(0);
+                    // Do nothing
+                } else {
+                    initSwipeHandler(helper, time, this::startZoomOutAnim);
+                }
             }
-
-            if (isQuickTap) {
-                // Focus last task. Start is on background thread so that all ActivityManager calls
-                // are serialized
-                BackgroundExecutor.get().submit(this::startLastTask);
-                return;
-            }
-            if (helper.switchToRecentsIfVisible()) {
-                SysuiEventLogger.writeDummyRecentsTransition(0);
-                return;
-            }
-
-            initSwipeHandler(helper, time, this::startZoomOutAnim);
         });
     }
 
@@ -357,10 +363,6 @@
         return false;
     }
 
-    public boolean isUsingFallbackActivity() {
-        return DEBUG_START_FALLBACK_ACTIVITY;
-    }
-
     public ActivityControlHelper getActivityControlHelper() {
         if (DEBUG_START_FALLBACK_ACTIVITY) {
             return new FallbackActivityControllerHelper();
diff --git a/quickstep/src/com/android/quickstep/QuickScrubController.java b/quickstep/src/com/android/quickstep/QuickScrubController.java
index 275fd29..fd089b5 100644
--- a/quickstep/src/com/android/quickstep/QuickScrubController.java
+++ b/quickstep/src/com/android/quickstep/QuickScrubController.java
@@ -138,9 +138,8 @@
     }
 
     public void snapToNextTaskIfAvailable() {
-        if (mInQuickScrub && mRecentsView.getChildCount() > 0) {
-            int toPage = mStartedFromHome ? 0 : mRecentsView.getNextPage() + 1;
-            mRecentsView.snapToPage(toPage, QUICK_SCRUB_START_DURATION);
+        if (!mStartedFromHome && mInQuickScrub && mRecentsView.getChildCount() > 0) {
+            mRecentsView.snapToPage(mRecentsView.getNextPage() + 1, QUICK_SCRUB_START_DURATION);
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityOptions.java b/quickstep/src/com/android/quickstep/RecentsAnimationActivityOptions.java
similarity index 71%
rename from quickstep/src/com/android/quickstep/FallbackActivityOptions.java
rename to quickstep/src/com/android/quickstep/RecentsAnimationActivityOptions.java
index 04352c3..a25e192 100644
--- a/quickstep/src/com/android/quickstep/FallbackActivityOptions.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationActivityOptions.java
@@ -24,24 +24,33 @@
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.TransactionCompat;
 import com.android.systemui.shared.system.WindowManagerWrapper;
+import java.util.function.Consumer;
 
 /**
- * Temporary class to create activity options to emulate recents transition for fallback activtiy.
+ * Class to create activity options to emulate recents transition.
  */
-public class FallbackActivityOptions implements RemoteAnimationRunnerCompat {
+public class RecentsAnimationActivityOptions implements RemoteAnimationRunnerCompat {
 
     private final RecentsAnimationListener mListener;
+    private final Runnable mFinishCallback;
 
-    public FallbackActivityOptions(RecentsAnimationListener listener) {
+    public RecentsAnimationActivityOptions(RecentsAnimationListener listener,
+            Runnable finishCallback) {
         mListener = listener;
+        mFinishCallback = finishCallback;
     }
 
     @Override
     public void onAnimationStart(RemoteAnimationTargetCompat[] targetCompats,
             Runnable runnable) {
         showOpeningTarget(targetCompats);
-        DummyRecentsAnimationControllerCompat dummyRecentsAnim =
-                new DummyRecentsAnimationControllerCompat(runnable);
+        RemoteRecentsAnimationControllerCompat dummyRecentsAnim =
+                new RemoteRecentsAnimationControllerCompat(() -> {
+                    runnable.run();
+                    if (mFinishCallback != null) {
+                        mFinishCallback.run();
+                    }
+                });
 
         Rect insets = new Rect();
         WindowManagerWrapper.getInstance().getStableInsets(insets);
@@ -54,23 +63,22 @@
     }
 
     private void showOpeningTarget(RemoteAnimationTargetCompat[] targetCompats) {
+        TransactionCompat t = new TransactionCompat();
         for (RemoteAnimationTargetCompat target : targetCompats) {
-            TransactionCompat t = new TransactionCompat();
             int layer = target.mode == RemoteAnimationTargetCompat.MODE_CLOSING
                     ? Integer.MAX_VALUE
                     : target.prefixOrderIndex;
             t.setLayer(target.leash, layer);
             t.show(target.leash);
-            t.apply();
         }
+        t.apply();
     }
 
-    private static class DummyRecentsAnimationControllerCompat
-            extends RecentsAnimationControllerCompat {
+    private class RemoteRecentsAnimationControllerCompat extends RecentsAnimationControllerCompat {
 
         final Runnable mFinishCallback;
 
-        public DummyRecentsAnimationControllerCompat(Runnable finishCallback) {
+        public RemoteRecentsAnimationControllerCompat(Runnable finishCallback) {
             mFinishCallback = finishCallback;
         }
 
@@ -87,7 +95,8 @@
 
         @Override
         public void finish(boolean toHome) {
-            if (toHome) {
+            // This should never be called with toHome == false
+            if (mFinishCallback != null) {
                 mFinishCallback.run();
             }
         }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
index 4e11220..12f8d52 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
@@ -28,7 +28,9 @@
     public RecentsAnimationControllerCompat controller;
     public RemoteAnimationTargetCompat[] targets;
 
-    private boolean mInputConsumerEnabled;
+    private boolean mInputConsumerEnabled = false;
+    private boolean mBehindSystemBars = true;
+    private boolean mSplitScreenMinimized = false;
 
     public synchronized void setController(
             RecentsAnimationControllerCompat controller, RemoteAnimationTargetCompat[] targets) {
@@ -75,4 +77,42 @@
             });
         }
     }
+
+    public void setAnimationTargetsBehindSystemBars(boolean behindSystemBars) {
+        if (mBehindSystemBars == behindSystemBars) {
+            return;
+        }
+        mBehindSystemBars = behindSystemBars;
+        BackgroundExecutor.get().submit(() -> {
+            synchronized (this) {
+                TraceHelper.partitionSection("RecentsController",
+                        "Setting behind system bars on " + controller);
+                if (controller != null) {
+                    controller.setAnimationTargetsBehindSystemBars(behindSystemBars);
+                }
+            }
+        });
+    }
+
+    /**
+     * NOTE: As a workaround for conflicting animations (Launcher animating the task leash, and
+     * SystemUI resizing the docked stack, which resizes the task), we currently only set the
+     * minimized mode, and not the inverse.
+     * TODO: Synchronize the minimize animation with the launcher animation
+     */
+    public void setSplitScreenMinimizedForTransaction(boolean minimized) {
+        if (mSplitScreenMinimized || !minimized) {
+            return;
+        }
+        mSplitScreenMinimized = minimized;
+        BackgroundExecutor.get().submit(() -> {
+            synchronized (this) {
+                TraceHelper.partitionSection("RecentsController",
+                        "Setting minimize dock on " + controller);
+                if (controller != null) {
+                    controller.setSplitScreenMinimized(minimized);
+                }
+            }
+        });
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 1e43202..4652f2d 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -159,6 +159,16 @@
     }
 
     @Override
+    public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
+        mTaskChangeId++;
+    }
+
+    @Override
+    public void onActivityUnpinned() {
+        mTaskChangeId++;
+    }
+
+    @Override
     public void onTaskStackChanged() {
         mTaskChangeId++;
 
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 1ded4dd..b610f4d 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -134,6 +134,8 @@
             if (triggeredFromAltTab) {
                 setupTouchConsumer(HIT_TARGET_NONE);
                 mEventQueue.onOverviewShownFromAltTab();
+            } else {
+                mOverviewCommandHelper.onOverviewShown();
             }
         }
 
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index 33f1310..f6cf85a 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -196,6 +196,10 @@
     private final long mTouchTimeMs;
     private long mLauncherFrameDrawnTime;
 
+    // Only used with the recents activity, when the screenshot should be fetched at the beginning
+    // of the animation and not at the end when the activity is already paused
+    private boolean mSkipScreenshotAtEndOfTransition;
+
     WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context, long touchTimeMs,
             ActivityControlHelper<T> controller) {
         mContext = context;
@@ -336,6 +340,9 @@
         }
         mWasLauncherAlreadyVisible = alreadyOnHome;
         mActivity = activity;
+        // Override the visibility of the activity until the gesture actually starts and we swipe
+        // up, or until we transition home and the home animation is composed
+        mActivity.setForceInvisible(true);
 
         mRecentsView = activity.getOverviewPanel();
         mQuickScrubController = mRecentsView.getQuickScrubController();
@@ -497,6 +504,7 @@
                 TransactionCompat transaction = new TransactionCompat();
                 for (RemoteAnimationTargetCompat app : mRecentsAnimationWrapper.targets) {
                     if (app.mode == MODE_CLOSING) {
+                        mTmpMatrix.postTranslate(app.position.x, app.position.y);
                         transaction.setMatrix(app.leash, mTmpMatrix)
                                 .setWindowCrop(app.leash, mClipRect);
 
@@ -534,10 +542,10 @@
                     }
                 }
                 if (mRecentsAnimationWrapper.controller != null) {
-
                     // TODO: This logic is spartanic!
-                    mRecentsAnimationWrapper.controller.setAnimationTargetsBehindSystemBars(
-                            shift < 0.12f);
+                    boolean passedThreshold = shift > 0.12f;
+                    mRecentsAnimationWrapper.setAnimationTargetsBehindSystemBars(!passedThreshold);
+                    mRecentsAnimationWrapper.setSplitScreenMinimizedForTransaction(passedThreshold);
                 }
             };
             if (Looper.getMainLooper() == Looper.myLooper()) {
@@ -581,7 +589,6 @@
                     mSourceStackBounds.set(target.sourceContainerBounds);
 
                     initTransitionEndpoints(dp);
-                    break;
                 }
             }
         }
@@ -609,6 +616,9 @@
     private void notifyGestureStarted() {
         final T curActivity = mActivity;
         if (curActivity != null) {
+            // Once the gesture starts, we can no longer transition home through the button, so
+            // reset the force override of the activity visibility
+            mActivity.setForceInvisible(false);
             mActivityControlHelper.onQuickstepGestureStarted(
                     curActivity, mWasLauncherAlreadyVisible);
         }
@@ -724,8 +734,9 @@
                         () -> setStateOnUiThread(STATE_SWITCH_TO_SCREENSHOT_COMPLETE));
             }
         };
+
         synchronized (mRecentsAnimationWrapper) {
-            if (mRecentsAnimationWrapper.controller != null) {
+            if (mRecentsAnimationWrapper.controller != null && !mSkipScreenshotAtEndOfTransition) {
                 TransactionCompat transaction = new TransactionCompat();
                 for (RemoteAnimationTargetCompat app : mRecentsAnimationWrapper.targets) {
                     if (app.mode == MODE_CLOSING) {
@@ -752,6 +763,12 @@
         doLogGesture(true /* toLauncher */);
     }
 
+    @UiThread
+    public void switchToScreenshotImmediate(ThumbnailData thumbnail) {
+        mRecentsView.updateThumbnail(mRunningTaskId, thumbnail);
+        mSkipScreenshotAtEndOfTransition = true;
+    }
+
     private void setupLauncherUiAfterSwipeUpAnimation() {
         if (mLauncherTransitionController != null) {
             mLauncherTransitionController.getAnimationPlayer().end();
@@ -762,6 +779,8 @@
         // Animate the first icon.
         mRecentsView.setFirstTaskIconScaledDown(false /* isScaledDown */, true /* animate */);
 
+        mRecentsView.setSwipeDownShouldLaunchApp(true);
+
         reset();
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index d428f23..6788827 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -20,7 +20,6 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
 
-import android.animation.Animator;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.annotation.TargetApi;
@@ -45,20 +44,6 @@
 @TargetApi(Build.VERSION_CODES.O)
 public class LauncherRecentsView extends RecentsView<Launcher> implements Insettable {
 
-    public static final FloatProperty<LauncherRecentsView> TRANSLATION_X_FACTOR =
-            new FloatProperty<LauncherRecentsView>("translationXFactor") {
-
-                @Override
-                public void setValue(LauncherRecentsView view, float v) {
-                    view.setTranslationXFactor(v);
-                }
-
-                @Override
-                public Float get(LauncherRecentsView view) {
-                    return view.mTranslationXFactor;
-                }
-            };
-
     public static final FloatProperty<LauncherRecentsView> TRANSLATION_Y_FACTOR =
             new FloatProperty<LauncherRecentsView>("translationYFactor") {
 
@@ -74,8 +59,6 @@
             };
 
     @ViewDebug.ExportedProperty(category = "launcher")
-    private float mTranslationXFactor;
-    @ViewDebug.ExportedProperty(category = "launcher")
     private float mTranslationYFactor;
 
     private Rect mPagePadding = new Rect();
@@ -114,11 +97,6 @@
         setTranslationYFactor(mTranslationYFactor);
     }
 
-    public void setTranslationXFactor(float translationFactor) {
-        mTranslationXFactor = translationFactor;
-        invalidate();
-    }
-
     public void setTranslationYFactor(float translationFactor) {
         mTranslationYFactor = translationFactor;
         setTranslationY(mTranslationYFactor * (mPagePadding.bottom - mPagePadding.top));
@@ -127,10 +105,7 @@
     @Override
     public void draw(Canvas canvas) {
         maybeDrawEmptyMessage(canvas);
-        int count = canvas.save();
-        canvas.translate(mTranslationXFactor * (mIsRtl ? -getWidth() : getWidth()), 0);
         super.draw(canvas);
-        canvas.restoreToCount(count);
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index b895155..d95619c 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -59,8 +59,8 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.PendingAnimation;
 import com.android.launcher3.util.Themes;
-import com.android.quickstep.PendingAnimation;
 import com.android.quickstep.QuickScrubController;
 import com.android.quickstep.RecentsAnimationInterpolator;
 import com.android.quickstep.RecentsAnimationInterpolator.TaskWindowBounds;
@@ -85,8 +85,6 @@
 
     public static final FloatProperty<RecentsView> CONTENT_ALPHA =
             new FloatProperty<RecentsView>("contentAlpha") {
-
-
         @Override
         public void setValue(RecentsView recentsView, float v) {
             recentsView.setContentAlpha(v);
@@ -98,6 +96,20 @@
         }
     };
 
+
+
+    public static final FloatProperty<RecentsView> ADJACENT_SCALE =
+            new FloatProperty<RecentsView>("adjacentScale") {
+        @Override
+        public void setValue(RecentsView recentsView, float v) {
+            recentsView.setAdjacentScale(v);
+        }
+
+        @Override
+        public Float get(RecentsView recentsView) {
+            return recentsView.mAdjacentScale;
+        }
+    };
     private static final String PREF_FLIP_RECENTS = "pref_flip_recents";
     private static final int DISMISS_TASK_DURATION = 300;
 
@@ -126,6 +138,26 @@
                 }
             }
         }
+
+        @Override
+        public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
+            // Check this is for the right user
+            if (!checkCurrentUserId(userId, false /* debug */)) {
+                return;
+            }
+
+            // Remove the task immediately from the task list
+            TaskView taskView = getTaskView(taskId);
+            if (taskView != null) {
+                removeView(taskView);
+            }
+        }
+
+        @Override
+        public void onActivityUnpinned() {
+            // TODO: Re-enable layout transitions for addition of the unpinned task
+            reloadIfNeeded();
+        }
     };
 
     private int mLoadPlanId = -1;
@@ -139,11 +171,14 @@
     private boolean mOverviewStateEnabled;
     private boolean mTaskStackListenerRegistered;
     private Runnable mNextPageSwitchRunnable;
+    private boolean mSwipeDownShouldLaunchApp;
 
     private PendingAnimation mPendingAnimation;
 
     @ViewDebug.ExportedProperty(category = "launcher")
     private float mContentAlpha = 1;
+    @ViewDebug.ExportedProperty(category = "launcher")
+    private float mAdjacentScale = 1;
 
     // Keeps track of task views whose visual state should not be reset
     private ArraySet<TaskView> mIgnoreResetTaskViews = new ArraySet<>();
@@ -275,6 +310,9 @@
             mNextPageSwitchRunnable.run();
             mNextPageSwitchRunnable = null;
         }
+        if (getNextPage() > 0) {
+            setSwipeDownShouldLaunchApp(true);
+        }
     }
 
     @Override
@@ -577,6 +615,23 @@
         }
     }
 
+    public void showNextTask() {
+        TaskView runningTaskView = getTaskView(mRunningTaskId);
+        if (runningTaskView == null) {
+            // Launch the first task
+            if (getChildCount() > 0) {
+                ((TaskView) getChildAt(0)).launchTask(true /* animate */);
+            }
+        } else {
+            // Get the next launch task
+            int runningTaskIndex = indexOfChild(runningTaskView);
+            int nextTaskIndex = Math.max(0, Math.min(getChildCount() - 1, runningTaskIndex + 1));
+            if (nextTaskIndex < getChildCount()) {
+                ((TaskView) getChildAt(nextTaskIndex)).launchTask(true /* animate */);
+            }
+        }
+    }
+
     public QuickScrubController getQuickScrubController() {
         return mQuickScrubController;
     }
@@ -601,6 +656,14 @@
         }
     }
 
+    public void setSwipeDownShouldLaunchApp(boolean swipeDownShouldLaunchApp) {
+        mSwipeDownShouldLaunchApp = swipeDownShouldLaunchApp;
+    }
+
+    public boolean shouldSwipeDownLaunchApp() {
+        return mSwipeDownShouldLaunchApp;
+    }
+
     public interface PageCallbacks {
 
         /**
@@ -791,10 +854,58 @@
         setVisibility(alpha > 0 ? VISIBLE : GONE);
     }
 
+    public void setAdjacentScale(float adjacentScale) {
+        if (mAdjacentScale == adjacentScale) {
+            return;
+        }
+        mAdjacentScale = adjacentScale;
+        TaskView currTask = getPageAt(mCurrentPage);
+        if (currTask == null) {
+            return;
+        }
+        currTask.setScaleX(mAdjacentScale);
+        currTask.setScaleY(mAdjacentScale);
+
+        if (mCurrentPage - 1 >= 0) {
+            TaskView adjacentTask = getPageAt(mCurrentPage - 1);
+            float[] scaleAndTranslation = getAdjacentScaleAndTranslation(currTask, adjacentTask,
+                    mAdjacentScale, 0);
+            adjacentTask.setScaleX(scaleAndTranslation[0]);
+            adjacentTask.setScaleY(scaleAndTranslation[0]);
+            adjacentTask.setTranslationX(-scaleAndTranslation[1]);
+            adjacentTask.setTranslationY(scaleAndTranslation[2]);
+        }
+        if (mCurrentPage + 1 < getChildCount()) {
+            TaskView adjacentTask = getPageAt(mCurrentPage + 1);
+            float[] scaleAndTranslation = getAdjacentScaleAndTranslation(currTask, adjacentTask,
+                    mAdjacentScale, 0);
+            adjacentTask.setScaleX(scaleAndTranslation[0]);
+            adjacentTask.setScaleY(scaleAndTranslation[0]);
+            adjacentTask.setTranslationX(scaleAndTranslation[1]);
+            adjacentTask.setTranslationY(scaleAndTranslation[2]);
+        }
+    }
+
+    private float[] getAdjacentScaleAndTranslation(TaskView currTask, TaskView adjacentTask,
+            float currTaskToScale, float currTaskToTranslationY) {
+        float displacement = currTask.getWidth() * (currTaskToScale - currTask.getCurveScale());
+        return new float[] {
+                currTaskToScale * adjacentTask.getCurveScale(),
+                mIsRtl ? -displacement : displacement,
+                currTaskToTranslationY
+        };
+    }
+
     @Override
     public void onViewAdded(View child) {
         super.onViewAdded(child);
         child.setAlpha(mContentAlpha);
+        setAdjacentScale(mAdjacentScale);
+    }
+
+    @Override
+    public TaskView getPageAt(int index) {
+        return (TaskView) getChildAt(index);
     }
 
     public void updateEmptyMessage() {
@@ -872,18 +983,24 @@
         float toScale = endInterpolation.taskScale;
         float toTranslationY = endInterpolation.taskY;
 
-        float displacementX = tv.getWidth() * (toScale - tv.getScaleX());
         if (launchingCenterTask) {
+            TaskView centerTask = getPageAt(centerTaskIndex);
             if (taskIndex - 1 >= 0) {
-                anim.play(createAnimForChild(
-                        taskIndex - 1, toScale, displacementX, toTranslationY));
+                TaskView adjacentTask = getPageAt(taskIndex - 1);
+                float[] scaleAndTranslation = getAdjacentScaleAndTranslation(centerTask,
+                        adjacentTask, toScale, toTranslationY);
+                scaleAndTranslation[1] = -scaleAndTranslation[1];
+                anim.play(createAnimForChild(adjacentTask, scaleAndTranslation));
             }
             if (taskIndex + 1 < getPageCount()) {
-                anim.play(createAnimForChild(
-                        taskIndex + 1, toScale, -displacementX, toTranslationY));
+                TaskView adjacentTask = getPageAt(taskIndex + 1);
+                float[] scaleAndTranslation = getAdjacentScaleAndTranslation(centerTask,
+                        adjacentTask, toScale, toTranslationY);
+                anim.play(createAnimForChild(adjacentTask, scaleAndTranslation));
             }
         } else {
             // We are launching an adjacent task, so parallax the center and other adjacent task.
+            float displacementX = tv.getWidth() * (toScale - tv.getCurveScale());
             anim.play(ObjectAnimator.ofFloat(getPageAt(centerTaskIndex), TRANSLATION_X,
                     mIsRtl ? -displacementX : displacementX));
 
@@ -899,13 +1016,12 @@
         return anim;
     }
 
-    private ObjectAnimator createAnimForChild(int childIndex, float toScale, float tx, float ty) {
-        View child = getChildAt(childIndex);
+    private ObjectAnimator createAnimForChild(View child, float[] toScaleAndTranslation) {
         return ObjectAnimator.ofPropertyValuesHolder(child,
                         new PropertyListBuilder()
-                                .scale(child.getScaleX() * toScale)
-                                .translationY(ty)
-                                .translationX(mIsRtl ? tx : -tx)
+                                .scale(child.getScaleX() * toScaleAndTranslation[0])
+                                .translationX(toScaleAndTranslation[1])
+                                .translationY(toScaleAndTranslation[2])
                                 .build());
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index 4c8d69f..8b41b58 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -54,6 +54,7 @@
 
     private final TaskOverlay mOverlay;
     private final Paint mPaint = new Paint();
+    private final Paint mLockedPaint = new Paint();
 
     private final Matrix mMatrix = new Matrix();
 
@@ -77,6 +78,7 @@
         mFadeLength = getResources().getDimension(R.dimen.task_fade_length);
         mOverlay = TaskOverlayFactory.get(context).createOverlay(this);
         mPaint.setFilterBitmap(true);
+        mLockedPaint.setColor(Color.WHITE);
     }
 
     public void bind() {
@@ -123,14 +125,19 @@
 
     @Override
     protected void onDraw(Canvas canvas) {
-        canvas.drawRoundRect(0, 0, getMeasuredWidth(), getMeasuredHeight(),
-                mCornerRadius, mCornerRadius, mPaint);
+        if (mTask == null) {
+            return;
+        }
+        canvas.drawRoundRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mCornerRadius,
+                mCornerRadius, mTask.isLocked ? mLockedPaint : mPaint);
     }
 
     private void updateThumbnailPaintFilter() {
         int mul = (int) (mDimAlpha * 255);
         if (mBitmapShader != null) {
-            mPaint.setColorFilter(getLightingColorFilter(mul));
+            LightingColorFilter filter = getLightingColorFilter(mul);
+            mPaint.setColorFilter(filter);
+            mLockedPaint.setColorFilter(filter);
         } else {
             mPaint.setColorFilter(null);
             mPaint.setColor(Color.argb(255, mul, mul, mul));
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 57516b0..42da472 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -70,6 +70,7 @@
     private Task mTask;
     private TaskThumbnailView mSnapshotView;
     private ImageView mIconView;
+    private float mCurveScale;
 
     public TaskView(Context context) {
         this(context, null);
@@ -178,9 +179,13 @@
 
         mSnapshotView.setDimAlpha(1 - curveInterpolation * MAX_PAGE_SCRIM_ALPHA);
 
-        float scale = 1 - curveInterpolation * EDGE_SCALE_DOWN_FACTOR;
-        setScaleX(scale);
-        setScaleY(scale);
+        mCurveScale = 1 - curveInterpolation * EDGE_SCALE_DOWN_FACTOR;
+        setScaleX(mCurveScale);
+        setScaleY(mCurveScale);
+    }
+
+    public float getCurveScale() {
+        return mCurveScale;
     }
 
     @Override
diff --git a/res/layout/launcher_preference.xml b/res/layout/launcher_preference.xml
deleted file mode 100644
index ed0ea7c..0000000
--- a/res/layout/launcher_preference.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?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.
--->
-
-<com.android.launcher3.views.HighlightableListView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@android:id/list"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:cacheColorHint="@android:color/transparent"
-    android:clipToPadding="false"
-    android:drawSelectorOnTop="false"
-    android:orientation="vertical"
-    android:scrollbarAlwaysDrawVerticalTrack="true"
-    android:scrollbarStyle="outsideOverlay" />
\ No newline at end of file
diff --git a/res/layout/longpress_options_menu.xml b/res/layout/longpress_options_menu.xml
index 71d117a..168dbc3 100644
--- a/res/layout/longpress_options_menu.xml
+++ b/res/layout/longpress_options_menu.xml
@@ -15,83 +15,11 @@
 -->
 <com.android.launcher3.views.OptionsPopupView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:launcher="http://schemas.android.com/apk/res-auto"
-    android:layout_width="match_parent"
-    android:layout_height="96dp"
+    android:id="@+id/deep_shortcuts_container"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
     android:background="?attr/popupColorPrimary"
+    android:clipToPadding="false"
+    android:clipChildren="false"
     android:elevation="@dimen/deep_shortcuts_elevation"
-    android:orientation="horizontal"
-    launcher:layout_ignoreInsets="true">
-
-    <FrameLayout
-        android:id="@+id/wallpaper_button"
-        android:layout_width="0dp"
-        android:layout_height="match_parent"
-        android:layout_weight="1"
-        android:background="?android:attr/selectableItemBackground">
-
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center"
-            android:drawablePadding="4dp"
-            android:drawableTint="?android:attr/textColorPrimary"
-            android:drawableTop="@drawable/ic_wallpaper"
-            android:fontFamily="sans-serif-condensed"
-            android:gravity="center"
-            android:paddingLeft="16dp"
-            android:paddingRight="16dp"
-            android:text="@string/wallpaper_button_text"
-            android:textColor="?android:attr/textColorPrimary"
-            android:textSize="12sp"/>
-    </FrameLayout>
-
-    <FrameLayout
-        android:id="@+id/widget_button"
-        android:layout_width="0dp"
-        android:layout_height="match_parent"
-        android:layout_weight="1"
-        android:background="?android:attr/selectableItemBackground">
-
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center"
-            android:drawablePadding="4dp"
-            android:drawableTint="?android:attr/textColorPrimary"
-            android:drawableTop="@drawable/ic_widget"
-            android:fontFamily="sans-serif-condensed"
-            android:gravity="center"
-            android:paddingLeft="16dp"
-            android:paddingRight="16dp"
-            android:text="@string/widget_button_text"
-            android:textColor="?android:attr/textColorPrimary"
-            android:textSize="12sp"/>
-
-    </FrameLayout>
-
-    <FrameLayout
-        android:id="@+id/settings_button"
-        android:layout_width="0dp"
-        android:layout_height="match_parent"
-        android:layout_weight="1"
-        android:background="?android:attr/selectableItemBackground">
-
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center"
-            android:drawablePadding="4dp"
-            android:drawableTint="?android:attr/textColorPrimary"
-            android:drawableTop="@drawable/ic_setting"
-            android:fontFamily="sans-serif-condensed"
-            android:gravity="center"
-            android:paddingLeft="16dp"
-            android:paddingRight="16dp"
-            android:text="@string/settings_button_text"
-            android:textColor="?android:attr/textColorPrimary"
-            android:textSize="12sp"/>
-
-    </FrameLayout>
-
-</com.android.launcher3.views.OptionsPopupView>
\ No newline at end of file
+    android:orientation="vertical" />
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index a2f7286..b1ad11e 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -222,4 +222,5 @@
 
 <!-- Overview -->
     <dimen name="options_menu_icon_size">24dp</dimen>
+    <dimen name="options_menu_thumb_size">32dp</dimen>
 </resources>
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index cf2d79f..ae631a4 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -39,6 +39,9 @@
     protected SystemUiController mSystemUiController;
 
     private boolean mStarted;
+    // When the recents animation is running, the visibility of the Launcher is managed by the
+    // animation
+    private boolean mForceInvisible;
     private boolean mUserActive;
 
     public DeviceProfile getDeviceProfile() {
@@ -100,6 +103,7 @@
     @Override
     protected void onStop() {
         mStarted = false;
+        mForceInvisible = false;
         super.onStop();
     }
 
@@ -126,6 +130,22 @@
     }
 
     /**
+     * Used to set the override visibility state, used only to handle the transition home with the
+     * recents animation.
+     * @see LauncherAppTransitionManagerImpl.getWallpaperOpenRunner()
+     */
+    public void setForceInvisible(boolean invisible) {
+        mForceInvisible = invisible;
+    }
+
+    /**
+     * @return Wether this activity should be considered invisible regardless of actual visibility.
+     */
+    public boolean isForceInvisible() {
+        return mForceInvisible;
+    }
+
+    /**
      * Sets the device profile, adjusting it accordingly in case of multi-window
      */
     protected void setDeviceProfile(DeviceProfile dp) {
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 4c11fe6..bde9ad3 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -47,7 +47,7 @@
     private static final String TAG = "BaseDraggingActivity";
 
     // The Intent extra that defines whether to ignore the launch animation
-    protected static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
+    public static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
             "com.android.launcher3.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION";
 
     // When starting an action mode, setting this tag will cause the action mode to be cancelled
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index e2f7488..b410f4f 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -357,11 +357,6 @@
         if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0) {
             mUserEventDispatcher = null;
             initDeviceProfile(mDeviceProfile.inv);
-            FileLog.d(TAG, "Config changed, my orientation=" +
-                    getResources().getConfiguration().orientation +
-                    ", new orientation=" + newConfig.orientation +
-                    ", old orientation=" + mOldConfig.orientation +
-                    ", isTransposed=" + mDeviceProfile.isVerticalBarLayout());
             dispatchDeviceProfileChanged();
 
             getRootView().dispatchInsets();
@@ -1634,30 +1629,6 @@
         }
     }
 
-    /**
-     * Event handler for the wallpaper picker button that appears after a long press
-     * on the home screen.
-     */
-    public void onClickWallpaperPicker(View v) {
-        if (!Utilities.isWallpaperAllowed(this)) {
-            Toast.makeText(this, R.string.msg_disabled_by_admin, Toast.LENGTH_SHORT).show();
-            return;
-        }
-        int pageScroll = mWorkspace.getScrollForPage(mWorkspace.getPageNearestToCenterOfScreen());
-        float offset = mWorkspace.mWallpaperOffset.wallpaperOffsetForScroll(pageScroll);
-        Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER)
-                .putExtra(Utilities.EXTRA_WALLPAPER_OFFSET, offset);
-
-        String pickerPackage = getString(R.string.wallpaper_picker_package);
-        if (!TextUtils.isEmpty(pickerPackage)) {
-            intent.setPackage(pickerPackage);
-        } else {
-            // If there is no target package, use the default intent chooser animation
-            intent.putExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION, true);
-        }
-        startActivitySafely(v, intent, null);
-    }
-
     @TargetApi(Build.VERSION_CODES.M)
     @Override
     public ActivityOptions getActivityLaunchOptions(View v, boolean useDefaultLaunchOptions) {
@@ -2415,7 +2386,7 @@
 
                 // Setting the touch point to (-1, -1) will show the options popup in the center of
                 // the screen.
-                OptionsPopupView.show(this, -1, -1);
+                OptionsPopupView.showDefaultOptions(this, -1, -1);
             }
             return true;
         }
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 8b7ba20..7d208d4 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -34,7 +34,6 @@
 import android.database.Cursor;
 import android.database.SQLException;
 import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
 import android.database.sqlite.SQLiteQueryBuilder;
 import android.database.sqlite.SQLiteStatement;
 import android.net.Uri;
@@ -56,7 +55,6 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.DbDowngradeHelper;
-import com.android.launcher3.model.ModelWriter;
 import com.android.launcher3.provider.LauncherDbUtils;
 import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
 import com.android.launcher3.provider.RestoreDbTask;
@@ -320,11 +318,6 @@
 
     @Override
     public int delete(Uri uri, String selection, String[] selectionArgs) {
-        if (ModelWriter.DEBUG_DELETE) {
-            String args = selectionArgs == null ? null : TextUtils.join(",", selectionArgs);
-            FileLog.d(TAG, "Delete uri=" + uri + ", selection=" + selection
-                    + ", selectionArgs=" + args, new Exception());
-        }
         createDbIfNotExists();
         SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
 
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 21f9d5a..4697b82 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -18,7 +18,6 @@
 import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
 import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
-
 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
 import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
 
@@ -182,12 +181,12 @@
     }
 
     /**
-     * Returns 2 floats designating how much to translate overview:
-     *   X factor is based on width, e.g. 0 is fully onscreen and 1 is fully offscreen
-     *   Y factor is based on padding, e.g. 0 is top aligned and 0.5 is centered vertically
+     * Returns 2 floats designating how to transition overview:
+     *   scale for the current and adjacent pages
+     *   translationY factor where 0 is top aligned and 0.5 is centered vertically
      */
-    public float[] getOverviewTranslationFactor(Launcher launcher) {
-        return new float[] {1f, 0f};
+    public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
+        return new float[] {1.2f, 0.2f};
     }
 
     public void onStateEnabled(Launcher launcher) {
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index ef285df..534c8ae 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -356,7 +356,11 @@
      * starting another animation and may block some launcher interactions while running.
      */
     public void setCurrentAnimation(AnimatorSet anim) {
+        boolean reapplyNeeded = mConfig.mCurrentAnimation != null;
         cancelAnimation();
+        if (reapplyNeeded) {
+            reapplyState();
+        }
         mConfig.setAnimation(anim);
     }
 
diff --git a/src/com/android/launcher3/SettingsActivity.java b/src/com/android/launcher3/SettingsActivity.java
index 7fa0e52..c9bd32b 100644
--- a/src/com/android/launcher3/SettingsActivity.java
+++ b/src/com/android/launcher3/SettingsActivity.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3;
 
+import android.annotation.TargetApi;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Dialog;
@@ -26,22 +27,25 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
+import android.os.Build;
 import android.os.Bundle;
 import android.preference.ListPreference;
 import android.preference.Preference;
 import android.preference.PreferenceFragment;
+import android.preference.PreferenceScreen;
 import android.provider.Settings;
 import android.text.TextUtils;
-import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
 import android.widget.Adapter;
+import android.widget.ListView;
 
 import com.android.launcher3.graphics.IconShapeOverride;
 import com.android.launcher3.notification.NotificationListener;
+import com.android.launcher3.util.ListViewHighlighter;
 import com.android.launcher3.util.SettingsObserver;
 import com.android.launcher3.views.ButtonPreference;
-import com.android.launcher3.views.HighlightableListView;
+
+import java.util.Objects;
 
 /**
  * Settings activity for Launcher. Currently implements the following setting: Allow rotation
@@ -85,12 +89,6 @@
         private boolean mPreferenceHighlighted = false;
 
         @Override
-        public View onCreateView(LayoutInflater inflater, ViewGroup container,
-                Bundle savedInstanceState) {
-            return inflater.inflate(R.layout.launcher_preference, container, false);
-        }
-
-        @Override
         public void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
             if (savedInstanceState != null) {
@@ -145,12 +143,25 @@
         }
 
         private void highlightPreference() {
-            HighlightableListView list = getView().findViewById(android.R.id.list);
             Preference pref = findPreference(mPreferenceKey);
-            Adapter adapter = list.getAdapter();
-            if (adapter == null) {
+            if (pref == null || getPreferenceScreen() == null) {
                 return;
             }
+            PreferenceScreen screen = getPreferenceScreen();
+            if (Utilities.ATLEAST_OREO) {
+                screen = selectPreferenceRecursive(pref, screen);
+            }
+            if (screen == null) {
+                return;
+            }
+
+            View root = screen.getDialog() != null
+                    ? screen.getDialog().getWindow().getDecorView() : getView();
+            ListView list = root.findViewById(android.R.id.list);
+            if (list == null || list.getAdapter() == null) {
+                return;
+            }
+            Adapter adapter = list.getAdapter();
 
             // Find the position
             int position = -1;
@@ -160,7 +171,7 @@
                     break;
                 }
             }
-            list.highlightPosition(position);
+            new ListViewHighlighter(list, position);
             mPreferenceHighlighted = true;
         }
 
@@ -172,6 +183,25 @@
             }
             super.onDestroy();
         }
+
+        @TargetApi(Build.VERSION_CODES.O)
+        private PreferenceScreen selectPreferenceRecursive(
+                Preference pref, PreferenceScreen topParent) {
+            if (!(pref.getParent() instanceof PreferenceScreen)) {
+                return null;
+            }
+
+            PreferenceScreen parent = (PreferenceScreen) pref.getParent();
+            if (Objects.equals(parent.getKey(), topParent.getKey())) {
+                return parent;
+            } else if (selectPreferenceRecursive(parent, topParent) != null) {
+                ((PreferenceScreen) parent.getParent())
+                        .onItemClick(null, null, parent.getOrder(), 0);
+                return parent;
+            } else {
+                return null;
+            }
+        }
     }
 
     /**
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index a39d273..1e2e3b1 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -349,6 +349,11 @@
         }
     }
 
+    public float getWallpaperOffsetForCenterPage() {
+        int pageScroll = getScrollForPage(getPageNearestToCenterOfScreen());
+        return mWallpaperOffset.wallpaperOffsetForScroll(pageScroll);
+    }
+
     public Rect estimateItemPosition(CellLayout cl, int hCell, int vCell, int hSpan, int vSpan) {
         Rect r = new Rect();
         cl.cellToRect(hCell, vCell, hSpan, vSpan, r);
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index fa86906..420a7c4 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -31,7 +31,6 @@
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.graphics.ViewScrim;
-import com.android.launcher3.uioverrides.UiFactory;
 
 /**
  * Manages the animations between each of the workspace states.
@@ -74,11 +73,11 @@
                     propertySetter);
         }
 
-        propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, Interpolators.ZOOM_IN);
+        propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, Interpolators.ZOOM_OUT);
         propertySetter.setFloat(mWorkspace, View.TRANSLATION_X,
-                scaleAndTranslation[1], Interpolators.ZOOM_IN);
+                scaleAndTranslation[1], Interpolators.ZOOM_OUT);
         propertySetter.setFloat(mWorkspace, View.TRANSLATION_Y,
-                scaleAndTranslation[2], Interpolators.ZOOM_IN);
+                scaleAndTranslation[2], Interpolators.ZOOM_OUT);
 
         int elements = state.getVisibleElements(mLauncher);
         float hotseatIconsAlpha = (elements & HOTSEAT_ICONS) != 0 ? 1 : 0;
@@ -113,7 +112,7 @@
         int drawableAlpha = Math.round(pageAlpha * (state.hasWorkspacePageBackground ? 255 : 0));
 
         propertySetter.setInt(cl.getScrimBackground(),
-                DRAWABLE_ALPHA, drawableAlpha, Interpolators.ZOOM_IN);
+                DRAWABLE_ALPHA, drawableAlpha, Interpolators.ZOOM_OUT);
         propertySetter.setFloat(cl.getShortcutsAndWidgets(), View.ALPHA,
                 pageAlpha, pageAlphaProvider.interpolator);
     }
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 8788db4..ed9873e 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -99,6 +99,7 @@
             mAppsView.setAlpha(1);
             mLauncher.getHotseat().setTranslationY(0);
             mLauncher.getWorkspace().getPageIndicator().setTranslationY(0);
+            mLauncher.getDragHandleIndicator().setTranslationY(0);
         }
     }
 
diff --git a/src/com/android/launcher3/anim/AnimatorSetBuilder.java b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
index 9191048..b209a2d 100644
--- a/src/com/android/launcher3/anim/AnimatorSetBuilder.java
+++ b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.anim;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.util.SparseArray;
 import android.view.animation.Interpolator;
@@ -23,6 +24,7 @@
 import com.android.launcher3.LauncherAnimUtils;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Utility class for building animator set
@@ -35,7 +37,7 @@
     protected final ArrayList<Animator> mAnims = new ArrayList<>();
 
     private final SparseArray<Interpolator> mInterpolators = new SparseArray<>();
-    private long mStartDelay = 0;
+    private List<Runnable> mOnFinishRunnables = new ArrayList<>();
 
     /**
      * Associates a tag with all the animations added after this call.
@@ -46,14 +48,24 @@
         mAnims.add(anim);
     }
 
-    public void setStartDelay(long startDelay) {
-        mStartDelay = startDelay;
+    public void addOnFinishRunnable(Runnable onFinishRunnable) {
+        mOnFinishRunnables.add(onFinishRunnable);
     }
 
     public AnimatorSet build() {
         AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
         anim.playTogether(mAnims);
-        anim.setStartDelay(mStartDelay);
+        if (!mOnFinishRunnables.isEmpty()) {
+            anim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    for (Runnable onFinishRunnable : mOnFinishRunnables) {
+                        onFinishRunnable.run();
+                    }
+                    mOnFinishRunnables.clear();
+                }
+            });
+        }
         return anim;
     }
 
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
index 6078776..06ddf22 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -65,15 +65,22 @@
             new PathInterpolator(0.3f, 0f, 0.1f, 1f);
 
     /**
-     * Inversion of zInterpolate, compounded with an ease-out.
+     * Inversion of ZOOM_OUT, compounded with an ease-out.
      */
     public static final Interpolator ZOOM_IN = new Interpolator() {
+        @Override
+        public float getInterpolation(float v) {
+            return DEACCEL_3.getInterpolation(1 - ZOOM_OUT.getInterpolation(1 - v));
+        }
+    };
+
+    public static final Interpolator ZOOM_OUT = new Interpolator() {
 
         private static final float FOCAL_LENGTH = 0.35f;
 
         @Override
         public float getInterpolation(float v) {
-            return DEACCEL_3.getInterpolation(1 - zInterpolate(1 - v));
+            return zInterpolate(v);
         }
 
         /**
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 95e1034..278eefd 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -53,6 +53,7 @@
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.util.InstantAppResolver;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
 import com.android.launcher3.widget.WidgetHostViewLoader;
@@ -82,6 +83,7 @@
     private Bundle mWidgetOptions;
 
     private boolean mFinishOnPause = false;
+    private InstantAppResolver mInstantAppResolver;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -95,6 +97,7 @@
 
         mApp = LauncherAppState.getInstance(this);
         mIdp = mApp.getInvariantDeviceProfile();
+        mInstantAppResolver = InstantAppResolver.newInstance(this);
 
         // Use the application context to get the device profile, as in multiwindow-mode, the
         // confirmation activity might be rotated.
@@ -298,7 +301,7 @@
     private void logCommand(int command) {
         getUserEventDispatcher().dispatchUserEvent(newLauncherEvent(
                 newCommandAction(command),
-                newItemTarget(mWidgetCell.getWidgetView()),
+                newItemTarget(mWidgetCell.getWidgetView(), mInstantAppResolver),
                 newContainerTarget(ContainerType.PINITEM)), null);
     }
 }
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index e60a2c7..5094280 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -76,7 +76,7 @@
     /**
      * Draws the {@link #mView} into the given {@param destCanvas}.
      */
-    private void drawDragView(Canvas destCanvas, float scale) {
+    protected void drawDragView(Canvas destCanvas, float scale) {
         destCanvas.save();
         destCanvas.scale(scale, scale);
 
diff --git a/src/com/android/launcher3/logging/LoggerUtils.java b/src/com/android/launcher3/logging/LoggerUtils.java
index d68ac15..01b1424 100644
--- a/src/com/android/launcher3/logging/LoggerUtils.java
+++ b/src/com/android/launcher3/logging/LoggerUtils.java
@@ -15,10 +15,12 @@
  */
 package com.android.launcher3.logging;
 
+import android.content.Context;
 import android.util.ArrayMap;
 import android.util.SparseArray;
 import android.view.View;
 
+import com.android.launcher3.AppInfo;
 import com.android.launcher3.ButtonDropTarget;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherSettings;
@@ -29,6 +31,7 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import com.android.launcher3.util.InstantAppResolver;
 
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
@@ -127,18 +130,21 @@
         return t;
     }
 
-    public static Target newItemTarget(View v) {
+    public static Target newItemTarget(View v, InstantAppResolver instantAppResolver) {
         return (v.getTag() instanceof ItemInfo)
-                ? newItemTarget((ItemInfo) v.getTag())
+                ? newItemTarget((ItemInfo) v.getTag(), instantAppResolver)
                 : newTarget(Target.Type.ITEM);
     }
 
-    public static Target newItemTarget(ItemInfo info) {
+    public static Target newItemTarget(ItemInfo info, InstantAppResolver instantAppResolver) {
         Target t = newTarget(Target.Type.ITEM);
 
         switch (info.itemType) {
             case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
-                t.itemType = ItemType.APP_ICON;
+                t.itemType = (instantAppResolver != null && info instanceof AppInfo
+                        && instantAppResolver.isInstantApp(((AppInfo) info)) )
+                        ? ItemType.WEB_APP
+                        : ItemType.APP_ICON;
                 t.predictedRank = -100; // Never assigned
                 break;
             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index 627115d..90355bd 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -38,6 +38,7 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import com.android.launcher3.util.InstantAppResolver;
 import com.android.launcher3.util.LogConfig;
 
 import java.util.Locale;
@@ -78,6 +79,7 @@
         ued.mIsInLandscapeMode = dp.isVerticalBarLayout();
         ued.mIsInMultiWindowMode = dp.isMultiWindowMode;
         ued.mUuidStr = uuidStr;
+        ued.mInstantAppResolver = InstantAppResolver.newInstance(context);
         return ued;
     }
 
@@ -126,6 +128,7 @@
     private boolean mIsInMultiWindowMode;
     private boolean mIsInLandscapeMode;
     private String mUuidStr;
+    protected InstantAppResolver mInstantAppResolver;
 
     //                      APP_ICON    SHORTCUT    WIDGET
     // --------------------------------------------------------------
@@ -151,7 +154,7 @@
 
     public void logAppLaunch(View v, Intent intent) {
         LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.TAP),
-                newItemTarget(v), newTarget(Target.Type.CONTAINER));
+                newItemTarget(v, mInstantAppResolver), newTarget(Target.Type.CONTAINER));
 
         if (fillInLogContainerData(event, v)) {
             fillIntentInfo(event.srcTarget[0], intent);
@@ -184,7 +187,7 @@
 
     public void logNotificationLaunch(View v, PendingIntent intent) {
         LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.TAP),
-                newItemTarget(v), newTarget(Target.Type.CONTAINER));
+                newItemTarget(v, mInstantAppResolver), newTarget(Target.Type.CONTAINER));
         if (fillInLogContainerData(event, v)) {
             event.srcTarget[0].packageNameHash = (mUuidStr + intent.getCreatorPackage()).hashCode();
         }
@@ -215,7 +218,7 @@
      */
     public void logActionCommand(int command, View itemView, int srcContainerType) {
         LauncherEvent event = newLauncherEvent(newCommandAction(command),
-                newItemTarget(itemView), newTarget(Target.Type.CONTAINER));
+                newItemTarget(itemView, mInstantAppResolver), newTarget(Target.Type.CONTAINER));
 
         if (fillInLogContainerData(event, itemView)) {
             // TODO: Remove the following two lines once fillInLogContainerData can take in a
@@ -320,7 +323,7 @@
         }
         ItemInfo info = (ItemInfo) icon.getTag();
         LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.LONGPRESS),
-                newItemTarget(info), newTarget(Target.Type.CONTAINER));
+                newItemTarget(info, mInstantAppResolver), newTarget(Target.Type.CONTAINER));
         provider.fillInLogContainerData(icon, info, event.srcTarget[0], event.srcTarget[1]);
         dispatchUserEvent(event, null);
 
@@ -338,9 +341,11 @@
 
     public void logDragNDrop(DropTarget.DragObject dragObj, View dropTargetAsView) {
         LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.DRAGDROP),
-                newItemTarget(dragObj.originalDragInfo), newTarget(Target.Type.CONTAINER));
+                newItemTarget(dragObj.originalDragInfo, mInstantAppResolver),
+                newTarget(Target.Type.CONTAINER));
         event.destTarget = new Target[] {
-                newItemTarget(dragObj.originalDragInfo), newDropTarget(dropTargetAsView)
+                newItemTarget(dragObj.originalDragInfo, mInstantAppResolver),
+                newDropTarget(dropTargetAsView)
         };
 
         dragObj.dragSource.fillInLogContainerData(null, dragObj.originalDragInfo,
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index 72c703b..eba7515 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -50,7 +50,6 @@
 public class ModelWriter {
 
     private static final String TAG = "ModelWriter";
-    public static final boolean DEBUG_DELETE = true;
 
     private final Context mContext;
     private final LauncherModel mModel;
@@ -257,14 +256,6 @@
      * Removes the specified items from the database
      */
     public void deleteItemsFromDatabase(final Iterable<? extends ItemInfo> items) {
-        if (DEBUG_DELETE) {
-            // Log it on the colling thread to get the proper stack trace
-            FileLog.d(TAG, "Starting item deletion", new Exception());
-            for (ItemInfo item : items) {
-                FileLog.d(TAG, "deleting item " + item);
-            }
-            FileLog.d(TAG, "Finished deleting items");
-        }
         ModelVerifier verifier = new ModelVerifier();
 
         mWorkerExecutor.execute(() -> {
@@ -282,11 +273,6 @@
      * Remove the specified folder and all its contents from the database.
      */
     public void deleteFolderAndContentsFromDatabase(final FolderInfo info) {
-        if (DEBUG_DELETE) {
-            // Log it on the colling thread to get the proper stack trace
-            FileLog.d(TAG, "Deleting folder " + info, new Exception());
-        }
-
         ModelVerifier verifier = new ModelVerifier();
 
         mWorkerExecutor.execute(() -> {
diff --git a/src/com/android/launcher3/notification/NotificationItemView.java b/src/com/android/launcher3/notification/NotificationItemView.java
index 2fefa85..32410a6 100644
--- a/src/com/android/launcher3/notification/NotificationItemView.java
+++ b/src/com/android/launcher3/notification/NotificationItemView.java
@@ -20,7 +20,6 @@
 import android.content.Context;
 import android.graphics.Color;
 import android.graphics.Rect;
-import android.support.annotation.Nullable;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup.MarginLayoutParams;
@@ -83,7 +82,7 @@
 
     public void addGutter() {
         if (mGutter == null) {
-            mGutter = mContainer.inflateAndAdd(R.layout.notification_gutter);
+            mGutter = mContainer.inflateAndAdd(R.layout.notification_gutter, mContainer);
         }
     }
 
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
new file mode 100644
index 0000000..bd08aaa
--- /dev/null
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -0,0 +1,470 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3.popup;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.CornerPathEffect;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.ShapeDrawable;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.animation.AccelerateDecelerateInterpolator;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.RevealOutlineAnimation;
+import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.graphics.TriangleShape;
+import com.android.launcher3.util.Themes;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
+
+/**
+ * A container for shortcuts to deep links and notifications associated with an app.
+ */
+public abstract class ArrowPopup extends AbstractFloatingView {
+
+    private final Rect mTempRect = new Rect();
+
+    protected final LayoutInflater mInflater;
+    private final float mOutlineRadius;
+    protected final Launcher mLauncher;
+    protected final boolean mIsRtl;
+
+    private final int mArrayOffset;
+    private final View mArrow;
+
+    protected boolean mIsLeftAligned;
+    protected boolean mIsAboveIcon;
+    private int mGravity;
+
+    protected Animator mOpenCloseAnimator;
+    protected boolean mDeferContainerRemoval;
+    private final Rect mStartRect = new Rect();
+    private final Rect mEndRect = new Rect();
+
+    public ArrowPopup(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mInflater = LayoutInflater.from(context);
+        mOutlineRadius = getResources().getDimension(R.dimen.bg_round_rect_radius);
+        mLauncher = Launcher.getLauncher(context);
+        mIsRtl = Utilities.isRtl(getResources());
+
+        setClipToOutline(true);
+        setOutlineProvider(new ViewOutlineProvider() {
+            @Override
+            public void getOutline(View view, Outline outline) {
+                outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mOutlineRadius);
+            }
+        });
+
+        // Initialize arrow view
+        final Resources resources = getResources();
+        final int arrowWidth = resources.getDimensionPixelSize(R.dimen.popup_arrow_width);
+        final int arrowHeight = resources.getDimensionPixelSize(R.dimen.popup_arrow_height);
+        mArrow = new View(context);
+        mArrow.setLayoutParams(new DragLayer.LayoutParams(arrowWidth, arrowHeight));
+        mArrayOffset = resources.getDimensionPixelSize(R.dimen.popup_arrow_vertical_offset);
+    }
+
+    public ArrowPopup(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ArrowPopup(Context context) {
+        this(context, null, 0);
+    }
+
+    @Override
+    protected void handleClose(boolean animate) {
+        if (animate) {
+            animateClose();
+        } else {
+            closeComplete();
+        }
+    }
+
+    public <T extends View> T inflateAndAdd(int resId, ViewGroup container) {
+        View view = mInflater.inflate(resId, container, false);
+        container.addView(view);
+        return (T) view;
+    }
+
+    /**
+     * Called when all view inflation and reordering in complete.
+     */
+    protected void onInflationComplete(boolean isReversed) { }
+
+    /**
+     * Shows the popup at the desired location, optionally reversing the children.
+     * @param viewsToFlip number of views from the top to to flip in case of reverse order
+     */
+    protected void reorderAndShow(int viewsToFlip) {
+        setVisibility(View.INVISIBLE);
+        mIsOpen = true;
+        mLauncher.getDragLayer().addView(this);
+        orientAboutObject();
+
+        boolean reverseOrder = mIsAboveIcon;
+        if (reverseOrder) {
+            int count = getChildCount();
+            ArrayList<View> allViews = new ArrayList<>(count);
+            for (int i = 0; i < count; i++) {
+                if (i == viewsToFlip) {
+                    Collections.reverse(allViews);
+                }
+                allViews.add(getChildAt(i));
+            }
+            Collections.reverse(allViews);
+            removeAllViews();
+            for (int i = 0; i < count; i++) {
+                addView(allViews.get(i));
+            }
+
+            orientAboutObject();
+        }
+        onInflationComplete(reverseOrder);
+
+        // Add the arrow.
+        final Resources res = getResources();
+        final int arrowCenterOffset = res.getDimensionPixelSize(isAlignedWithStart()
+                ? R.dimen.popup_arrow_horizontal_center_start
+                : R.dimen.popup_arrow_horizontal_center_end);
+        final int halfArrowWidth = res.getDimensionPixelSize(R.dimen.popup_arrow_width) / 2;
+        mLauncher.getDragLayer().addView(mArrow);
+        DragLayer.LayoutParams arrowLp = (DragLayer.LayoutParams) mArrow.getLayoutParams();
+        if (mIsLeftAligned) {
+            mArrow.setX(getX() + arrowCenterOffset - halfArrowWidth);
+        } else {
+            mArrow.setX(getX() + getMeasuredWidth() - arrowCenterOffset - halfArrowWidth);
+        }
+
+        if (Gravity.isVertical(mGravity)) {
+            // This is only true if there wasn't room for the container next to the icon,
+            // so we centered it instead. In that case we don't want to showDefaultOptions the arrow.
+            mArrow.setVisibility(INVISIBLE);
+        } else {
+            ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
+                    arrowLp.width, arrowLp.height, !mIsAboveIcon));
+            Paint arrowPaint = arrowDrawable.getPaint();
+            arrowPaint.setColor(Themes.getAttrColor(mLauncher, R.attr.popupColorPrimary));
+            // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable.
+            int radius = getResources().getDimensionPixelSize(R.dimen.popup_arrow_corner_radius);
+            arrowPaint.setPathEffect(new CornerPathEffect(radius));
+            mArrow.setBackground(arrowDrawable);
+            mArrow.setElevation(getElevation());
+        }
+
+        mArrow.setPivotX(arrowLp.width / 2);
+        mArrow.setPivotY(mIsAboveIcon ? 0 : arrowLp.height);
+
+        animateOpen();
+    }
+
+    protected boolean isAlignedWithStart() {
+        return mIsLeftAligned && !mIsRtl || !mIsLeftAligned && mIsRtl;
+    }
+
+    /**
+     * Provide the location of the target object relative to the dragLayer.
+     */
+    protected abstract void getTargetObjectLocation(Rect outPos);
+
+    /**
+     * Orients this container above or below the given icon, aligning with the left or right.
+     *
+     * These are the preferred orientations, in order (RTL prefers right-aligned over left):
+     * - Above and left-aligned
+     * - Above and right-aligned
+     * - Below and left-aligned
+     * - Below and right-aligned
+     *
+     * So we always align left if there is enough horizontal space
+     * and align above if there is enough vertical space.
+     */
+    protected void orientAboutObject() {
+        measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+        int width = getMeasuredWidth();
+        int extraVerticalSpace = mArrow.getLayoutParams().height + mArrayOffset
+                + getResources().getDimensionPixelSize(R.dimen.popup_vertical_padding);
+        int height = getMeasuredHeight() + extraVerticalSpace;
+
+        getTargetObjectLocation(mTempRect);
+        DragLayer dragLayer = mLauncher.getDragLayer();
+        Rect insets = dragLayer.getInsets();
+
+        // Align left (right in RTL) if there is room.
+        int leftAlignedX = mTempRect.left;
+        int rightAlignedX = mTempRect.right - width;
+        int x = leftAlignedX;
+        boolean canBeLeftAligned = leftAlignedX + width + insets.left
+                < dragLayer.getRight() - insets.right;
+        boolean canBeRightAligned = rightAlignedX > dragLayer.getLeft() + insets.left;
+        if (!canBeLeftAligned || (mIsRtl && canBeRightAligned)) {
+            x = rightAlignedX;
+        }
+        mIsLeftAligned = x == leftAlignedX;
+
+        // Offset x so that the arrow and shortcut icons are center-aligned with the original icon.
+        int iconWidth = mTempRect.width();
+        Resources resources = getResources();
+        int xOffset;
+        if (isAlignedWithStart()) {
+            // Aligning with the shortcut icon.
+            int shortcutIconWidth = resources.getDimensionPixelSize(R.dimen.deep_shortcut_icon_size);
+            int shortcutPaddingStart = resources.getDimensionPixelSize(
+                    R.dimen.popup_padding_start);
+            xOffset = iconWidth / 2 - shortcutIconWidth / 2 - shortcutPaddingStart;
+        } else {
+            // Aligning with the drag handle.
+            int shortcutDragHandleWidth = resources.getDimensionPixelSize(
+                    R.dimen.deep_shortcut_drag_handle_size);
+            int shortcutPaddingEnd = resources.getDimensionPixelSize(
+                    R.dimen.popup_padding_end);
+            xOffset = iconWidth / 2 - shortcutDragHandleWidth / 2 - shortcutPaddingEnd;
+        }
+        x += mIsLeftAligned ? xOffset : -xOffset;
+
+        // Open above icon if there is room.
+        int iconHeight = mTempRect.height();
+        int y = mTempRect.top - height;
+        mIsAboveIcon = y > dragLayer.getTop() + insets.top;
+        if (!mIsAboveIcon) {
+            y = mTempRect.top + iconHeight + extraVerticalSpace;
+        }
+
+        // Insets are added later, so subtract them now.
+        if (mIsRtl) {
+            x += insets.right;
+        } else {
+            x -= insets.left;
+        }
+        y -= insets.top;
+
+        mGravity = 0;
+        if (y + height > dragLayer.getBottom() - insets.bottom) {
+            // The container is opening off the screen, so just center it in the drag layer instead.
+            mGravity = Gravity.CENTER_VERTICAL;
+            // Put the container next to the icon, preferring the right side in ltr (left in rtl).
+            int rightSide = leftAlignedX + iconWidth - insets.left;
+            int leftSide = rightAlignedX - iconWidth - insets.left;
+            if (!mIsRtl) {
+                if (rightSide + width < dragLayer.getRight()) {
+                    x = rightSide;
+                    mIsLeftAligned = true;
+                } else {
+                    x = leftSide;
+                    mIsLeftAligned = false;
+                }
+            } else {
+                if (leftSide > dragLayer.getLeft()) {
+                    x = leftSide;
+                    mIsLeftAligned = false;
+                } else {
+                    x = rightSide;
+                    mIsLeftAligned = true;
+                }
+            }
+            mIsAboveIcon = true;
+        }
+
+        setX(x);
+        if (Gravity.isVertical(mGravity)) {
+            return;
+        }
+
+        DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
+        DragLayer.LayoutParams arrowLp = (DragLayer.LayoutParams) mArrow.getLayoutParams();
+        if (mIsAboveIcon) {
+            arrowLp.gravity = lp.gravity = Gravity.BOTTOM;
+            lp.bottomMargin =
+                    mLauncher.getDragLayer().getHeight() - y - getMeasuredHeight() - insets.top;
+            arrowLp.bottomMargin = lp.bottomMargin - arrowLp.height - mArrayOffset - insets.bottom;
+        } else {
+            arrowLp.gravity = lp.gravity = Gravity.TOP;
+            lp.topMargin = y + insets.top;
+            arrowLp.topMargin = lp.topMargin - insets.top - arrowLp.height - mArrayOffset;
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+
+        // enforce contained is within screen
+        DragLayer dragLayer = mLauncher.getDragLayer();
+        if (getTranslationX() + l < 0 || getTranslationX() + r > dragLayer.getWidth()) {
+            // If we are still off screen, center horizontally too.
+            mGravity |= Gravity.CENTER_HORIZONTAL;
+        }
+
+        if (Gravity.isHorizontal(mGravity)) {
+            setX(dragLayer.getWidth() / 2 - getMeasuredWidth() / 2);
+            mArrow.setVisibility(INVISIBLE);
+        }
+        if (Gravity.isVertical(mGravity)) {
+            setY(dragLayer.getHeight() / 2 - getMeasuredHeight() / 2);
+        }
+    }
+
+    private void animateOpen() {
+        setVisibility(View.VISIBLE);
+
+        final AnimatorSet openAnim = LauncherAnimUtils.createAnimatorSet();
+        final Resources res = getResources();
+        final long revealDuration = (long) res.getInteger(R.integer.config_popupOpenCloseDuration);
+        final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator();
+
+        // Rectangular reveal.
+        final ValueAnimator revealAnim = createOpenCloseOutlineProvider()
+                .createRevealAnimator(this, false);
+        revealAnim.setDuration(revealDuration);
+        revealAnim.setInterpolator(revealInterpolator);
+
+        Animator fadeIn = ObjectAnimator.ofFloat(this, ALPHA, 0, 1);
+        fadeIn.setDuration(revealDuration);
+        fadeIn.setInterpolator(revealInterpolator);
+        openAnim.play(fadeIn);
+
+        // Animate the arrow.
+        mArrow.setScaleX(0);
+        mArrow.setScaleY(0);
+        Animator arrowScale = ObjectAnimator.ofFloat(mArrow, LauncherAnimUtils.SCALE_PROPERTY, 1)
+                .setDuration(res.getInteger(R.integer.config_popupArrowOpenDuration));
+
+        openAnim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mOpenCloseAnimator = null;
+                sendCustomAccessibilityEvent(
+                        ArrowPopup.this,
+                        AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
+                        getContext().getString(R.string.action_deep_shortcut));
+            }
+        });
+
+        mOpenCloseAnimator = openAnim;
+        openAnim.playSequentially(revealAnim, arrowScale);
+        openAnim.start();
+    }
+
+    protected void animateClose() {
+        if (!mIsOpen) {
+            return;
+        }
+        mEndRect.setEmpty();
+        if (getOutlineProvider() instanceof RevealOutlineAnimation) {
+            ((RevealOutlineAnimation) getOutlineProvider()).getOutline(mEndRect);
+        }
+        if (mOpenCloseAnimator != null) {
+            mOpenCloseAnimator.cancel();
+        }
+        mIsOpen = false;
+
+        final AnimatorSet closeAnim = LauncherAnimUtils.createAnimatorSet();
+        // Hide the arrow
+        closeAnim.play(ObjectAnimator.ofFloat(mArrow, LauncherAnimUtils.SCALE_PROPERTY, 0));
+        closeAnim.play(ObjectAnimator.ofFloat(mArrow, ALPHA, 0));
+
+        final Resources res = getResources();
+        final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator();
+
+        // Rectangular reveal (reversed).
+        final ValueAnimator revealAnim = createOpenCloseOutlineProvider()
+                .createRevealAnimator(this, true);
+        revealAnim.setInterpolator(revealInterpolator);
+        closeAnim.play(revealAnim);
+
+        Animator fadeOut = ObjectAnimator.ofFloat(this, ALPHA, 0);
+        fadeOut.setInterpolator(revealInterpolator);
+        closeAnim.play(fadeOut);
+
+        onCreateCloseAnimation(closeAnim);
+        closeAnim.setDuration((long) res.getInteger(R.integer.config_popupOpenCloseDuration));
+        closeAnim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mOpenCloseAnimator = null;
+                if (mDeferContainerRemoval) {
+                    setVisibility(INVISIBLE);
+                } else {
+                    closeComplete();
+                }
+            }
+        });
+        mOpenCloseAnimator = closeAnim;
+        closeAnim.start();
+    }
+
+    /**
+     * Called when creating the close transition allowing subclass can add additional animations.
+     */
+    protected void onCreateCloseAnimation(AnimatorSet anim) { }
+
+    private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() {
+        int arrowCenterX = getResources().getDimensionPixelSize(mIsLeftAligned ^ mIsRtl ?
+                R.dimen.popup_arrow_horizontal_center_start:
+                R.dimen.popup_arrow_horizontal_center_end);
+        if (!mIsLeftAligned) {
+            arrowCenterX = getMeasuredWidth() - arrowCenterX;
+        }
+        int arrowCenterY = mIsAboveIcon ? getMeasuredHeight() : 0;
+
+        mStartRect.set(arrowCenterX, arrowCenterY, arrowCenterX, arrowCenterY);
+        if (mEndRect.isEmpty()) {
+            mEndRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
+        }
+
+        return new RoundedRectRevealOutlineProvider
+                (mOutlineRadius, mOutlineRadius, mStartRect, mEndRect);
+    }
+
+    /**
+     * Closes the popup without animation.
+     */
+    protected void closeComplete() {
+        if (mOpenCloseAnimator != null) {
+            mOpenCloseAnimator.cancel();
+            mOpenCloseAnimator = null;
+        }
+        mIsOpen = false;
+        mDeferContainerRemoval = false;
+        mLauncher.getDragLayer().removeView(this);
+        mLauncher.getDragLayer().removeView(mArrow);
+    }
+}
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 033fdf8..422a4ec 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -16,36 +16,28 @@
 
 package com.android.launcher3.popup;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
+import static com.android.launcher3.notification.NotificationMainView.NOTIFICATION_ITEM_INFO;
+import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS;
+import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS_IF_NOTIFICATIONS;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+
 import android.animation.AnimatorSet;
 import android.animation.LayoutTransition;
-import android.animation.ObjectAnimator;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
 import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.CornerPathEffect;
-import android.graphics.Outline;
-import android.graphics.Paint;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
-import android.graphics.drawable.ShapeDrawable;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
-import android.view.ViewOutlineProvider;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.animation.AccelerateDecelerateInterpolator;
 import android.widget.ImageView;
 
 import com.android.launcher3.AbstractFloatingView;
@@ -56,20 +48,15 @@
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate;
-import com.android.launcher3.anim.RevealOutlineAnimation;
-import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
 import com.android.launcher3.badge.BadgeInfo;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DragView;
-import com.android.launcher3.graphics.TriangleShape;
 import com.android.launcher3.logging.LoggerUtils;
 import com.android.launcher3.notification.NotificationInfo;
 import com.android.launcher3.notification.NotificationItemView;
@@ -79,85 +66,38 @@
 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
 import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.util.Themes;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
-import static com.android.launcher3.notification.NotificationMainView.NOTIFICATION_ITEM_INFO;
-import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS;
-import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS_IF_NOTIFICATIONS;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.Target;
-
 /**
  * A container for shortcuts to deep links and notifications associated with an app.
  */
 @TargetApi(Build.VERSION_CODES.N)
-public class PopupContainerWithArrow extends AbstractFloatingView implements DragSource,
+public class PopupContainerWithArrow extends ArrowPopup implements DragSource,
         DragController.DragListener, View.OnLongClickListener,
         View.OnTouchListener {
 
     private final List<DeepShortcutView> mShortcuts = new ArrayList<>();
     private final PointF mInterceptTouchDown = new PointF();
-    private final Rect mTempRect = new Rect();
     private final Point mIconLastTouchPos = new Point();
 
     private final int mStartDragThreshold;
-    private final LayoutInflater mInflater;
-    private final float mOutlineRadius;
-    private final Launcher mLauncher;
     private final LauncherAccessibilityDelegate mAccessibilityDelegate;
-    private final boolean mIsRtl;
-
-    private final int mArrayOffset;
-    private final View mArrow;
 
     private BubbleTextView mOriginalIcon;
     private NotificationItemView mNotificationItemView;
+    private int mNumNotifications;
 
     private ViewGroup mSystemShortcutContainer;
 
-    private boolean mIsLeftAligned;
-    protected boolean mIsAboveIcon;
-    private int mNumNotifications;
-    private int mGravity;
-
-    protected Animator mOpenCloseAnimator;
-    protected boolean mDeferContainerRemoval;
-    private final Rect mStartRect = new Rect();
-    private final Rect mEndRect = new Rect();
-
     public PopupContainerWithArrow(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         mStartDragThreshold = getResources().getDimensionPixelSize(
                 R.dimen.deep_shortcuts_start_drag_threshold);
-        mInflater = LayoutInflater.from(context);
-        mOutlineRadius = getResources().getDimension(R.dimen.bg_round_rect_radius);
-        mLauncher = Launcher.getLauncher(context);
         mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(mLauncher);
-        mIsRtl = Utilities.isRtl(getResources());
-
-        setClipToOutline(true);
-        setOutlineProvider(new ViewOutlineProvider() {
-            @Override
-            public void getOutline(View view, Outline outline) {
-                outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mOutlineRadius);
-            }
-        });
-
-        // Initialize arrow view
-        final Resources resources = getResources();
-        final int arrowWidth = resources.getDimensionPixelSize(R.dimen.popup_arrow_width);
-        final int arrowHeight = resources.getDimensionPixelSize(R.dimen.popup_arrow_height);
-        mArrow = new View(context);
-        mArrow.setLayoutParams(new DragLayer.LayoutParams(arrowWidth, arrowHeight));
-        mArrayOffset = resources.getDimensionPixelSize(R.dimen.popup_arrow_vertical_offset);
     }
 
     public PopupContainerWithArrow(Context context, AttributeSet attrs) {
@@ -222,21 +162,6 @@
         return false;
     }
 
-    @Override
-    protected void handleClose(boolean animate) {
-        if (animate) {
-            animateClose();
-        } else {
-            closeComplete();
-        }
-    }
-
-    public  <T extends View> T inflateAndAdd(int resId) {
-        View view = mInflater.inflate(resId, this, false);
-        addView(view);
-        return (T) view;
-    }
-
     /**
      * Shows the notifications and deep shortcuts associated with {@param icon}.
      * @return the container if shown or null.
@@ -267,13 +192,30 @@
         return container;
     }
 
+    @Override
+    protected void onInflationComplete(boolean isReversed) {
+        if (isReversed && mNotificationItemView != null) {
+            mNotificationItemView.inverseGutterMargin();
+        }
+
+        // Update dividers
+        int count = getChildCount();
+        DeepShortcutView lastView = null;
+        for (int i = 0; i < count; i++) {
+            View view = getChildAt(i);
+            if (view.getVisibility() == VISIBLE && view instanceof DeepShortcutView) {
+                if (lastView != null) {
+                    lastView.setDividerVisibility(VISIBLE);
+                }
+                lastView = (DeepShortcutView) view;
+                lastView.setDividerVisibility(INVISIBLE);
+            }
+        }
+    }
+
     private void populateAndShow(final BubbleTextView originalIcon, final List<String> shortcutIds,
             final List<NotificationKeyData> notificationKeys, List<SystemShortcut> systemShortcuts) {
         mNumNotifications = notificationKeys.size();
-
-        setVisibility(View.INVISIBLE);
-        mLauncher.getDragLayer().addView(this);
-
         mOriginalIcon = originalIcon;
 
         // Add views
@@ -295,17 +237,15 @@
             }
 
             for (int i = shortcutIds.size(); i > 0; i--) {
-                mShortcuts.add(inflateAndAdd(R.layout.deep_shortcut));
+                mShortcuts.add(inflateAndAdd(R.layout.deep_shortcut, this));
             }
             updateHiddenShortcuts();
 
             if (!systemShortcuts.isEmpty()) {
-                mSystemShortcutContainer = inflateAndAdd(R.layout.system_shortcut_icons);
+                mSystemShortcutContainer = inflateAndAdd(R.layout.system_shortcut_icons, this);
                 for (SystemShortcut shortcut : systemShortcuts) {
-                    View view = mInflater.inflate(R.layout.system_shortcut_icon_only,
-                            mSystemShortcutContainer, false);
-                    mSystemShortcutContainer.addView(view);
-                    initializeSystemShortcut(view, shortcut);
+                    initializeSystemShortcut(
+                            R.layout.system_shortcut_icon_only, mSystemShortcutContainer, shortcut);
                 }
             }
         } else if (!systemShortcuts.isEmpty()) {
@@ -314,68 +254,11 @@
             }
 
             for (SystemShortcut shortcut : systemShortcuts) {
-                initializeSystemShortcut(inflateAndAdd(R.layout.system_shortcut), shortcut);
+                initializeSystemShortcut(R.layout.system_shortcut, this, shortcut);
             }
         }
-        orientAboutIcon();
 
-        boolean reverseOrder = mIsAboveIcon;
-        if (reverseOrder) {
-            int count = getChildCount();
-            ArrayList<View> allViews = new ArrayList<>(count);
-            for (int i = 0; i < count; i++) {
-                if (i == viewsToFlip) {
-                    Collections.reverse(allViews);
-                }
-                allViews.add(getChildAt(i));
-            }
-            Collections.reverse(allViews);
-            removeAllViews();
-            for (int i = 0; i < count; i++) {
-                addView(allViews.get(i));
-            }
-            if (mNotificationItemView != null) {
-                mNotificationItemView.inverseGutterMargin();
-            }
-
-            orientAboutIcon();
-        }
-        updateDividers();
-
-        // Add the arrow.
-        final Resources res = getResources();
-        final int arrowCenterOffset = res.getDimensionPixelSize(isAlignedWithStart()
-                ? R.dimen.popup_arrow_horizontal_center_start
-                : R.dimen.popup_arrow_horizontal_center_end);
-        final int halfArrowWidth = res.getDimensionPixelSize(R.dimen.popup_arrow_width) / 2;
-        mLauncher.getDragLayer().addView(mArrow);
-        DragLayer.LayoutParams arrowLp = (DragLayer.LayoutParams) mArrow.getLayoutParams();
-        if (mIsLeftAligned) {
-            mArrow.setX(getX() + arrowCenterOffset - halfArrowWidth);
-        } else {
-            mArrow.setX(getX() + getMeasuredWidth() - arrowCenterOffset - halfArrowWidth);
-        }
-
-        if (Gravity.isVertical(mGravity)) {
-            // This is only true if there wasn't room for the container next to the icon,
-            // so we centered it instead. In that case we don't want to show the arrow.
-            mArrow.setVisibility(INVISIBLE);
-        } else {
-            ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
-                    arrowLp.width, arrowLp.height, !mIsAboveIcon));
-            Paint arrowPaint = arrowDrawable.getPaint();
-            arrowPaint.setColor(Themes.getAttrColor(mLauncher, R.attr.popupColorPrimary));
-            // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable.
-            int radius = getResources().getDimensionPixelSize(R.dimen.popup_arrow_corner_radius);
-            arrowPaint.setPathEffect(new CornerPathEffect(radius));
-            mArrow.setBackground(arrowDrawable);
-            mArrow.setElevation(getElevation());
-        }
-
-        mArrow.setPivotX(arrowLp.width / 2);
-        mArrow.setPivotY(mIsAboveIcon ? 0 : arrowLp.height);
-
-        animateOpen();
+        reorderAndShow(viewsToFlip);
 
         ItemInfo originalItemInfo = (ItemInfo) originalIcon.getTag();
         int numShortcuts = mShortcuts.size() + systemShortcuts.size();
@@ -401,189 +284,15 @@
                 this, shortcutIds, mShortcuts, notificationKeys));
     }
 
-    protected boolean isAlignedWithStart() {
-        return mIsLeftAligned && !mIsRtl || !mIsLeftAligned && mIsRtl;
-    }
-
-    /**
-     * Orients this container above or below the given icon, aligning with the left or right.
-     *
-     * These are the preferred orientations, in order (RTL prefers right-aligned over left):
-     * - Above and left-aligned
-     * - Above and right-aligned
-     * - Below and left-aligned
-     * - Below and right-aligned
-     *
-     * So we always align left if there is enough horizontal space
-     * and align above if there is enough vertical space.
-     */
-    protected void orientAboutIcon() {
-        measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
-        int width = getMeasuredWidth();
-        int extraVerticalSpace = mArrow.getLayoutParams().height + mArrayOffset
-                + getResources().getDimensionPixelSize(R.dimen.popup_vertical_padding);
-        int height = getMeasuredHeight() + extraVerticalSpace;
-
-        DragLayer dragLayer = mLauncher.getDragLayer();
-        dragLayer.getDescendantRectRelativeToSelf(mOriginalIcon, mTempRect);
-        Rect insets = dragLayer.getInsets();
-
-        // Align left (right in RTL) if there is room.
-        int leftAlignedX = mTempRect.left + mOriginalIcon.getPaddingLeft();
-        int rightAlignedX = mTempRect.right - width - mOriginalIcon.getPaddingRight();
-        int x = leftAlignedX;
-        boolean canBeLeftAligned = leftAlignedX + width + insets.left
-                < dragLayer.getRight() - insets.right;
-        boolean canBeRightAligned = rightAlignedX > dragLayer.getLeft() + insets.left;
-        if (!canBeLeftAligned || (mIsRtl && canBeRightAligned)) {
-            x = rightAlignedX;
-        }
-        mIsLeftAligned = x == leftAlignedX;
-
-        // Offset x so that the arrow and shortcut icons are center-aligned with the original icon.
-        int iconWidth = mOriginalIcon.getWidth()
-                - mOriginalIcon.getTotalPaddingLeft() - mOriginalIcon.getTotalPaddingRight();
-        iconWidth *= mOriginalIcon.getScaleX();
-        Resources resources = getResources();
-        int xOffset;
-        if (isAlignedWithStart()) {
-            // Aligning with the shortcut icon.
-            int shortcutIconWidth = resources.getDimensionPixelSize(R.dimen.deep_shortcut_icon_size);
-            int shortcutPaddingStart = resources.getDimensionPixelSize(
-                    R.dimen.popup_padding_start);
-            xOffset = iconWidth / 2 - shortcutIconWidth / 2 - shortcutPaddingStart;
-        } else {
-            // Aligning with the drag handle.
-            int shortcutDragHandleWidth = resources.getDimensionPixelSize(
-                    R.dimen.deep_shortcut_drag_handle_size);
-            int shortcutPaddingEnd = resources.getDimensionPixelSize(
-                    R.dimen.popup_padding_end);
-            xOffset = iconWidth / 2 - shortcutDragHandleWidth / 2 - shortcutPaddingEnd;
-        }
-        x += mIsLeftAligned ? xOffset : -xOffset;
-
-        // Open above icon if there is room.
-        int iconHeight = getIconHeightForPopupPlacement();
-        int y = mTempRect.top + mOriginalIcon.getPaddingTop() - height;
-        mIsAboveIcon = y > dragLayer.getTop() + insets.top;
-        if (!mIsAboveIcon) {
-            y = mTempRect.top + mOriginalIcon.getPaddingTop() + iconHeight + extraVerticalSpace;
-        }
-
-        // Insets are added later, so subtract them now.
-        if (mIsRtl) {
-            x += insets.right;
-        } else {
-            x -= insets.left;
-        }
-        y -= insets.top;
-
-        mGravity = 0;
-        if (y + height > dragLayer.getBottom() - insets.bottom) {
-            // The container is opening off the screen, so just center it in the drag layer instead.
-            mGravity = Gravity.CENTER_VERTICAL;
-            // Put the container next to the icon, preferring the right side in ltr (left in rtl).
-            int rightSide = leftAlignedX + iconWidth - insets.left;
-            int leftSide = rightAlignedX - iconWidth - insets.left;
-            if (!mIsRtl) {
-                if (rightSide + width < dragLayer.getRight()) {
-                    x = rightSide;
-                    mIsLeftAligned = true;
-                } else {
-                    x = leftSide;
-                    mIsLeftAligned = false;
-                }
-            } else {
-                if (leftSide > dragLayer.getLeft()) {
-                    x = leftSide;
-                    mIsLeftAligned = false;
-                } else {
-                    x = rightSide;
-                    mIsLeftAligned = true;
-                }
-            }
-            mIsAboveIcon = true;
-        }
-
-        setX(x);
-        if (Gravity.isVertical(mGravity)) {
-            return;
-        }
-
-        DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
-        DragLayer.LayoutParams arrowLp = (DragLayer.LayoutParams) mArrow.getLayoutParams();
-        if (mIsAboveIcon) {
-            arrowLp.gravity = lp.gravity = Gravity.BOTTOM;
-            lp.bottomMargin =
-                    mLauncher.getDragLayer().getHeight() - y - getMeasuredHeight() - insets.top;
-            arrowLp.bottomMargin = lp.bottomMargin - arrowLp.height - mArrayOffset - insets.bottom;
-        } else {
-            arrowLp.gravity = lp.gravity = Gravity.TOP;
-            lp.topMargin = y + insets.top;
-            arrowLp.topMargin = lp.topMargin - insets.top - arrowLp.height - mArrayOffset;
-        }
-    }
-
     @Override
-    protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        super.onLayout(changed, l, t, r, b);
-
-        // enforce contained is within screen
-        DragLayer dragLayer = mLauncher.getDragLayer();
-        if (getTranslationX() + l < 0 || getTranslationX() + r > dragLayer.getWidth()) {
-            // If we are still off screen, center horizontally too.
-            mGravity |= Gravity.CENTER_HORIZONTAL;
-        }
-
-        if (Gravity.isHorizontal(mGravity)) {
-            setX(dragLayer.getWidth() / 2 - getMeasuredWidth() / 2);
-            mArrow.setVisibility(INVISIBLE);
-        }
-        if (Gravity.isVertical(mGravity)) {
-            setY(dragLayer.getHeight() / 2 - getMeasuredHeight() / 2);
-        }
-    }
-
-    protected void animateOpen() {
-        setVisibility(View.VISIBLE);
-        mIsOpen = true;
-
-        final AnimatorSet openAnim = LauncherAnimUtils.createAnimatorSet();
-        final Resources res = getResources();
-        final long revealDuration = (long) res.getInteger(R.integer.config_popupOpenCloseDuration);
-        final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator();
-
-        // Rectangular reveal.
-        final ValueAnimator revealAnim = createOpenCloseOutlineProvider()
-                .createRevealAnimator(this, false);
-        revealAnim.setDuration(revealDuration);
-        revealAnim.setInterpolator(revealInterpolator);
-
-        Animator fadeIn = ObjectAnimator.ofFloat(this, ALPHA, 0, 1);
-        fadeIn.setDuration(revealDuration);
-        fadeIn.setInterpolator(revealInterpolator);
-        openAnim.play(fadeIn);
-
-        // Animate the arrow.
-        mArrow.setScaleX(0);
-        mArrow.setScaleY(0);
-        Animator arrowScale = ObjectAnimator.ofFloat(mArrow, LauncherAnimUtils.SCALE_PROPERTY, 1)
-                .setDuration(res.getInteger(R.integer.config_popupArrowOpenDuration));
-
-        openAnim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mOpenCloseAnimator = null;
-                sendCustomAccessibilityEvent(
-                        PopupContainerWithArrow.this,
-                        AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
-                        getContext().getString(R.string.action_deep_shortcut));
-            }
-        });
-
-        mOpenCloseAnimator = openAnim;
-        openAnim.playSequentially(revealAnim, arrowScale);
-        openAnim.start();
+    protected void getTargetObjectLocation(Rect outPos) {
+        mLauncher.getDragLayer().getDescendantRectRelativeToSelf(mOriginalIcon, outPos);
+        outPos.top += mOriginalIcon.getPaddingTop();
+        outPos.left += mOriginalIcon.getPaddingLeft();
+        outPos.right -= mOriginalIcon.getPaddingRight();
+        outPos.bottom = outPos.top + (mOriginalIcon.getIcon() != null
+                ? mOriginalIcon.getIcon().getBounds().height()
+                : mOriginalIcon.getHeight());
     }
 
     public void applyNotificationInfos(List<NotificationInfo> notificationInfos) {
@@ -642,10 +351,8 @@
         if (onClickListener != null && widgetsView == null) {
             // We didn't have any widgets cached but now there are some, so enable the shortcut.
             if (mSystemShortcutContainer != this) {
-                View view = mInflater.inflate(R.layout.system_shortcut_icon_only,
-                        mSystemShortcutContainer, false);
-                mSystemShortcutContainer.addView(view);
-                initializeSystemShortcut(view, widgetInfo);
+                initializeSystemShortcut(
+                        R.layout.system_shortcut_icon_only, mSystemShortcutContainer, widgetInfo);
             } else {
                 // If using the expanded system shortcut (as opposed to just the icon), we need to
                 // reopen the container to ensure measurements etc. all work out. While this could
@@ -665,7 +372,8 @@
         }
     }
 
-    private void initializeSystemShortcut(View view, SystemShortcut info) {
+    private void initializeSystemShortcut(int resId, ViewGroup container, SystemShortcut info) {
+        View view = inflateAndAdd(resId, container);
         if (view instanceof DeepShortcutView) {
             // Expanded system shortcut, with both icon and text shown on white background.
             final DeepShortcutView shortcutView = (DeepShortcutView) view;
@@ -682,12 +390,6 @@
                 (ItemInfo) mOriginalIcon.getTag()));
     }
 
-    protected int getIconHeightForPopupPlacement() {
-        return mOriginalIcon.getIcon() != null
-                ? mOriginalIcon.getIcon().getBounds().height()
-                : mOriginalIcon.getHeight();
-    }
-
     /**
      * Determines when the deferred drag should be started.
      *
@@ -807,91 +509,11 @@
         targetParent.containerType = ContainerType.DEEPSHORTCUTS;
     }
 
-    protected void animateClose() {
-        if (!mIsOpen) {
-            return;
-        }
-        mEndRect.setEmpty();
-        if (getOutlineProvider() instanceof RevealOutlineAnimation) {
-            ((RevealOutlineAnimation) getOutlineProvider()).getOutline(mEndRect);
-        }
-        if (mOpenCloseAnimator != null) {
-            mOpenCloseAnimator.cancel();
-        }
-        mIsOpen = false;
-
-        final AnimatorSet closeAnim = LauncherAnimUtils.createAnimatorSet();
-        // Hide the arrow
-        closeAnim.play(ObjectAnimator.ofFloat(mArrow, LauncherAnimUtils.SCALE_PROPERTY, 0));
-        closeAnim.play(ObjectAnimator.ofFloat(mArrow, ALPHA, 0));
-
+    @Override
+    protected void onCreateCloseAnimation(AnimatorSet anim) {
         // Animate original icon's text back in.
-        closeAnim.play(mOriginalIcon.createTextAlphaAnimator(true /* fadeIn */));
+        anim.play(mOriginalIcon.createTextAlphaAnimator(true /* fadeIn */));
         mOriginalIcon.forceHideBadge(false);
-
-        final Resources res = getResources();
-        final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator();
-
-        // Rectangular reveal (reversed).
-        final ValueAnimator revealAnim = createOpenCloseOutlineProvider()
-                .createRevealAnimator(this, true);
-        revealAnim.setInterpolator(revealInterpolator);
-        closeAnim.play(revealAnim);
-
-        Animator fadeOut = ObjectAnimator.ofFloat(this, ALPHA, 0);
-        fadeOut.setInterpolator(revealInterpolator);
-        closeAnim.play(fadeOut);
-        closeAnim.setDuration((long) res.getInteger(R.integer.config_popupOpenCloseDuration));
-
-        closeAnim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mOpenCloseAnimator = null;
-                if (mDeferContainerRemoval) {
-                    setVisibility(INVISIBLE);
-                } else {
-                    closeComplete();
-                }
-            }
-        });
-        mOpenCloseAnimator = closeAnim;
-        closeAnim.start();
-    }
-
-    private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() {
-        int arrowCenterX = getResources().getDimensionPixelSize(mIsLeftAligned ^ mIsRtl ?
-                R.dimen.popup_arrow_horizontal_center_start:
-                R.dimen.popup_arrow_horizontal_center_end);
-        if (!mIsLeftAligned) {
-            arrowCenterX = getMeasuredWidth() - arrowCenterX;
-        }
-        int arrowCenterY = mIsAboveIcon ? getMeasuredHeight() : 0;
-
-        mStartRect.set(arrowCenterX, arrowCenterY, arrowCenterX, arrowCenterY);
-        if (mEndRect.isEmpty()) {
-            mEndRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
-        }
-
-        return new RoundedRectRevealOutlineProvider
-                (mOutlineRadius, mOutlineRadius, mStartRect, mEndRect);
-    }
-
-    /**
-     * Closes the popup without animation.
-     */
-    private void closeComplete() {
-        mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible());
-        mOriginalIcon.forceHideBadge(false);
-
-        mLauncher.getDragController().removeDragListener(this);
-        if (mOpenCloseAnimator != null) {
-            mOpenCloseAnimator.cancel();
-            mOpenCloseAnimator = null;
-        }
-        mIsOpen = false;
-        mDeferContainerRemoval = false;
-        mLauncher.getDragLayer().removeView(this);
-        mLauncher.getDragLayer().removeView(mArrow);
     }
 
     @Override
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index a22f450..9726704 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -19,7 +19,6 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.util.Log;
 import android.view.MotionEvent;
@@ -31,6 +30,7 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.TouchController;
+import com.android.launcher3.util.PendingAnimation;
 
 /**
  * TouchController for handling state changes
@@ -54,10 +54,12 @@
     protected LauncherState mFromState;
     protected LauncherState mToState;
     protected AnimatorPlaybackController mCurrentAnimation;
+    protected PendingAnimation mPendingAnimation;
 
     private float mStartProgress;
     // Ratio of transition process [0, 1] to drag displacement (px)
     private float mProgressMultiplier;
+    private float mDisplacementShift;
 
     public AbstractStateChangeTouchController(Launcher l, SwipeDetector.Direction dir) {
         mLauncher = l;
@@ -68,7 +70,7 @@
 
     /**
      * Initializes the {@code mFromState} and {@code mToState} and swipe direction to use for
-     * the detector. In can of disabling swipe, return 0.
+     * the detector. In case of disabling swipe, return 0.
      */
     protected abstract int getSwipeDirection(MotionEvent ev);
 
@@ -122,16 +124,36 @@
         return mLauncher.getAllAppsController().getShiftRange();
     }
 
+    protected abstract LauncherState getTargetState(LauncherState fromState,
+            boolean isDragTowardPositive);
+
     protected abstract float initCurrentAnimation();
 
+    private boolean reinitCurrentAnimation(boolean reachedToState, boolean isDragTowardPositive) {
+        LauncherState newFromState = mFromState == null ? mLauncher.getStateManager().getState()
+                : reachedToState ? mToState : mFromState;
+        LauncherState newToState = getTargetState(newFromState, isDragTowardPositive);
+
+        if (newFromState == mFromState && newToState == mToState || (newFromState == newToState)) {
+            return false;
+        }
+
+        mFromState = newFromState;
+        mToState = newToState;
+
+        mStartProgress = 0;
+        mProgressMultiplier = initCurrentAnimation();
+        mCurrentAnimation.getTarget().addListener(this);
+        mCurrentAnimation.dispatchOnStart();
+        return true;
+    }
+
     @Override
     public void onDragStart(boolean start) {
         if (mCurrentAnimation == null) {
-            mStartProgress = 0;
-            mProgressMultiplier = initCurrentAnimation();
-
-            mCurrentAnimation.getTarget().addListener(this);
-            mCurrentAnimation.dispatchOnStart();
+            mFromState = mToState = null;
+            reinitCurrentAnimation(false, mDetector.wasInitialTouchPositive());
+            mDisplacementShift = 0;
         } else {
             mCurrentAnimation.pause();
             mStartProgress = mCurrentAnimation.getProgressFraction();
@@ -140,8 +162,19 @@
 
     @Override
     public boolean onDrag(float displacement, float velocity) {
-        float deltaProgress = mProgressMultiplier * displacement;
-        updateProgress(deltaProgress + mStartProgress);
+        float deltaProgress = mProgressMultiplier * (displacement - mDisplacementShift);
+        float progress = deltaProgress + mStartProgress;
+        updateProgress(progress);
+        boolean isDragTowardPositive = (displacement - mDisplacementShift) < 0;
+        if (progress <= 0) {
+            if (reinitCurrentAnimation(false, isDragTowardPositive)) {
+                mDisplacementShift = displacement;
+            }
+        } else if (progress >= 1) {
+            if (reinitCurrentAnimation(true, isDragTowardPositive)) {
+                mDisplacementShift = displacement;
+            }
+        }
         return true;
     }
 
@@ -223,7 +256,16 @@
                     mLauncher.getWorkspace().getCurrentPage());
         }
         clearState();
-        mLauncher.getStateManager().goToState(targetState, false /* animated */);
+        boolean shouldGoToTargetState = true;
+        if (mPendingAnimation != null) {
+            boolean reachedTarget = mToState == targetState;
+            mPendingAnimation.finish(reachedTarget);
+            mPendingAnimation = null;
+            shouldGoToTargetState = !reachedTarget;
+        }
+        if (shouldGoToTargetState) {
+            mLauncher.getStateManager().goToState(targetState, false /* animated */);
+        }
     }
 
     protected void clearState() {
diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
index 2f9cf3a..23f55aa 100644
--- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java
+++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
@@ -144,7 +144,7 @@
             mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
                     Action.Direction.NONE, ContainerType.WORKSPACE,
                     mWorkspace.getCurrentPage());
-            OptionsPopupView.show(mLauncher, mTouchDownPoint.x, mTouchDownPoint.y);
+            OptionsPopupView.showDefaultOptions(mLauncher, mTouchDownPoint.x, mTouchDownPoint.y);
         }
     }
 }
diff --git a/src/com/android/launcher3/util/ListViewHighlighter.java b/src/com/android/launcher3/util/ListViewHighlighter.java
new file mode 100644
index 0000000..ecad2af
--- /dev/null
+++ b/src/com/android/launcher3/util/ListViewHighlighter.java
@@ -0,0 +1,141 @@
+/*
+ * 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.
+ */
+package com.android.launcher3.util;
+
+import android.animation.ArgbEvaluator;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.support.v4.graphics.ColorUtils;
+import android.view.View;
+import android.view.View.OnLayoutChangeListener;
+import android.widget.AbsListView;
+import android.widget.AbsListView.OnScrollListener;
+import android.widget.AbsListView.RecyclerListener;
+import android.widget.ListView;
+
+import com.android.launcher3.R;
+
+/**
+ * Utility class to scroll and highlight a list view item
+ */
+public class ListViewHighlighter implements OnScrollListener, RecyclerListener,
+        OnLayoutChangeListener {
+
+    private final ListView mListView;
+    private int mPosHighlight;
+
+    private boolean mColorAnimated = false;
+
+    public ListViewHighlighter(ListView listView, int posHighlight) {
+        mListView = listView;
+        mPosHighlight = posHighlight;
+        mListView.setOnScrollListener(this);
+        mListView.setRecyclerListener(this);
+        mListView.addOnLayoutChangeListener(this);
+
+        mListView.post(this::tryHighlight);
+    }
+
+    @Override
+    public void onLayoutChange(View v, int left, int top, int right, int bottom,
+            int oldLeft, int oldTop, int oldRight, int oldBottom) {
+        mListView.post(this::tryHighlight);
+    }
+
+    private void tryHighlight() {
+        if (mPosHighlight < 0 || mListView.getChildCount() == 0) {
+            return;
+        }
+        if (!highlightIfVisible(mListView.getFirstVisiblePosition(),
+                mListView.getLastVisiblePosition())) {
+            mListView.smoothScrollToPosition(mPosHighlight);
+        }
+    }
+
+    @Override
+    public void onScrollStateChanged(AbsListView absListView, int i) { }
+
+    @Override
+    public void onScroll(AbsListView view, int firstVisibleItem,
+            int visibleItemCount, int totalItemCount) {
+        highlightIfVisible(firstVisibleItem, firstVisibleItem + visibleItemCount);
+    }
+
+    private boolean highlightIfVisible(int start, int end) {
+        if (mPosHighlight < 0 || mListView.getChildCount() == 0) {
+            return false;
+        }
+        if (start > mPosHighlight || mPosHighlight > end) {
+            return false;
+        }
+        highlightView(mListView.getChildAt(mPosHighlight - start));
+
+        // finish highlight
+        mListView.setOnScrollListener(null);
+        mListView.removeOnLayoutChangeListener(this);
+
+        mPosHighlight = -1;
+        return true;
+    }
+
+    @Override
+    public void onMovedToScrapHeap(View view) {
+        unhighlightView(view);
+    }
+
+    private void highlightView(View view) {
+        if (Boolean.TRUE.equals(view.getTag(R.id.view_highlighted))) {
+            // already highlighted
+        } else {
+            view.setTag(R.id.view_highlighted, true);
+            view.setTag(R.id.view_unhighlight_background, view.getBackground());
+            view.setBackground(getHighlightBackground());
+            view.postDelayed(() -> {
+                unhighlightView(view);
+            }, 15000L);
+        }
+    }
+
+    private void unhighlightView(View view) {
+        if (Boolean.TRUE.equals(view.getTag(R.id.view_highlighted))) {
+            Object background = view.getTag(R.id.view_unhighlight_background);
+            if (background instanceof Drawable) {
+                view.setBackground((Drawable) background);
+            }
+            view.setTag(R.id.view_unhighlight_background, null);
+            view.setTag(R.id.view_highlighted, false);
+        }
+    }
+
+    private ColorDrawable getHighlightBackground() {
+        int color = ColorUtils.setAlphaComponent(Themes.getColorAccent(mListView.getContext()), 26);
+        if (mColorAnimated) {
+            return new ColorDrawable(color);
+        }
+        mColorAnimated = true;
+        ColorDrawable bg = new ColorDrawable(Color.WHITE);
+        ObjectAnimator anim = ObjectAnimator.ofInt(bg, "color", Color.WHITE, color);
+        anim.setEvaluator(new ArgbEvaluator());
+        anim.setDuration(200L);
+        anim.setRepeatMode(ValueAnimator.REVERSE);
+        anim.setRepeatCount(4);
+        anim.start();
+        return bg;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/PendingAnimation.java b/src/com/android/launcher3/util/PendingAnimation.java
similarity index 97%
rename from quickstep/src/com/android/quickstep/PendingAnimation.java
rename to src/com/android/launcher3/util/PendingAnimation.java
index d22ef61..4116d56 100644
--- a/quickstep/src/com/android/quickstep/PendingAnimation.java
+++ b/src/com/android/launcher3/util/PendingAnimation.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.quickstep;
+package com.android.launcher3.util;
 
 import android.animation.AnimatorSet;
 import android.annotation.TargetApi;
diff --git a/src/com/android/launcher3/util/TraceHelper.java b/src/com/android/launcher3/util/TraceHelper.java
index ac381cc..4aa2f37 100644
--- a/src/com/android/launcher3/util/TraceHelper.java
+++ b/src/com/android/launcher3/util/TraceHelper.java
@@ -24,7 +24,6 @@
 import android.util.Log;
 import android.util.MutableLong;
 
-import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 
 /**
@@ -35,8 +34,7 @@
  */
 public class TraceHelper {
 
-    private static final boolean FORCE_LOG = Utilities.IS_DEBUG_DEVICE;
-    private static final boolean ENABLED = FORCE_LOG || FeatureFlags.IS_DOGFOOD_BUILD;
+    private static final boolean ENABLED = FeatureFlags.IS_DOGFOOD_BUILD;
 
     private static final boolean SYSTEM_TRACE = false;
     private static final ArrayMap<String, MutableLong> sUpTimes = ENABLED ? new ArrayMap<>() : null;
@@ -45,7 +43,7 @@
         if (ENABLED) {
             MutableLong time = sUpTimes.get(sectionName);
             if (time == null) {
-                time = new MutableLong((FORCE_LOG || isLoggable(sectionName, VERBOSE)) ? 0 : -1);
+                time = new MutableLong(isLoggable(sectionName, VERBOSE) ? 0 : -1);
                 sUpTimes.put(sectionName, time);
             }
             if (time.value >= 0) {
diff --git a/src/com/android/launcher3/views/HighlightableListView.java b/src/com/android/launcher3/views/HighlightableListView.java
deleted file mode 100644
index 7da979f..0000000
--- a/src/com/android/launcher3/views/HighlightableListView.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.launcher3.views;
-
-import android.animation.ArgbEvaluator;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.support.v4.graphics.ColorUtils;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.HeaderViewListAdapter;
-import android.widget.ListAdapter;
-import android.widget.ListView;
-
-import com.android.launcher3.R;
-import com.android.launcher3.util.Themes;
-
-import java.util.ArrayList;
-
-/**
- * Extension of list view with support for element highlighting.
- */
-public class HighlightableListView extends ListView {
-
-    private int mPosHighlight = -1;
-    private boolean mColorAnimated = false;
-
-    public HighlightableListView(Context context) {
-        super(context);
-    }
-
-    public HighlightableListView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public HighlightableListView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    @Override
-    public void setAdapter(ListAdapter adapter) {
-        super.setAdapter(new HighLightAdapter(adapter));
-    }
-
-    public void highlightPosition(int pos) {
-        if (mPosHighlight == pos) {
-            return;
-        }
-
-        mColorAnimated = false;
-        mPosHighlight = pos;
-        setSelection(mPosHighlight);
-
-        int start = getFirstVisiblePosition();
-        int end = getLastVisiblePosition();
-        if (start <= mPosHighlight && mPosHighlight <= end) {
-            highlightView(getChildAt(mPosHighlight - start));
-        }
-    }
-
-    private void highlightView(View view) {
-        if (Boolean.TRUE.equals(view.getTag(R.id.view_highlighted))) {
-            // already highlighted
-        } else {
-            view.setTag(R.id.view_highlighted, true);
-            view.setTag(R.id.view_unhighlight_background, view.getBackground());
-            view.setBackground(getHighlightBackground());
-            view.postDelayed(() -> {
-                mPosHighlight = -1;
-                unhighlightView(view);
-            }, 15000L);
-        }
-    }
-
-    private void unhighlightView(View view) {
-        if (Boolean.TRUE.equals(view.getTag(R.id.view_highlighted))) {
-            Object background = view.getTag(R.id.view_unhighlight_background);
-            if (background instanceof Drawable) {
-                view.setBackground((Drawable) background);
-            }
-            view.setTag(R.id.view_unhighlight_background, null);
-            view.setTag(R.id.view_highlighted, false);
-        }
-    }
-
-    private class HighLightAdapter extends HeaderViewListAdapter {
-        public HighLightAdapter(ListAdapter adapter) {
-            super(new ArrayList<>(), new ArrayList<>(), adapter);
-        }
-
-        @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            View view =  super.getView(position, convertView, parent);
-
-            if (position == mPosHighlight) {
-                highlightView(view);
-            } else {
-                unhighlightView(view);
-            }
-            return view;
-        }
-    }
-
-    private ColorDrawable getHighlightBackground() {
-        int color = ColorUtils.setAlphaComponent(Themes.getColorAccent(getContext()), 26);
-        if (mColorAnimated) {
-            return new ColorDrawable(color);
-        }
-        mColorAnimated = true;
-        ColorDrawable bg = new ColorDrawable(Color.WHITE);
-        ObjectAnimator anim = ObjectAnimator.ofInt(bg, "color", Color.WHITE, color);
-        anim.setEvaluator(new ArgbEvaluator());
-        anim.setDuration(200L);
-        anim.setRepeatMode(ValueAnimator.REVERSE);
-        anim.setRepeatCount(4);
-        anim.start();
-        return bg;
-    }
-}
diff --git a/src/com/android/launcher3/views/LauncherDragIndicator.java b/src/com/android/launcher3/views/LauncherDragIndicator.java
index f15129c..986e4be 100644
--- a/src/com/android/launcher3/views/LauncherDragIndicator.java
+++ b/src/com/android/launcher3/views/LauncherDragIndicator.java
@@ -108,15 +108,12 @@
 
     @Override
     public boolean performAccessibilityAction(int action, Bundle arguments) {
-        Launcher launcher = Launcher.getLauncher(getContext());
         if (action == WALLPAPERS) {
-            launcher.onClickWallpaperPicker(this);
-            return true;
+            return OptionsPopupView.startWallpaperPicker(this);
         } else if (action == WIDGETS) {
-            return OptionsPopupView.onWidgetsClicked(launcher);
+            return OptionsPopupView.onWidgetsClicked(this);
         } else if (action == SETTINGS) {
-            OptionsPopupView.startSettings(launcher);
-            return true;
+            return OptionsPopupView.startSettings(this);
         }
         return super.performAccessibilityAction(action, arguments);
     }
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 709a7e5..56b92c7 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -15,52 +15,42 @@
  */
 package com.android.launcher3.views;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
+import static com.android.launcher3.BaseDraggingActivity.INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION;
+import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_OFFSET;
+
 import android.content.Context;
 import android.content.Intent;
-import android.graphics.Outline;
-import android.graphics.PointF;
 import android.graphics.Rect;
+import android.graphics.RectF;
+import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.View.OnLongClickListener;
-import android.view.ViewGroup;
-import android.view.ViewOutlineProvider;
 import android.widget.Toast;
 
-import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.anim.RevealOutlineAnimation;
-import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
-import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.graphics.ColorScrim;
+import com.android.launcher3.popup.ArrowPopup;
+import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
 import com.android.launcher3.widget.WidgetsFullSheet;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Popup shown on long pressing an empty space in launcher
  */
-public class OptionsPopupView extends AbstractFloatingView
+public class OptionsPopupView extends ArrowPopup
         implements OnClickListener, OnLongClickListener {
 
-    private final float mOutlineRadius;
-    private final Launcher mLauncher;
-    private final PointF mTouchPoint = new PointF();
-
-    private final ColorScrim mScrim;
-
-    protected Animator mOpenCloseAnimator;
+    private final ArrayMap<View, OptionItem> mItemMap = new ArrayMap<>();
+    private RectF mTargetRect;
 
     public OptionsPopupView(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
@@ -68,31 +58,6 @@
 
     public OptionsPopupView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-
-        mOutlineRadius = getResources().getDimension(R.dimen.bg_round_rect_radius);
-        setClipToOutline(true);
-        setOutlineProvider(new ViewOutlineProvider() {
-            @Override
-            public void getOutline(View view, Outline outline) {
-                outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mOutlineRadius);
-            }
-        });
-
-        mLauncher = Launcher.getLauncher(context);
-        mScrim = ColorScrim.createExtractedColorScrim(this);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        attachListeners(findViewById(R.id.wallpaper_button));
-        attachListeners(findViewById(R.id.widget_button));
-        attachListeners(findViewById(R.id.settings_button));
-    }
-
-    private void attachListeners(View view) {
-        view.setOnClickListener(this);
-        view.setOnLongClickListener(this);
     }
 
     @Override
@@ -106,20 +71,14 @@
     }
 
     private boolean handleViewClick(View view, int action) {
-        if (view.getId() == R.id.wallpaper_button) {
-            mLauncher.onClickWallpaperPicker(view);
-            logTap(action, ControlType.WALLPAPER_BUTTON);
-            close(true);
-            return true;
-        } else if (view.getId() == R.id.widget_button) {
-            logTap(action, ControlType.WIDGETS_BUTTON);
-            if (onWidgetsClicked(mLauncher)) {
-                close(true);
-                return true;
-            }
-        } else if (view.getId() == R.id.settings_button) {
-            startSettings(mLauncher);
-            logTap(action, ControlType.SETTINGS_BUTTON);
+        OptionItem item = mItemMap.get(view);
+        if (item == null) {
+            return false;
+        }
+        if (item.mControlTypeForLog > 0) {
+            logTap(action, item.mControlTypeForLog);
+        }
+        if (item.mClickListener.onLongClick(view)) {
             close(true);
             return true;
         }
@@ -143,63 +102,6 @@
     }
 
     @Override
-    protected void handleClose(boolean animate) {
-        if (animate) {
-            animateClose();
-        } else {
-            closeComplete();
-        }
-    }
-
-    protected void animateClose() {
-        if (!mIsOpen) {
-            return;
-        }
-        mIsOpen = false;
-
-        final AnimatorSet closeAnim = LauncherAnimUtils.createAnimatorSet();
-        closeAnim.setDuration(getResources().getInteger(R.integer.config_popupOpenCloseDuration));
-
-        // Rectangular reveal (reversed).
-        final ValueAnimator revealAnim = createOpenCloseOutlineProvider()
-                .createRevealAnimator(this, true);
-        closeAnim.play(revealAnim);
-
-        Animator fadeOut = ObjectAnimator.ofFloat(this, ALPHA, 0);
-        fadeOut.setInterpolator(Interpolators.DEACCEL);
-        closeAnim.play(fadeOut);
-
-        Animator gradientAlpha = ObjectAnimator.ofFloat(mScrim, ColorScrim.PROGRESS, 0);
-        gradientAlpha.setInterpolator(Interpolators.DEACCEL);
-        closeAnim.play(gradientAlpha);
-
-        closeAnim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mOpenCloseAnimator = null;
-                closeComplete();
-            }
-        });
-        if (mOpenCloseAnimator != null) {
-            mOpenCloseAnimator.cancel();
-        }
-        mOpenCloseAnimator = closeAnim;
-        closeAnim.start();
-    }
-
-    /**
-     * Closes the popup without animation.
-     */
-    private void closeComplete() {
-        if (mOpenCloseAnimator != null) {
-            mOpenCloseAnimator.cancel();
-            mOpenCloseAnimator = null;
-        }
-        mIsOpen = false;
-        mLauncher.getDragLayer().removeView(this);
-    }
-
-    @Override
     public void logActionCommand(int command) {
         // TODO:
     }
@@ -209,90 +111,49 @@
         return (type & TYPE_OPTIONS_POPUP) != 0;
     }
 
-    private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() {
-        DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
-        Rect startRect = new Rect();
-        startRect.offset((int) (mTouchPoint.x - lp.x), (int) (mTouchPoint.y - lp.y));
-
-        Rect endRect = new Rect(0, 0, lp.width, lp.height);
-        if (getOutlineProvider() instanceof RevealOutlineAnimation) {
-            ((RevealOutlineAnimation) getOutlineProvider()).getOutline(endRect);
-        }
-
-        return new RoundedRectRevealOutlineProvider
-                (mOutlineRadius, mOutlineRadius, startRect, endRect);
+    @Override
+    protected void getTargetObjectLocation(Rect outPos) {
+        mTargetRect.roundOut(outPos);
     }
 
-    private void animateOpen() {
-        mIsOpen = true;
-        final AnimatorSet openAnim = LauncherAnimUtils.createAnimatorSet();
-        openAnim.setDuration(getResources().getInteger(R.integer.config_popupOpenCloseDuration));
+    public static void show(Launcher launcher, RectF targetRect, List<OptionItem> items) {
+        OptionsPopupView popup = (OptionsPopupView) launcher.getLayoutInflater()
+                .inflate(R.layout.longpress_options_menu, launcher.getDragLayer(), false);
+        popup.mTargetRect = targetRect;
 
-        final ValueAnimator revealAnim = createOpenCloseOutlineProvider()
-                .createRevealAnimator(this, false);
-        openAnim.play(revealAnim);
-
-        Animator gradientAlpha = ObjectAnimator.ofFloat(mScrim, ColorScrim.PROGRESS, 1);
-        gradientAlpha.setInterpolator(Interpolators.ACCEL);
-        openAnim.play(gradientAlpha);
-
-        mOpenCloseAnimator = openAnim;
-
-        openAnim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mOpenCloseAnimator = null;
-            }
-        });
-        openAnim.start();
+        for (OptionItem item : items) {
+            DeepShortcutView view = popup.inflateAndAdd(R.layout.system_shortcut, popup);
+            view.getIconView().setBackgroundResource(item.mIconRes);
+            view.getBubbleText().setText(item.mLabelRes);
+            view.setDividerVisibility(View.INVISIBLE);
+            view.setOnClickListener(popup);
+            view.setOnLongClickListener(popup);
+            popup.mItemMap.put(view, item);
+        }
+        popup.reorderAndShow(popup.getChildCount());
     }
 
-    public static void show(Launcher launcher, float x, float y) {
-        DragLayer dl = launcher.getDragLayer();
-        OptionsPopupView view = (OptionsPopupView) launcher.getLayoutInflater()
-                .inflate(R.layout.longpress_options_menu, dl, false);
-        DragLayer.LayoutParams lp = (DragLayer.LayoutParams) view.getLayoutParams();
-
-        int maxWidth = dl.getWidth();
-        int maxHeight = dl.getHeight();
-        if (x <= 0 || y <= 0 || x >= maxWidth || y >= maxHeight) {
-            x = maxWidth / 2;
-            y = maxHeight / 2;
+    public static void showDefaultOptions(Launcher launcher, float x, float y) {
+        float halfSize = launcher.getResources().getDimension(R.dimen.options_menu_thumb_size) / 2;
+        if (x < 0 || y < 0) {
+            x = launcher.getDragLayer().getWidth() / 2;
+            y = launcher.getDragLayer().getHeight() / 2;
         }
-        view.mTouchPoint.set(x, y);
+        RectF target = new RectF(x - halfSize, y - halfSize, x + halfSize, y + halfSize);
 
-        int height = lp.height;
+        ArrayList<OptionItem> options = new ArrayList<>();
+        options.add(new OptionItem(R.string.wallpaper_button_text, R.drawable.ic_wallpaper,
+                ControlType.WALLPAPER_BUTTON, OptionsPopupView::startWallpaperPicker));
+        options.add(new OptionItem(R.string.widget_button_text, R.drawable.ic_widget,
+                ControlType.WIDGETS_BUTTON, OptionsPopupView::onWidgetsClicked));
+        options.add(new OptionItem(R.string.settings_button_text, R.drawable.ic_setting,
+                ControlType.SETTINGS_BUTTON, OptionsPopupView::startSettings));
 
-        // Find a good width;
-        int childCount = view.getChildCount();
-        int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
-        int widthSpec = MeasureSpec.makeMeasureSpec(maxWidth / childCount, MeasureSpec.AT_MOST);
-        int maxChildWidth = 0;
-
-        for (int i = 0; i < childCount; i ++) {
-            View child = ((ViewGroup) view.getChildAt(i)).getChildAt(0);
-            child.measure(widthSpec, heightSpec);
-            maxChildWidth = Math.max(maxChildWidth, child.getMeasuredWidth());
-        }
-        Rect insets = dl.getInsets();
-        int margin = (int) (2 * view.getElevation());
-
-        int width = Math.min(maxWidth - insets.left - insets.right - 2 * margin,
-                maxChildWidth * childCount);
-        lp.width = width;
-
-        // Position is towards the finger
-        lp.customPosition = true;
-        lp.x = Utilities.boundToRange((int) (x - width / 2), insets.left + margin,
-                maxWidth - insets.right - width - margin);
-        lp.y = Utilities.boundToRange((int) (y - height / 2), insets.top + margin,
-                maxHeight - insets.bottom - height - margin);
-
-        view.animateOpen();
-        launcher.getDragLayer().addView(view);
+        show(launcher, target, options);
     }
 
-    public static boolean onWidgetsClicked(Launcher launcher) {
+    public static boolean onWidgetsClicked(View view) {
+        Launcher launcher = Launcher.getLauncher(view.getContext());
         if (launcher.getPackageManager().isSafeMode()) {
             Toast.makeText(launcher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
             return false;
@@ -302,9 +163,51 @@
         }
     }
 
-    public static void startSettings(Launcher launcher) {
+    public static boolean startSettings(View view) {
+        Launcher launcher = Launcher.getLauncher(view.getContext());
         launcher.startActivity(new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
                 .setPackage(launcher.getPackageName())
                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+        return true;
+    }
+
+    /**
+     * Event handler for the wallpaper picker button that appears after a long press
+     * on the home screen.
+     */
+    public static boolean startWallpaperPicker(View v) {
+        Launcher launcher = Launcher.getLauncher(v.getContext());
+        if (!Utilities.isWallpaperAllowed(launcher)) {
+            Toast.makeText(launcher, R.string.msg_disabled_by_admin, Toast.LENGTH_SHORT).show();
+            return false;
+        }
+        Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER)
+                .putExtra(EXTRA_WALLPAPER_OFFSET,
+                        launcher.getWorkspace().getWallpaperOffsetForCenterPage());
+
+        String pickerPackage = launcher.getString(R.string.wallpaper_picker_package);
+        if (!TextUtils.isEmpty(pickerPackage)) {
+            intent.setPackage(pickerPackage);
+        } else {
+            // If there is no target package, use the default intent chooser animation
+            intent.putExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION, true);
+        }
+        return launcher.startActivitySafely(v, intent, null);
+    }
+
+    public static class OptionItem {
+
+        private final int mLabelRes;
+        private final int mIconRes;
+        private final int mControlTypeForLog;
+        private final OnLongClickListener mClickListener;
+
+        public OptionItem(int labelRes, int iconRes, int controlTypeForLog,
+                OnLongClickListener clickListener) {
+            mLabelRes = labelRes;
+            mIconRes = iconRes;
+            mControlTypeForLog = controlTypeForLog;
+            mClickListener = clickListener;
+        }
     }
 }
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
index e495477..c97c3cc 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
@@ -7,6 +7,7 @@
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
 import com.android.launcher3.touch.AbstractStateChangeTouchController;
 import com.android.launcher3.touch.SwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -43,12 +44,8 @@
     protected int getSwipeDirection(MotionEvent ev) {
         if (mLauncher.isInState(ALL_APPS)) {
             mStartContainerType = ContainerType.ALLAPPS;
-            mFromState = ALL_APPS;
-            mToState = NORMAL;
             return SwipeDetector.DIRECTION_NEGATIVE;
         } else {
-            mFromState = NORMAL;
-            mToState = ALL_APPS;
             mStartContainerType = mLauncher.getDragLayer().isEventOverHotseat(ev) ?
                     ContainerType.HOTSEAT : ContainerType.WORKSPACE;
             return SwipeDetector.DIRECTION_POSITIVE;
@@ -56,6 +53,11 @@
     }
 
     @Override
+    protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
+        return fromState == ALL_APPS ? NORMAL : ALL_APPS;
+    }
+
+    @Override
     protected float initCurrentAnimation() {
         float range = getShiftRange();
         long maxAccuracy = (long) (2 * range);