Merge branch 'ub-launcher3-master', build 5258802

Test: Manual

Bug:111697218  [Quickstep 1.1] Integrate PiP / Live Tiles into Quickstep
Bug:111698021  [Quickstep 1.1] Improve motion within Quickstep
Bug:111699315  [Quickstep 2] Improve Quickscrub / switch gesture
Bug:111926330  [Quickstep 2] Nav bar, gestures, edge-to-edge, and OEM updates
Bug:112324288  Pixel launcher keeps on crashing when trying to remove a widget
Bug:114136250  Have a more spartan RecentsActivity on android go
Bug:117332845  testPendingWidget_autoRestored is failing on TAP
Bug:118206886  Add fallback launcher mechanism
Bug:118319143  1Digital Wellbeing - Implement showing app's remaining time in Launcher
Bug:119500058  Tests switch Launcher to a state without icons
Bug:120628042  Add race condition repro tests
Bug:122262084  Expose Launcher grid customization options
Bug:122262946  Each app installed from playstore occupies new workspace
Bug:122345781  Inflation happens and a binder call during swipe up gesture, that may cause jank
Bug:122347746  Jank in app closing animation for Maps on taimen
Bug:122472687  Recents icon shape should change when theme changed.
Bug:122545624  Iconloaderlib: make createIconBitmap in BaseIconFactory a public method
Bug:122612839  Flash of old snapshot when swiping up
Bug:122662526  Test broken: can't swipe back from AllApps to Overview
Bug:122809947  [C2][F2] Launcher keeps restarting after Setup wizard, it impacts testing seriously.
Bug:65162781  Status bar gradient sometimes slides up when unlocking
Change-Id: I22774df74963efb9189ea2a8bf3cdace6361c988
diff --git a/Android.mk b/Android.mk
index b330a54..a5e338f 100644
--- a/Android.mk
+++ b/Android.mk
@@ -182,15 +182,20 @@
 endif
 LOCAL_MODULE := Launcher3QuickStepLib
 LOCAL_PRIVILEGED_MODULE := true
-LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3CommonDepsLib
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+    Launcher3CommonDepsLib \
+    SecondaryDisplayLauncherLib
 
 LOCAL_SRC_FILES := \
     $(call all-java-files-under, src) \
     $(call all-java-files-under, quickstep/src) \
+    $(call all-java-files-under, quickstep/recents_ui_overrides/src) \
     $(call all-java-files-under, src_flags) \
     $(call all-java-files-under, src_shortcuts_overrides)
 
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/quickstep/res
+LOCAL_RESOURCE_DIR := \
+    $(LOCAL_PATH)/quickstep/res \
+    $(LOCAL_PATH)/quickstep/recents_ui_overrides/res
 LOCAL_PROGUARD_ENABLED := disabled
 
 
@@ -219,7 +224,9 @@
 LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3
 LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
 
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/quickstep/res
+LOCAL_RESOURCE_DIR := \
+    $(LOCAL_PATH)/quickstep/res \
+    $(LOCAL_PATH)/quickstep/recents_ui_overrides/res
 
 LOCAL_FULL_LIBS_MANIFEST_FILES := \
     $(LOCAL_PATH)/AndroidManifest.xml \
@@ -251,10 +258,12 @@
 LOCAL_SRC_FILES := \
     $(call all-java-files-under, src) \
     $(call all-java-files-under, quickstep/src) \
+    $(call all-java-files-under, quickstep/recents_ui_overrides/src) \
     $(call all-java-files-under, go/src)
 
 LOCAL_RESOURCE_DIR := \
     $(LOCAL_PATH)/quickstep/res \
+    $(LOCAL_PATH)/quickstep/recents_ui_overrides/res \
     $(LOCAL_PATH)/go/res
 
 LOCAL_PROGUARD_FLAG_FILES := proguard.flags
@@ -273,6 +282,51 @@
 LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.launcher3.*
 include $(BUILD_PACKAGE)
 
+#
+# Build rule for Launcher3 Go app with quickstep and Go-specific
+# version of recents for Android Go devices.
+#
+include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
+LOCAL_MODULE_TAGS := optional
+
+ifneq (,$(wildcard frameworks/base))
+  LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLib launcherprotosnano
+  LOCAL_PRIVATE_PLATFORM_APIS := true
+else
+  LOCAL_STATIC_JAVA_LIBRARIES := libSharedSystemUI libLauncherProtos
+  LOCAL_SDK_VERSION := system_current
+  LOCAL_MIN_SDK_VERSION := 26
+endif
+LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3CommonDepsLib
+
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under, src) \
+    $(call all-java-files-under, quickstep/src) \
+    $(call all-java-files-under, go/src) \
+    $(call all-java-files-under, go/quickstep/src)
+
+LOCAL_RESOURCE_DIR := \
+    $(LOCAL_PATH)/quickstep/res \
+    $(LOCAL_PATH)/go/res \
+    $(LOCAL_PATH)/go/quickstep/res
+
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
+LOCAL_PROGUARD_ENABLED := full
+
+LOCAL_PACKAGE_NAME := Launcher3QuickStepGoIconRecents
+LOCAL_PRIVILEGED_MODULE := true
+LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3QuickStep
+
+LOCAL_FULL_LIBS_MANIFEST_FILES := \
+    $(LOCAL_PATH)/go/AndroidManifest.xml \
+    $(LOCAL_PATH)/AndroidManifest.xml \
+    $(LOCAL_PATH)/AndroidManifest-common.xml
+
+LOCAL_MANIFEST_FILE := quickstep/AndroidManifest.xml
+LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.launcher3.*
+include $(BUILD_PACKAGE)
+
 
 # ==================================================
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index d7c16e6..979096c 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -153,6 +153,15 @@
             android:readPermission="${packageName}.permission.READ_SETTINGS" />
 
         <!--
+        The content provider for exposing various launcher grid options.
+        TODO: Add proper permissions
+        -->
+        <provider
+            android:name="com.android.launcher3.graphics.GridOptionsProvider"
+            android:authorities="${packageName}.grid_control"
+            android:exported="true" />
+
+        <!--
         The settings activity. To extend point settings_fragment_name to appropriate fragment class
         -->
         <activity
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 1a485ed..9500a2f 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -61,6 +61,9 @@
                 <category android:name="android.intent.category.MONKEY"/>
                 <category android:name="android.intent.category.LAUNCHER_APP" />
             </intent-filter>
+            <meta-data
+                android:name="com.android.launcher3.grid.control"
+                android:value="${packageName}.grid.control" />
         </activity>
 
     </application>
diff --git a/SecondaryDisplayLauncher/res/values-af/strings.xml b/SecondaryDisplayLauncher/res/values-af/strings.xml
index 63d83a2..b544be7a 100644
--- a/SecondaryDisplayLauncher/res/values-af/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-af/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Kon nie die aktiwiteit begin nie"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Voeg programkortpad by"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Stel muurpapier"</string>
diff --git a/SecondaryDisplayLauncher/res/values-am/strings.xml b/SecondaryDisplayLauncher/res/values-am/strings.xml
index 3d99783..71854ad 100644
--- a/SecondaryDisplayLauncher/res/values-am/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-am/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"እንቅስቃሴውን ማስጀመር አልተቻለም"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"የመተግበሪያ አቋራጭ ያክሉ"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"ልጣፍ አዘጋጅ"</string>
diff --git a/SecondaryDisplayLauncher/res/values-ar/strings.xml b/SecondaryDisplayLauncher/res/values-ar/strings.xml
index 1c0ee17..ba81c11 100644
--- a/SecondaryDisplayLauncher/res/values-ar/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-ar/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"تعذَّر تشغيل النشاط."</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"إضافة اختصار التطبيق"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"تعيين الخلفية"</string>
diff --git a/SecondaryDisplayLauncher/res/values-as/strings.xml b/SecondaryDisplayLauncher/res/values-as/strings.xml
index 5e4f690..d199a26 100644
--- a/SecondaryDisplayLauncher/res/values-as/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-as/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"কাৰ্যকলাপটো লঞ্চ কৰিব পৰা নগ’ল"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"এপৰ শ্বর্টকাট যোগ কৰক"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"ৱালপেপাৰ ছেট কৰক"</string>
diff --git a/SecondaryDisplayLauncher/res/values-az/strings.xml b/SecondaryDisplayLauncher/res/values-az/strings.xml
index 5cc7313..cee70a0 100644
--- a/SecondaryDisplayLauncher/res/values-az/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-az/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"Əlavə Displey Başladıcısı"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Fəaliyyəti başlatmaq mümkün olmadı"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Tətbiq qısayolu əlavə edin"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Divar kağızı ayarlayın"</string>
diff --git a/SecondaryDisplayLauncher/res/values-b+sr+Latn/strings.xml b/SecondaryDisplayLauncher/res/values-b+sr+Latn/strings.xml
index 873b1ea..a8859d9 100644
--- a/SecondaryDisplayLauncher/res/values-b+sr+Latn/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-b+sr+Latn/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Pokretanje aktivnosti nije uspelo"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Dodaj prečicu za aplikaciju"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Podesite pozadinu"</string>
diff --git a/SecondaryDisplayLauncher/res/values-be/strings.xml b/SecondaryDisplayLauncher/res/values-be/strings.xml
index bb59e46..3df3760 100644
--- a/SecondaryDisplayLauncher/res/values-be/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-be/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Не ўдалося запусціць дзеянне"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Дадаць ярлык праграмы"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Устанавіць шпалеры"</string>
diff --git a/SecondaryDisplayLauncher/res/values-bg/strings.xml b/SecondaryDisplayLauncher/res/values-bg/strings.xml
index 9c2abbb..4474815 100644
--- a/SecondaryDisplayLauncher/res/values-bg/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-bg/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Активността не можа да бъде стартирана"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Добавяне на пряк път към приложението"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Задаване на тапет"</string>
diff --git a/SecondaryDisplayLauncher/res/values-bn/strings.xml b/SecondaryDisplayLauncher/res/values-bn/strings.xml
index 5500754..7322691 100644
--- a/SecondaryDisplayLauncher/res/values-bn/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-bn/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"অ্যাক্টিভিটি চালু করা যায়নি"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"অ্যাপ শর্টকাট যোগ করুন"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"ওয়ালপেপার সেট করুন"</string>
diff --git a/SecondaryDisplayLauncher/res/values-bs/strings.xml b/SecondaryDisplayLauncher/res/values-bs/strings.xml
index 7d2f011..1e59d33 100644
--- a/SecondaryDisplayLauncher/res/values-bs/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-bs/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"Pokretač za sekundarni ekran"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Pokretanje aktivnosti nije uspjelo"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Dodaj prečicu aplikacije"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Postavi pozadinsku sliku"</string>
diff --git a/SecondaryDisplayLauncher/res/values-ca/strings.xml b/SecondaryDisplayLauncher/res/values-ca/strings.xml
index f17c30c..c0274d1 100644
--- a/SecondaryDisplayLauncher/res/values-ca/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-ca/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"No s\'ha pogut iniciar l\'activitat"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Afegeix una drecera d\'aplicació"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Estableix el fons de pantalla"</string>
diff --git a/SecondaryDisplayLauncher/res/values-cs/strings.xml b/SecondaryDisplayLauncher/res/values-cs/strings.xml
index 1864aac..92ed5fa 100644
--- a/SecondaryDisplayLauncher/res/values-cs/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-cs/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Aktivitu nelze zahájit"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Přidat zkratku aplikace"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Nastavení tapety"</string>
diff --git a/SecondaryDisplayLauncher/res/values-da/strings.xml b/SecondaryDisplayLauncher/res/values-da/strings.xml
index 0ad86e1..16bdb33 100644
--- a/SecondaryDisplayLauncher/res/values-da/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-da/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Aktiviteten kunne ikke startes"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Tilføj appgenvej"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Angiv baggrund"</string>
diff --git a/SecondaryDisplayLauncher/res/values-de/strings.xml b/SecondaryDisplayLauncher/res/values-de/strings.xml
index fe1b103..3617a5b 100644
--- a/SecondaryDisplayLauncher/res/values-de/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-de/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Aktivität konnte nicht gestartet werden"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"App-Verknüpfung hinzufügen"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Hintergrund festlegen"</string>
diff --git a/SecondaryDisplayLauncher/res/values-el/strings.xml b/SecondaryDisplayLauncher/res/values-el/strings.xml
index 1a62114..8d19d09 100644
--- a/SecondaryDisplayLauncher/res/values-el/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-el/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Δεν ήταν δυνατή η εκκίνηση της δραστηριότητας"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Προσθήκη συντόμευσης εφαρμογής"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Ορισμός ταπετσαρίας"</string>
diff --git a/SecondaryDisplayLauncher/res/values-en-rAU/strings.xml b/SecondaryDisplayLauncher/res/values-en-rAU/strings.xml
index ab55a6a..8d8c419 100644
--- a/SecondaryDisplayLauncher/res/values-en-rAU/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-en-rAU/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Couldn\'t launch the activity"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Add app shortcut"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Set wallpaper"</string>
diff --git a/SecondaryDisplayLauncher/res/values-en-rGB/strings.xml b/SecondaryDisplayLauncher/res/values-en-rGB/strings.xml
index ab55a6a..8d8c419 100644
--- a/SecondaryDisplayLauncher/res/values-en-rGB/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-en-rGB/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Couldn\'t launch the activity"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Add app shortcut"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Set wallpaper"</string>
diff --git a/SecondaryDisplayLauncher/res/values-en-rIN/strings.xml b/SecondaryDisplayLauncher/res/values-en-rIN/strings.xml
index ab55a6a..8d8c419 100644
--- a/SecondaryDisplayLauncher/res/values-en-rIN/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-en-rIN/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Couldn\'t launch the activity"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Add app shortcut"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Set wallpaper"</string>
diff --git a/SecondaryDisplayLauncher/res/values-es-rUS/strings.xml b/SecondaryDisplayLauncher/res/values-es-rUS/strings.xml
index 64b686c..ff6772b 100644
--- a/SecondaryDisplayLauncher/res/values-es-rUS/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-es-rUS/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"No se pudo iniciar la actividad"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Agregar acceso directo a app"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Establecer fondo de pantalla"</string>
diff --git a/SecondaryDisplayLauncher/res/values-es/strings.xml b/SecondaryDisplayLauncher/res/values-es/strings.xml
index fa5e4b4..0654dcb 100644
--- a/SecondaryDisplayLauncher/res/values-es/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-es/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"No se ha podido iniciar la acción"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Añadir acceso directo a la aplicación"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Establecer fondo de pantalla"</string>
diff --git a/SecondaryDisplayLauncher/res/values-et/strings.xml b/SecondaryDisplayLauncher/res/values-et/strings.xml
index 8fde9dd..3410fd4 100644
--- a/SecondaryDisplayLauncher/res/values-et/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-et/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Tegevust ei saanud käivitada"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Lisa rakenduse otsetee"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Määra taustapilt"</string>
diff --git a/SecondaryDisplayLauncher/res/values-eu/strings.xml b/SecondaryDisplayLauncher/res/values-eu/strings.xml
index 5048127..d7abe33 100644
--- a/SecondaryDisplayLauncher/res/values-eu/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-eu/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Ezin izan da abiarazi jarduera"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Gehitu aplikaziorako lasterbidea"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Ezarri horma-papera"</string>
diff --git a/SecondaryDisplayLauncher/res/values-fa/strings.xml b/SecondaryDisplayLauncher/res/values-fa/strings.xml
index 2dc3780..4d3ec4d 100644
--- a/SecondaryDisplayLauncher/res/values-fa/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-fa/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"راه‌انداز نمایشگر ثانویه"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"فعالیت راه‌اندازی نشد"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"افزودن میان‌بر برنامه"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"تنظیم کاغذدیواری"</string>
diff --git a/SecondaryDisplayLauncher/res/values-fi/strings.xml b/SecondaryDisplayLauncher/res/values-fi/strings.xml
index 5d6d48f..e56f67a 100644
--- a/SecondaryDisplayLauncher/res/values-fi/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-fi/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Käynnistäminen epäonnistui"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Lisää sovelluksen pikakuvake"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Aseta taustakuva"</string>
diff --git a/SecondaryDisplayLauncher/res/values-fr-rCA/strings.xml b/SecondaryDisplayLauncher/res/values-fr-rCA/strings.xml
index 9d88e1f..f5c9ba5 100644
--- a/SecondaryDisplayLauncher/res/values-fr-rCA/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-fr-rCA/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Impossible de lancer l\'activité"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Ajouter un raccourci vers l\'application"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Définir le fond d\'écran"</string>
diff --git a/SecondaryDisplayLauncher/res/values-fr/strings.xml b/SecondaryDisplayLauncher/res/values-fr/strings.xml
index 10254bf..daa186b 100644
--- a/SecondaryDisplayLauncher/res/values-fr/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-fr/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Impossible de lancer l\'activité"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Ajouter un raccourci vers l\'application"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Configurer le fond d\'écran"</string>
diff --git a/SecondaryDisplayLauncher/res/values-gl/strings.xml b/SecondaryDisplayLauncher/res/values-gl/strings.xml
index eb86e39..0bcf969 100644
--- a/SecondaryDisplayLauncher/res/values-gl/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-gl/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"Launcher da pantalla secundaria"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Non se puido iniciar a actividade"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Engadir atallo da aplicación"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Definir fondo de pantalla"</string>
diff --git a/SecondaryDisplayLauncher/res/values-gu/strings.xml b/SecondaryDisplayLauncher/res/values-gu/strings.xml
index f13422e..82b4444 100644
--- a/SecondaryDisplayLauncher/res/values-gu/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-gu/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"પ્રવૃત્તિ લૉન્ચ કરી શકાઈ નથી"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"ઍપ શૉર્ટકટ ઉમેરો"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"વૉલપેપર સેટ કરો"</string>
diff --git a/SecondaryDisplayLauncher/res/values-hi/strings.xml b/SecondaryDisplayLauncher/res/values-hi/strings.xml
index 8b7728d..8adb519 100644
--- a/SecondaryDisplayLauncher/res/values-hi/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-hi/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"गतिविधि लॉन्च नहीं हो सकी"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"ऐप्लिकेशन शॉर्टकट जोड़ें"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"वॉलपेपर सेट करें"</string>
diff --git a/SecondaryDisplayLauncher/res/values-hr/strings.xml b/SecondaryDisplayLauncher/res/values-hr/strings.xml
index d622928..87ac874 100644
--- a/SecondaryDisplayLauncher/res/values-hr/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-hr/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"Pokretač za sekundarni zaslon"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Pokretanje aktivnosti nije uspjelo"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Dodajte aplikacijski prečac"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Postavljanje pozadine"</string>
diff --git a/SecondaryDisplayLauncher/res/values-hu/strings.xml b/SecondaryDisplayLauncher/res/values-hu/strings.xml
index 868a3bb..a8870fc 100644
--- a/SecondaryDisplayLauncher/res/values-hu/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-hu/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Nem sikerült elindítani a tevékenységet"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Alkalmazás parancsikonjának hozzáadása"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Háttérkép beállítása"</string>
diff --git a/SecondaryDisplayLauncher/res/values-hy/strings.xml b/SecondaryDisplayLauncher/res/values-hy/strings.xml
index 1fdd1c4..a64233f 100644
--- a/SecondaryDisplayLauncher/res/values-hy/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-hy/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Չհաջողվեց գործարկել գործողությունը"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Ավելացնել հավելվածի դյուրանցումը"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Դարձնել պաստառ"</string>
diff --git a/SecondaryDisplayLauncher/res/values-in/strings.xml b/SecondaryDisplayLauncher/res/values-in/strings.xml
index 4260e5f..f51d238 100644
--- a/SecondaryDisplayLauncher/res/values-in/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-in/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Tidak dapat meluncurkan aktivitas"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Tambahkan pintasan app"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Setel wallpaper"</string>
diff --git a/SecondaryDisplayLauncher/res/values-is/strings.xml b/SecondaryDisplayLauncher/res/values-is/strings.xml
index 3435c60..e8b3e97 100644
--- a/SecondaryDisplayLauncher/res/values-is/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-is/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Ekki tókst að ræsa aðgerðina"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Bæta við flýtileið forrita"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Velja veggfóður"</string>
diff --git a/SecondaryDisplayLauncher/res/values-it/strings.xml b/SecondaryDisplayLauncher/res/values-it/strings.xml
index 4cf2f44..4941515 100644
--- a/SecondaryDisplayLauncher/res/values-it/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-it/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Impossibile avviare l\'attività"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Aggiungi scorciatoia app"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Imposta sfondo"</string>
diff --git a/SecondaryDisplayLauncher/res/values-iw/strings.xml b/SecondaryDisplayLauncher/res/values-iw/strings.xml
index f904c38..06b0c42 100644
--- a/SecondaryDisplayLauncher/res/values-iw/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-iw/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"לא ניתן היה להפעיל את הפעילות"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"הוספת קיצור דרך של אפליקציה"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"הגדרת טפט"</string>
diff --git a/SecondaryDisplayLauncher/res/values-ja/strings.xml b/SecondaryDisplayLauncher/res/values-ja/strings.xml
index 3e85178..3ed7b2b 100644
--- a/SecondaryDisplayLauncher/res/values-ja/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-ja/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"アクティビティを開始できませんでした"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"アプリのショートカットを追加"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"壁紙を設定"</string>
diff --git a/SecondaryDisplayLauncher/res/values-ka/strings.xml b/SecondaryDisplayLauncher/res/values-ka/strings.xml
index 1f388e3..ac85f70 100644
--- a/SecondaryDisplayLauncher/res/values-ka/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-ka/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"აქტივობის გაშვება ვერ მოხერხდა"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"აპის მალსახმობის დამატება"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"ფონის დაყენება"</string>
diff --git a/SecondaryDisplayLauncher/res/values-kk/strings.xml b/SecondaryDisplayLauncher/res/values-kk/strings.xml
index a679b09..f9ac455 100644
--- a/SecondaryDisplayLauncher/res/values-kk/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-kk/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Әрекет іске қосылмады"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Қолданба таңбашасын енгізу"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Тұсқағаз орнату"</string>
diff --git a/SecondaryDisplayLauncher/res/values-km/strings.xml b/SecondaryDisplayLauncher/res/values-km/strings.xml
index 2f1c75f..afc050f 100644
--- a/SecondaryDisplayLauncher/res/values-km/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-km/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"មិនអាចចាប់ផ្តើមសកម្មភាពទេ"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"បញ្ចូល​ផ្លូវកាត់​កម្មវិធី"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"កំណត់​ផ្ទាំង​រូបភាព"</string>
diff --git a/SecondaryDisplayLauncher/res/values-kn/strings.xml b/SecondaryDisplayLauncher/res/values-kn/strings.xml
index aa0ccc1..09c327f 100644
--- a/SecondaryDisplayLauncher/res/values-kn/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-kn/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"ಚಟುವಟಿಕೆಯನ್ನು ಲಾಂಚ್‌ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"ಆ್ಯಪ್‌ ಶಾರ್ಟ್‌ಕಟ್ ಸೇರಿಸಿ"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"ವಾಲ್‌ಪೇಪರ್ ಹೊಂದಿಸಿ"</string>
diff --git a/SecondaryDisplayLauncher/res/values-ko/strings.xml b/SecondaryDisplayLauncher/res/values-ko/strings.xml
index 5b4b0c7..6a02ac0 100644
--- a/SecondaryDisplayLauncher/res/values-ko/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-ko/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"활동을 실행할 수 없음"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"앱 바로가기 추가"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"배경화면 설정"</string>
diff --git a/SecondaryDisplayLauncher/res/values-ky/strings.xml b/SecondaryDisplayLauncher/res/values-ky/strings.xml
index b62d486..56185fa 100644
--- a/SecondaryDisplayLauncher/res/values-ky/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-ky/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"Кошумча экранды жүргүзгүч"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Аракет аткарылган жок"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Колдонмого кыска жол кошуу"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Тушкагаз орнотуу"</string>
diff --git a/SecondaryDisplayLauncher/res/values-lo/strings.xml b/SecondaryDisplayLauncher/res/values-lo/strings.xml
index 5115547..36a6275 100644
--- a/SecondaryDisplayLauncher/res/values-lo/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-lo/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"ບໍ່ສາມາດເປີດໃຊ້ການເຄື່ອນໄຫວໄດ້"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"ເພີ່ມທາງລັດແອັບ"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"ຕັ້ງເປັນຮູບພື້ນຫຼັງ"</string>
diff --git a/SecondaryDisplayLauncher/res/values-lt/strings.xml b/SecondaryDisplayLauncher/res/values-lt/strings.xml
index 6ec68f9..8113eb6 100644
--- a/SecondaryDisplayLauncher/res/values-lt/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-lt/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Nepavyko paleisti veiklos"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Pridėti programos šaukinį"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Nustatyti ekrano foną"</string>
diff --git a/SecondaryDisplayLauncher/res/values-lv/strings.xml b/SecondaryDisplayLauncher/res/values-lv/strings.xml
index d708f13..e267933 100644
--- a/SecondaryDisplayLauncher/res/values-lv/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-lv/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Nevarēja palaist darbību"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Pievienot lietotnes saīsni"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Iestatīt fona tapeti"</string>
diff --git a/SecondaryDisplayLauncher/res/values-mk/strings.xml b/SecondaryDisplayLauncher/res/values-mk/strings.xml
index 5421ea8..e2cca03 100644
--- a/SecondaryDisplayLauncher/res/values-mk/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-mk/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"Стартер на секундарен екран"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Не можеше да се стартува активноста"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Додајте кратенка за апликација"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Поставете го тапетот"</string>
diff --git a/SecondaryDisplayLauncher/res/values-ml/strings.xml b/SecondaryDisplayLauncher/res/values-ml/strings.xml
index 308592b..864245b 100644
--- a/SecondaryDisplayLauncher/res/values-ml/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-ml/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"ആക്‌റ്റിവിറ്റി പ്രകാശിപ്പിക്കാനായില്ല"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"ആപ്പ് കുറുക്കുവഴികൾ ചേർക്കുക"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"വാൾപേപ്പർ സജ്ജീകരിക്കുക"</string>
diff --git a/SecondaryDisplayLauncher/res/values-mn/strings.xml b/SecondaryDisplayLauncher/res/values-mn/strings.xml
index f0ae3fd..85fb020 100644
--- a/SecondaryDisplayLauncher/res/values-mn/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-mn/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Үйл ажиллагааг эхлүүж чадсангүй"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Аппын товчлол нэмэх"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Ханын зураг тохируулах"</string>
diff --git a/SecondaryDisplayLauncher/res/values-mr/strings.xml b/SecondaryDisplayLauncher/res/values-mr/strings.xml
index d54cc89..6e92a2f 100644
--- a/SecondaryDisplayLauncher/res/values-mr/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-mr/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"अ‍ॅक्टिव्हिटी लाँच करता आली नाही"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"अ‍ॅप शॉर्टकट जोडा"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"वॉलपेपर सेट करा"</string>
diff --git a/SecondaryDisplayLauncher/res/values-ms/strings.xml b/SecondaryDisplayLauncher/res/values-ms/strings.xml
index 16dfee0..fd78053 100644
--- a/SecondaryDisplayLauncher/res/values-ms/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-ms/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Tidak dapat melancarkan aktiviti"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Tambah pintasan apl"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Tetapkan kertas dinding"</string>
diff --git a/SecondaryDisplayLauncher/res/values-my/strings.xml b/SecondaryDisplayLauncher/res/values-my/strings.xml
index a252c52..1521402 100644
--- a/SecondaryDisplayLauncher/res/values-my/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-my/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"အရန် ဖော်ပြမှု စတင်ခြင်းစနစ်"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"လုပ်ဆောင်ချက်ကို စတင်၍မရပါ"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"အက်ပ်ဖြတ်လမ်းလင့်ခ်ထည့်ရန်"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"နောက်ခံ သတ်မှတ်ရန်"</string>
diff --git a/SecondaryDisplayLauncher/res/values-nb/strings.xml b/SecondaryDisplayLauncher/res/values-nb/strings.xml
index b46835a..945c87b 100644
--- a/SecondaryDisplayLauncher/res/values-nb/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-nb/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Kunne ikke starte aktiviteten"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Legg til en appsnarvei"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Angi bakgrunn"</string>
diff --git a/SecondaryDisplayLauncher/res/values-ne/strings.xml b/SecondaryDisplayLauncher/res/values-ne/strings.xml
index d40d806..9a5b0a0 100644
--- a/SecondaryDisplayLauncher/res/values-ne/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-ne/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"उक्त क्रियाकलाप सुरु गर्न सकिएन"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"अनुप्रयोगको सर्टकट थप्नुहोस्‌"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"वालपेपर सेट गर्नुहोस्"</string>
diff --git a/SecondaryDisplayLauncher/res/values-nl/strings.xml b/SecondaryDisplayLauncher/res/values-nl/strings.xml
index ad10233..8767708 100644
--- a/SecondaryDisplayLauncher/res/values-nl/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-nl/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Kan de activiteit niet starten"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"App-snelkoppeling toevoegen"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Achtergrond instellen"</string>
diff --git a/SecondaryDisplayLauncher/res/values-or/strings.xml b/SecondaryDisplayLauncher/res/values-or/strings.xml
index 6f04f99..9bc5725 100644
--- a/SecondaryDisplayLauncher/res/values-or/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-or/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"ଗତିବିଧିକୁ ଲଞ୍ଚ କରାଯାଇପାରିଲା ନାହିଁ"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"ଆପ୍‌ ସର୍ଟକଟ୍‌ ଯୋଗ କରନ୍ତୁ"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"ୱାଲ୍‌‌ପେପର୍‌କୁ ସେଟ୍ କରନ୍ତୁ"</string>
diff --git a/SecondaryDisplayLauncher/res/values-pa/strings.xml b/SecondaryDisplayLauncher/res/values-pa/strings.xml
index 8657ef5..c5dd582 100644
--- a/SecondaryDisplayLauncher/res/values-pa/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-pa/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"ਸਰਗਰਮੀ ਨੂੰ ਲਾਂਚ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"ਐਪ ਸ਼ਾਰਟਕੱਟ ਸ਼ਾਮਲ ਕਰੋ"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"ਵਾਲਪੇਪਰ ਸੈੱਟ ਕਰੋ"</string>
diff --git a/SecondaryDisplayLauncher/res/values-pl/strings.xml b/SecondaryDisplayLauncher/res/values-pl/strings.xml
index 1eec235..e8efaed 100644
--- a/SecondaryDisplayLauncher/res/values-pl/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-pl/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Nie udało się uruchomić aktywności"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Dodaj skrót do aplikacji"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Ustaw tapetę"</string>
diff --git a/SecondaryDisplayLauncher/res/values-pt-rPT/strings.xml b/SecondaryDisplayLauncher/res/values-pt-rPT/strings.xml
index 7fadf3c..67c7557 100644
--- a/SecondaryDisplayLauncher/res/values-pt-rPT/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-pt-rPT/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Não foi possível iniciar a atividade."</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Adicionar atalho de aplicação"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Definir imagem de fundo"</string>
diff --git a/SecondaryDisplayLauncher/res/values-pt/strings.xml b/SecondaryDisplayLauncher/res/values-pt/strings.xml
index e8f6a55..201fc07 100644
--- a/SecondaryDisplayLauncher/res/values-pt/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-pt/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Não foi possível abrir a atividade"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Adicionar atalho de apps"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Definir plano de fundo"</string>
diff --git a/SecondaryDisplayLauncher/res/values-ro/strings.xml b/SecondaryDisplayLauncher/res/values-ro/strings.xml
index d485b2b..e2e21c5 100644
--- a/SecondaryDisplayLauncher/res/values-ro/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-ro/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Nu s-a putut lansa activitatea"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Adăugați comanda rapidă pentru aplicație"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Setați imaginea de fundal"</string>
diff --git a/SecondaryDisplayLauncher/res/values-ru/strings.xml b/SecondaryDisplayLauncher/res/values-ru/strings.xml
index 5c4df1d..64ba00e 100644
--- a/SecondaryDisplayLauncher/res/values-ru/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-ru/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Не удалось запустить объект activity"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Добавить ярлык приложения"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Установить обои"</string>
diff --git a/SecondaryDisplayLauncher/res/values-si/strings.xml b/SecondaryDisplayLauncher/res/values-si/strings.xml
index f665f9e..ac492eb 100644
--- a/SecondaryDisplayLauncher/res/values-si/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-si/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"ක්‍රියාකාරකම දියත් කිරීමට නොහැකි විය"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"යෙදුම් කෙටිමඟ එක් කරන්න"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"බිතුපත සකසන්න"</string>
diff --git a/SecondaryDisplayLauncher/res/values-sk/strings.xml b/SecondaryDisplayLauncher/res/values-sk/strings.xml
index 5e8d67a..5e6fa7a 100644
--- a/SecondaryDisplayLauncher/res/values-sk/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-sk/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Aktivitu sa nepodarilo spustiť"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Pridať odkaz do aplikácie"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Nastaviť tapetu"</string>
diff --git a/SecondaryDisplayLauncher/res/values-sl/strings.xml b/SecondaryDisplayLauncher/res/values-sl/strings.xml
index 864a9bb..f54dec9 100644
--- a/SecondaryDisplayLauncher/res/values-sl/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-sl/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"Zaganjalnik sekundarnega zaslona"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Dejavnosti ni bilo mogoče zagnati"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Dodaj bližnjico do aplikacije"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Nastavi ozadje"</string>
diff --git a/SecondaryDisplayLauncher/res/values-sq/strings.xml b/SecondaryDisplayLauncher/res/values-sq/strings.xml
index 486f82a..e626dd1 100644
--- a/SecondaryDisplayLauncher/res/values-sq/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-sq/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"Nisësi i ekranit dytësor"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Aktiviteti nuk mund të hapej"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Shto shkurtoren e aplikacionit"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Cakto imazhin e sfondit"</string>
diff --git a/SecondaryDisplayLauncher/res/values-sr/strings.xml b/SecondaryDisplayLauncher/res/values-sr/strings.xml
index e85b741..94214f1 100644
--- a/SecondaryDisplayLauncher/res/values-sr/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-sr/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Покретање активности није успело"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Додај пречицу за апликацију"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Подесите позадину"</string>
diff --git a/SecondaryDisplayLauncher/res/values-sv/strings.xml b/SecondaryDisplayLauncher/res/values-sv/strings.xml
index ec858db..53e17ef 100644
--- a/SecondaryDisplayLauncher/res/values-sv/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-sv/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Det gick inte att starta aktiviteten"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Lägg till appgenväg"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Ange bakgrund"</string>
diff --git a/SecondaryDisplayLauncher/res/values-sw/strings.xml b/SecondaryDisplayLauncher/res/values-sw/strings.xml
index 27e404d..490561a 100644
--- a/SecondaryDisplayLauncher/res/values-sw/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-sw/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"Mwonekano Mbadala wa Kifungua Programu"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Imeshindwa kuanzisha shughuli"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Ongeza njia ya mkato ya programu"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Weka mandhari"</string>
diff --git a/SecondaryDisplayLauncher/res/values-ta/strings.xml b/SecondaryDisplayLauncher/res/values-ta/strings.xml
index e2cfb4b..2f1262f 100644
--- a/SecondaryDisplayLauncher/res/values-ta/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-ta/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"நடவடிக்கையைத் துவக்க இயலவில்லை"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"ஆப்ஸ் குறுக்குவழியைச் சேர்"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"வால்பேப்பரை அமை"</string>
diff --git a/SecondaryDisplayLauncher/res/values-te/strings.xml b/SecondaryDisplayLauncher/res/values-te/strings.xml
index f204e2a..3dd3c9b 100644
--- a/SecondaryDisplayLauncher/res/values-te/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-te/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"కార్యకలాపాన్ని ప్రారంభించడం సాధ్యం కాలేదు"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"యాప్ షార్ట్‌కట్‌ని జోడించు"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"వాల్‌పేపర్‌ను సెట్ చేయండి"</string>
diff --git a/SecondaryDisplayLauncher/res/values-th/strings.xml b/SecondaryDisplayLauncher/res/values-th/strings.xml
index ab4ca1b..6368950 100644
--- a/SecondaryDisplayLauncher/res/values-th/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-th/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"เปิดกิจกรรมไม่ได้"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"เพิ่มทางลัดของแอป"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"ตั้งวอลเปเปอร์"</string>
diff --git a/SecondaryDisplayLauncher/res/values-tl/strings.xml b/SecondaryDisplayLauncher/res/values-tl/strings.xml
index 2189cf9..192e5c4 100644
--- a/SecondaryDisplayLauncher/res/values-tl/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-tl/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Hindi mailunsad ang aktibidad"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Magdagdag ng shortcut ng app"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Magtakda ng wallpaper"</string>
diff --git a/SecondaryDisplayLauncher/res/values-tr/strings.xml b/SecondaryDisplayLauncher/res/values-tr/strings.xml
index 5a47662..e7ed998 100644
--- a/SecondaryDisplayLauncher/res/values-tr/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-tr/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"İşlem başlatılamadı"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Uygulama kısayolu ekle"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Duvar kağıdı ayarla"</string>
diff --git a/SecondaryDisplayLauncher/res/values-uk/strings.xml b/SecondaryDisplayLauncher/res/values-uk/strings.xml
index 6301b1d..e465995 100644
--- a/SecondaryDisplayLauncher/res/values-uk/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-uk/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"Панель запуску додаткового екрана"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Не вдалося запустити активність"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Розмістити ярлик додатка"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Вибрати фоновий малюнок"</string>
diff --git a/SecondaryDisplayLauncher/res/values-ur/strings.xml b/SecondaryDisplayLauncher/res/values-ur/strings.xml
index 35ae0dc..e4c8641 100644
--- a/SecondaryDisplayLauncher/res/values-ur/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-ur/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"سرگرمی کو شروع نہیں کیا جا سکا"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"ایپ شارٹ کٹ شامل کریں"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"وال پیپر سیٹ کریں"</string>
diff --git a/SecondaryDisplayLauncher/res/values-uz/strings.xml b/SecondaryDisplayLauncher/res/values-uz/strings.xml
index 5708a1a..585739d 100644
--- a/SecondaryDisplayLauncher/res/values-uz/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-uz/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Faollik ishga tushmadi"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Yorliq yaratish"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Fonga rasm oʻrnatish"</string>
diff --git a/SecondaryDisplayLauncher/res/values-vi/strings.xml b/SecondaryDisplayLauncher/res/values-vi/strings.xml
index 4cd3943..15a1a44 100644
--- a/SecondaryDisplayLauncher/res/values-vi/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-vi/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Không thể chạy hoạt động"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Thêm lối tắt ứng dụng"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Đặt hình nền"</string>
diff --git a/SecondaryDisplayLauncher/res/values-zh-rCN/strings.xml b/SecondaryDisplayLauncher/res/values-zh-rCN/strings.xml
index 2318f92..3358499 100644
--- a/SecondaryDisplayLauncher/res/values-zh-rCN/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-zh-rCN/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"辅助显示屏启动器"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"无法启动该操作组件"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"添加应用快捷方式"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"设置壁纸"</string>
diff --git a/SecondaryDisplayLauncher/res/values-zh-rHK/strings.xml b/SecondaryDisplayLauncher/res/values-zh-rHK/strings.xml
index 3ac8ac2..bf76f29 100644
--- a/SecondaryDisplayLauncher/res/values-zh-rHK/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-zh-rHK/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"次要顯示屏啟動器"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"無法啟動活動"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"新增應用程式捷徑"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"設定桌布"</string>
diff --git a/SecondaryDisplayLauncher/res/values-zh-rTW/strings.xml b/SecondaryDisplayLauncher/res/values-zh-rTW/strings.xml
index a0e4180..bf76f29 100644
--- a/SecondaryDisplayLauncher/res/values-zh-rTW/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-zh-rTW/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"無法啟動活動"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"新增應用程式捷徑"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"設定桌布"</string>
diff --git a/SecondaryDisplayLauncher/res/values-zu/strings.xml b/SecondaryDisplayLauncher/res/values-zu/strings.xml
index 9dc423c..ad2f6b9 100644
--- a/SecondaryDisplayLauncher/res/values-zu/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-zu/strings.xml
@@ -19,7 +19,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="8116646702313548897">"SecondaryDisplayLauncher"</string>
     <string name="couldnt_launch" msgid="7873588052226763866">"Ayikwazanga ukuqalisa umsebenzi"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"Engeza isinqamuleli sohlelo lokusebenza"</string>
     <string name="set_wallpaper" msgid="6475195450505435904">"Setha isithombe sangemuva"</string>
diff --git a/build.gradle b/build.gradle
index 33409c5..4191d47 100644
--- a/build.gradle
+++ b/build.gradle
@@ -36,41 +36,56 @@
         targetCompatibility JavaVersion.VERSION_1_8
     }
 
-    flavorDimensions "default"
+    // The flavor dimensions for build variants (e.g. aospWithQuickstep, aospWithoutQuickstep)
+    // See: https://developer.android.com/studio/build/build-variants#flavor-dimensions
+    flavorDimensions "app", "recents"
 
     productFlavors {
         aosp {
-            dimension "default"
+            dimension "app"
             applicationId 'com.android.launcher3'
             testApplicationId 'com.android.launcher3.tests'
         }
 
         l3go {
-            dimension "default"
+            dimension "app"
             applicationId 'com.android.launcher3'
             testApplicationId 'com.android.launcher3.tests'
         }
 
-        quickstep {
-            dimension "default"
-            applicationId 'com.android.launcher3'
-            testApplicationId 'com.android.launcher3.tests'
+        withQuickstep {
+            dimension "recents"
 
             minSdkVersion 28
         }
+
+        withQuickstepIconRecents {
+            dimension "recents"
+
+            minSdkVersion 28
+        }
+
+        withoutQuickstep {
+            dimension "recents"
+        }
     }
 
     // Disable release builds for now
     android.variantFilter { variant ->
         if (variant.buildType.name.endsWith('release')) {
-            variant.setIgnore(true);
+            variant.setIgnore(true)
+        }
+
+        // Icon recents is Go only
+        if (name.contains("WithQuickstepIconRecents") && !name.contains("l3go")) {
+            variant.setIgnore(true)
         }
     }
 
     sourceSets {
         main {
             res.srcDirs = ['res']
-            java.srcDirs = ['src', 'src_shortcuts_overrides']
+            java.srcDirs = ['src', 'src_plugins']
             manifest.srcFile 'AndroidManifest-common.xml'
             proto {
                 srcDir 'protos/'
@@ -93,18 +108,29 @@
         }
 
         aosp {
-            java.srcDirs = ['src_flags', "src_ui_overrides"]
+            java.srcDirs = ['src_flags', 'src_shortcuts_overrides']
+            manifest.srcFile "AndroidManifest.xml"
         }
 
         l3go {
             res.srcDirs = ['go/res']
-            java.srcDirs = ['go/src', "src_ui_overrides"]
+            java.srcDirs = ['go/src']
             manifest.srcFile "go/AndroidManifest.xml"
         }
 
-        quickstep {
-            res.srcDirs = ['quickstep/res']
-            java.srcDirs = ['src_flags', 'quickstep/src']
+        withoutQuickstep {
+            java.srcDirs = ['src_ui_overrides']
+        }
+
+        withQuickstep {
+            res.srcDirs = ['quickstep/res', 'quickstep/recents_ui_overrides/res']
+            java.srcDirs = ['quickstep/src', 'quickstep/recents_ui_overrides/src']
+            manifest.srcFile "quickstep/AndroidManifest.xml"
+        }
+
+        withQuickstepIconRecents {
+            res.srcDirs = ['quickstep/res', 'go/quickstep/res']
+            java.srcDirs = ['quickstep/src', 'go/quickstep/src']
             manifest.srcFile "quickstep/AndroidManifest.xml"
         }
     }
@@ -121,14 +147,17 @@
     implementation "androidx.dynamicanimation:dynamicanimation:${ANDROID_X_VERSION}"
     implementation "androidx.recyclerview:recyclerview:${ANDROID_X_VERSION}"
     implementation "androidx.preference:preference:${ANDROID_X_VERSION}"
-    implementation PROTOBUF_DEPENDENCY
     implementation project(':IconLoader')
+    implementation fileTree(dir: "libs", include: 'launcher_protos.jar')
 
-    // This is already included in sysui_shared
-    aospImplementation fileTree(dir: "libs", include: 'plugin_core.jar')
-    l3goImplementation fileTree(dir: "libs", include: 'plugin_core.jar')
+    // Recents lib dependency
+    withQuickstepImplementation fileTree(dir: "quickstep/libs", include: 'sysui_shared.jar')
 
-    quickstepImplementation fileTree(dir: "quickstep/libs", include: 'sysui_shared.jar')
+    // Recents lib dependency for Go
+    withQuickstepIconRecentsImplementation fileTree(dir: "quickstep/libs", include: 'sysui_shared.jar')
+
+    // Required for AOSP to compile. This is already included in the sysui_shared.jar
+    withoutQuickstepImplementation fileTree(dir: "libs", include: 'plugin_core.jar')
 
     testImplementation 'junit:junit:4.12'
     androidTestImplementation "org.mockito:mockito-core:1.9.5"
diff --git a/go/quickstep/res/layout/icon_recents_root_view.xml b/go/quickstep/res/layout/icon_recents_root_view.xml
new file mode 100644
index 0000000..82d5890
--- /dev/null
+++ b/go/quickstep/res/layout/icon_recents_root_view.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2019 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.quickstep.views.IconRecentsView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center">
+    <!-- TODO(114136250): Remove this temporary placeholder view for Go recents -->
+    <TextView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        android:text="Stub!"
+        android:textSize="40sp"/>
+</com.android.quickstep.views.IconRecentsView>
\ No newline at end of file
diff --git a/go/quickstep/res/layout/overview_panel.xml b/go/quickstep/res/layout/overview_panel.xml
new file mode 100644
index 0000000..601edce
--- /dev/null
+++ b/go/quickstep/res/layout/overview_panel.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2019 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.
+-->
+<fragment android:name="com.android.quickstep.IconRecentsFragment"
+          xmlns:android="http://schemas.android.com/apk/res/android"
+          android:id="@+id/low_ram_recents_fragment"
+          android:layout_width="match_parent"
+          android:layout_height="match_parent"/>
diff --git a/quickstep/res/values/override.xml b/go/quickstep/res/values/override.xml
similarity index 88%
copy from quickstep/res/values/override.xml
copy to go/quickstep/res/values/override.xml
index d683659..7636fb3 100644
--- a/quickstep/res/values/override.xml
+++ b/go/quickstep/res/values/override.xml
@@ -14,8 +14,10 @@
      limitations under the License.
 -->
 
+<!-- Class overrides for Go version of launcher with Go recents. -->
+
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-  <string name="app_transition_manager_class" translatable="false">com.android.launcher3.LauncherAppTransitionManagerImpl</string>
+  <string name="app_transition_manager_class" translatable="false">com.android.launcher3.GoLauncherAppTransitionManagerImpl</string>
 
   <string name="instant_app_resolver_class" translatable="false">com.android.quickstep.InstantAppResolverImpl</string>
 
diff --git a/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java b/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java
new file mode 100644
index 0000000..95d8c4e
--- /dev/null
+++ b/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java
@@ -0,0 +1,65 @@
+package com.android.launcher3;
+
+import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.quickstep.views.IconRecentsView.CONTENT_ALPHA;
+
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.app.ActivityOptions;
+import android.content.Context;
+import android.view.View;
+
+import com.android.quickstep.views.IconRecentsView;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+/**
+ * A {@link QuickstepAppTransitionManagerImpl} with recents-specific app transitions based off
+ * {@link com.android.quickstep.views.IconRecentsView}.
+ */
+public final class GoLauncherAppTransitionManagerImpl extends QuickstepAppTransitionManagerImpl {
+
+    public GoLauncherAppTransitionManagerImpl(Context context) {
+        super(context);
+    }
+
+    @Override
+    protected boolean isLaunchingFromRecents(View v, RemoteAnimationTargetCompat[] targets) {
+        return mLauncher.getStateManager().getState().overviewUi;
+    }
+
+    @Override
+    protected boolean isQuickSwitchInProgress() {
+        // Go does not support quick scrub.
+        return false;
+    }
+
+    @Override
+    protected ActivityOptions getQuickSwitchActivityOptions() {
+        // Go does not support quick scrub.
+        return null;
+    }
+
+    @Override
+    protected void composeRecentsLaunchAnimator(AnimatorSet anim, View v,
+            RemoteAnimationTargetCompat[] targets, boolean launcherClosing) {
+        //TODO: Implement this based off IconRecentsView
+    }
+
+    @Override
+    protected Runnable composeViewContentAnimator(AnimatorSet anim, float[] alphas, float[] trans) {
+        IconRecentsView overview = mLauncher.getOverviewPanel();
+        ObjectAnimator alpha = ObjectAnimator.ofFloat(overview,
+                CONTENT_ALPHA, alphas);
+        alpha.setDuration(CONTENT_ALPHA_DURATION);
+        alpha.setInterpolator(LINEAR);
+        anim.play(alpha);
+
+        ObjectAnimator transY = ObjectAnimator.ofFloat(overview, View.TRANSLATION_Y, trans);
+        transY.setInterpolator(AGGRESSIVE_EASE);
+        transY.setDuration(CONTENT_TRANSLATION_DURATION);
+        anim.play(transY);
+
+        return mLauncher.getStateManager()::reapplyState;
+    }
+}
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/go/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
new file mode 100644
index 0000000..283e349
--- /dev/null
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2019 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.uioverrides;
+
+import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
+
+import android.graphics.Rect;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.R;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.quickstep.RecentsModel;
+
+/**
+ * Definition for overview state
+ */
+public class OverviewState extends LauncherState {
+
+    // TODO: Remove this when we refactor BackgroundAppState
+    protected static final Rect sTempRect = new Rect();
+
+    private static final int STATE_FLAGS = FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED
+            | FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI | FLAG_DISABLE_ACCESSIBILITY;
+
+    public OverviewState(int id) {
+        this(id, OVERVIEW_TRANSITION_MS, STATE_FLAGS);
+    }
+
+    protected OverviewState(int id, int transitionDuration, int stateFlags) {
+        super(id, LauncherLogProto.ContainerType.TASKSWITCHER, transitionDuration, stateFlags);
+    }
+
+    @Override
+    public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
+        return new float[] {1f, 0f};
+    }
+
+    @Override
+    public void onStateDisabled(Launcher launcher) {
+        RecentsModel.INSTANCE.get(launcher).resetAssistCache();
+    }
+
+    @Override
+    public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
+        return new PageAlphaProvider(DEACCEL_2) {
+            @Override
+            public float getPageAlpha(int pageIndex) {
+                return 0;
+            }
+        };
+    }
+
+    @Override
+    public int getVisibleElements(Launcher launcher) {
+        return NONE;
+    }
+
+    @Override
+    public float getWorkspaceScrimAlpha(Launcher launcher) {
+        return 0.5f;
+    }
+
+    @Override
+    public String getDescription(Launcher launcher) {
+        return launcher.getString(R.string.accessibility_desc_recent_apps);
+    }
+
+    @Override
+    public void onBackPressed(Launcher launcher) {
+        // TODO: Add logic to go back to task if coming from a currently running task.
+        super.onBackPressed(launcher);
+    }
+
+
+    public static float getDefaultSwipeHeight(Launcher launcher) {
+        DeviceProfile dp = launcher.getDeviceProfile();
+        return dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
+    }
+}
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/PortraitOverviewStateTouchHelper.java b/go/quickstep/src/com/android/launcher3/uioverrides/PortraitOverviewStateTouchHelper.java
new file mode 100644
index 0000000..a3b41b0
--- /dev/null
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/PortraitOverviewStateTouchHelper.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2019 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.uioverrides;
+
+import android.view.MotionEvent;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.util.PendingAnimation;
+
+/**
+ * Helper class for {@link PortraitStatesTouchController} that determines swipeable regions and
+ * animations on the overview state that depend on the recents implementation.
+ */
+public final class PortraitOverviewStateTouchHelper {
+
+    public PortraitOverviewStateTouchHelper(Launcher launcher) {}
+
+    /**
+     * Whether or not {@link PortraitStatesTouchController} should intercept the touch when on the
+     * overview state.
+     *
+     * @param ev the motion event
+     * @return true if we should intercept the motion event
+     */
+    boolean canInterceptTouch(MotionEvent ev) {
+        // Go does not support swiping to all-apps from recents.
+        return false;
+    }
+
+    /**
+     * Whether or not swiping down to leave overview state should return to the currently running
+     * task app.
+     *
+     * @return true if going back should take the user to the currently running task
+     */
+    boolean shouldSwipeDownReturnToApp() {
+        // Go does not support swiping tasks down to launch tasks from recents.
+        return false;
+    }
+
+    /**
+     * Create the animation for going from overview to the task app via swiping.
+     *
+     * @param duration how long the animation should be
+     * @return the animation
+     */
+    PendingAnimation createSwipeDownToTaskAppAnimation(long duration) {
+        // Go does not support swiping tasks down to launch tasks from recents.
+        return null;
+    }
+}
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
new file mode 100644
index 0000000..d0c255c
--- /dev/null
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2019 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.uioverrides;
+
+import static android.view.View.VISIBLE;
+
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+
+import android.view.View;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherStateManager.StateHandler;
+import com.android.launcher3.util.TouchController;
+
+/**
+ * Provides recents-related {@link UiFactory} logic and classes.
+ */
+public final class RecentsUiFactory {
+
+    // Scale recents takes before animating in
+    private static final float RECENTS_PREPARE_SCALE = 1.33f;
+
+    private RecentsUiFactory() {}
+
+    /**
+     * Creates and returns a touch controller for swiping recents tasks.
+     *
+     * @param launcher the launcher activity
+     * @return the touch controller for recents tasks
+     */
+    public static TouchController createTaskSwipeController(Launcher launcher) {
+        // We leave all input handling to the view itself.
+        return null;
+    }
+
+    /**
+     * Creates and returns the controller responsible for recents view state transitions.
+     *
+     * @param launcher the launcher activity
+     * @return state handler for recents
+     */
+    public static StateHandler createRecentsViewStateController(Launcher launcher) {
+        return new RecentsViewStateController(launcher);
+    }
+
+    /**
+     * Prepare the recents view to animate in.
+     *
+     * @param launcher the launcher activity
+     */
+    public static void prepareToShowRecents(Launcher launcher) {
+        View overview = launcher.getOverviewPanel();
+        if (overview.getVisibility() != VISIBLE) {
+            SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE);
+        }
+    }
+
+    /**
+     * Clean-up logic that occurs when recents is no longer in use/visible.
+     *
+     * @param launcher the launcher activity
+     */
+    public static void resetRecents(Launcher launcher) {}
+
+    /**
+     * Recents logic that triggers when launcher state changes or launcher activity stops/resumes.
+     *
+     * @param launcher the launcher activity
+     */
+    public static void onLauncherStateOrResumeChanged(Launcher launcher) {}
+}
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
new file mode 100644
index 0000000..f1cb75b
--- /dev/null
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019 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.uioverrides;
+
+import static com.android.quickstep.views.IconRecentsView.CONTENT_ALPHA;
+import static com.android.quickstep.views.IconRecentsView.TRANSLATION_Y_FACTOR;
+
+import android.util.FloatProperty;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.Launcher;
+import com.android.quickstep.views.IconRecentsView;
+
+/**
+ * State handler for Go's {@link IconRecentsView}.
+ */
+public final class RecentsViewStateController extends
+        BaseRecentsViewStateController<IconRecentsView> {
+
+    public RecentsViewStateController(@NonNull Launcher launcher) {
+        super(launcher);
+    }
+
+    @Override
+    FloatProperty<IconRecentsView> getTranslationYFactorProperty() {
+        return TRANSLATION_Y_FACTOR;
+    }
+
+    @Override
+    FloatProperty<IconRecentsView> getContentAlphaProperty() {
+        return CONTENT_ALPHA;
+    }
+}
diff --git a/go/quickstep/src/com/android/quickstep/IconRecentsFragment.java b/go/quickstep/src/com/android/quickstep/IconRecentsFragment.java
new file mode 100644
index 0000000..facf0d2
--- /dev/null
+++ b/go/quickstep/src/com/android/quickstep/IconRecentsFragment.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.R;
+
+public class IconRecentsFragment extends Fragment {
+    @Nullable
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.icon_recents_root_view, container, false);
+    }
+}
\ No newline at end of file
diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
new file mode 100644
index 0000000..e4741e9
--- /dev/null
+++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.views;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.FloatProperty;
+import android.view.ViewDebug;
+import android.widget.FrameLayout;
+
+/**
+ * Root view for the icon recents view.
+ */
+public final class IconRecentsView extends FrameLayout {
+
+    public static final FloatProperty<IconRecentsView> TRANSLATION_Y_FACTOR =
+            new FloatProperty<IconRecentsView>("translationYFactor") {
+
+                @Override
+                public void setValue(IconRecentsView view, float v) {
+                    view.setTranslationYFactor(v);
+                }
+
+                @Override
+                public Float get(IconRecentsView view) {
+                    return view.mTranslationYFactor;
+                }
+            };
+
+    public static final FloatProperty<IconRecentsView> CONTENT_ALPHA =
+            new FloatProperty<IconRecentsView>("contentAlpha") {
+                @Override
+                public void setValue(IconRecentsView view, float v) {
+                    ALPHA.set(view, v);
+                }
+
+                @Override
+                public Float get(IconRecentsView view) {
+                    return ALPHA.get(view);
+                }
+            };
+
+    /**
+     * A ratio representing the view's relative placement within its padded space. For example, 0
+     * is top aligned and 0.5 is centered vertically.
+     */
+    @ViewDebug.ExportedProperty(category = "launcher")
+    private float mTranslationYFactor;
+
+    public IconRecentsView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public void setTranslationYFactor(float translationFactor) {
+        mTranslationYFactor = translationFactor;
+        setTranslationY(computeTranslationYForFactor(mTranslationYFactor));
+    }
+
+    private float computeTranslationYForFactor(float translationYFactor) {
+        return translationYFactor * (getPaddingBottom() - getPaddingTop());
+    }
+}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
index 882af59..af1b353 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
@@ -253,19 +253,24 @@
         badge.draw(target);
     }
 
+    private Bitmap createIconBitmap(Drawable icon, float scale) {
+        return createIconBitmap(icon, scale, mIconBitmapSize);
+    }
+
     /**
+     * @param icon drawable that should be flattened to a bitmap
      * @param scale the scale to apply before drawing {@param icon} on the canvas
      */
-    private Bitmap createIconBitmap(Drawable icon, float scale) {
-        Bitmap bitmap = Bitmap.createBitmap(mIconBitmapSize, mIconBitmapSize,
-                Bitmap.Config.ARGB_8888);
+    public Bitmap createIconBitmap(Drawable icon, float scale, int size) {
+        Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
+
         mCanvas.setBitmap(bitmap);
         mOldBounds.set(icon.getBounds());
 
         if (ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
-            int offset = Math.max((int) Math.ceil(BLUR_FACTOR * mIconBitmapSize),
-                    Math.round(mIconBitmapSize * (1 - scale) / 2 ));
-            icon.setBounds(offset, offset, mIconBitmapSize - offset, mIconBitmapSize - offset);
+            int offset = Math.max((int) Math.ceil(BLUR_FACTOR * size),
+                    Math.round(size * (1 - scale) / 2 ));
+            icon.setBounds(offset, offset, size - offset, size - offset);
             icon.draw(mCanvas);
         } else {
             if (icon instanceof BitmapDrawable) {
@@ -275,8 +280,8 @@
                     bitmapDrawable.setTargetDensity(mContext.getResources().getDisplayMetrics());
                 }
             }
-            int width = mIconBitmapSize;
-            int height = mIconBitmapSize;
+            int width = size;
+            int height = size;
 
             int intrinsicWidth = icon.getIntrinsicWidth();
             int intrinsicHeight = icon.getIntrinsicHeight();
@@ -289,11 +294,11 @@
                     width = (int) (height * ratio);
                 }
             }
-            final int left = (mIconBitmapSize - width) / 2;
-            final int top = (mIconBitmapSize - height) / 2;
+            final int left = (size - width) / 2;
+            final int top = (size - height) / 2;
             icon.setBounds(left, top, left + width, top + height);
             mCanvas.save();
-            mCanvas.scale(scale, scale, mIconBitmapSize / 2, mIconBitmapSize / 2);
+            mCanvas.scale(scale, scale, size / 2, size / 2);
             icon.draw(mCanvas);
             mCanvas.restore();
 
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
index 8af310c..ab97344 100644
--- a/quickstep/libs/sysui_shared.jar
+++ b/quickstep/libs/sysui_shared.jar
Binary files differ
diff --git a/quickstep/res/layout/overview_panel.xml b/quickstep/recents_ui_overrides/res/layout/overview_panel.xml
similarity index 100%
rename from quickstep/res/layout/overview_panel.xml
rename to quickstep/recents_ui_overrides/res/layout/overview_panel.xml
diff --git a/quickstep/res/values/override.xml b/quickstep/recents_ui_overrides/res/values/override.xml
similarity index 95%
rename from quickstep/res/values/override.xml
rename to quickstep/recents_ui_overrides/res/values/override.xml
index d683659..c60cf5a 100644
--- a/quickstep/res/values/override.xml
+++ b/quickstep/recents_ui_overrides/res/values/override.xml
@@ -14,6 +14,8 @@
      limitations under the License.
 -->
 
+<!-- Class overrides for launcher with quickstep. -->
+
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
   <string name="app_transition_manager_class" translatable="false">com.android.launcher3.LauncherAppTransitionManagerImpl</string>
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
new file mode 100644
index 0000000..9921455
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -0,0 +1,148 @@
+/*
+ * 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;
+
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.quickstep.TaskUtils.findTaskViewToLaunch;
+import static com.android.quickstep.TaskUtils.getRecentsWindowAnimator;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.app.ActivityOptions;
+import android.content.Context;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.Interpolators;
+import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+/**
+ * A {@link QuickstepAppTransitionManagerImpl} that also implements recents transitions from
+ * {@link RecentsView}.
+ */
+public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransitionManagerImpl {
+
+    private RecentsView mRecentsView;
+
+    public LauncherAppTransitionManagerImpl(Context context) {
+        super(context);
+        mRecentsView = mLauncher.getOverviewPanel();
+    }
+
+    @Override
+    protected boolean isLaunchingFromRecents(@NonNull View v,
+            @Nullable RemoteAnimationTargetCompat[] targets) {
+        return mLauncher.getStateManager().getState().overviewUi
+                && findTaskViewToLaunch(mLauncher, v, targets) != null;
+    }
+
+    @Override
+    protected boolean isQuickSwitchInProgress() {
+        return mRecentsView.getQuickScrubController().isQuickSwitch();
+    }
+
+    @Override
+    protected ActivityOptions getQuickSwitchActivityOptions() {
+        return ActivityOptions.makeCustomAnimation(mLauncher, R.anim.no_anim,
+                R.anim.no_anim);
+    }
+
+    @Override
+    protected void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
+            @NonNull RemoteAnimationTargetCompat[] targets, boolean launcherClosing) {
+        RecentsView recentsView = mLauncher.getOverviewPanel();
+        boolean skipLauncherChanges = !launcherClosing;
+        boolean isLaunchingFromQuickscrub =
+                recentsView.getQuickScrubController().isWaitingForTaskLaunch();
+
+        TaskView taskView = findTaskViewToLaunch(mLauncher, v, targets);
+
+        int duration = isLaunchingFromQuickscrub
+                ? RECENTS_QUICKSCRUB_LAUNCH_DURATION
+                : RECENTS_LAUNCH_DURATION;
+
+        ClipAnimationHelper helper = new ClipAnimationHelper(mLauncher);
+        anim.play(getRecentsWindowAnimator(taskView, skipLauncherChanges, targets, helper)
+                .setDuration(duration));
+
+        Animator childStateAnimation = null;
+        // Found a visible recents task that matches the opening app, lets launch the app from there
+        Animator launcherAnim;
+        final AnimatorListenerAdapter windowAnimEndListener;
+        if (launcherClosing) {
+            launcherAnim = recentsView.createAdjacentPageAnimForTaskLaunch(taskView, helper);
+            launcherAnim.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
+            launcherAnim.setDuration(duration);
+
+            // Make sure recents gets fixed up by resetting task alphas and scales, etc.
+            windowAnimEndListener = new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mLauncher.getStateManager().moveToRestState();
+                    mLauncher.getStateManager().reapplyState();
+                }
+            };
+        } else {
+            AnimatorPlaybackController controller =
+                    mLauncher.getStateManager().createAnimationToNewWorkspace(NORMAL, duration);
+            controller.dispatchOnStart();
+            childStateAnimation = controller.getTarget();
+            launcherAnim = controller.getAnimationPlayer().setDuration(duration);
+            windowAnimEndListener = new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mLauncher.getStateManager().goToState(NORMAL, false);
+                }
+            };
+        }
+        anim.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(anim, childStateAnimation);
+        anim.addListener(windowAnimEndListener);
+    }
+
+    @Override
+    protected Runnable composeViewContentAnimator(@NonNull AnimatorSet anim, float[] alphas,
+            float[] trans) {
+        RecentsView overview = mLauncher.getOverviewPanel();
+        ObjectAnimator alpha = ObjectAnimator.ofFloat(overview,
+                RecentsView.CONTENT_ALPHA, alphas);
+        alpha.setDuration(CONTENT_ALPHA_DURATION);
+        alpha.setInterpolator(LINEAR);
+        anim.play(alpha);
+
+        ObjectAnimator transY = ObjectAnimator.ofFloat(overview, View.TRANSLATION_Y, trans);
+        transY.setInterpolator(AGGRESSIVE_EASE);
+        transY.setDuration(CONTENT_TRANSLATION_DURATION);
+        anim.play(transY);
+
+        return mLauncher.getStateManager()::reapplyState;
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewState.java
similarity index 94%
rename from quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
rename to quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewState.java
index 8f1d46c..de6f7a7 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -18,6 +18,7 @@
 import static com.android.launcher3.AbstractFloatingView.TYPE_QUICKSTEP_PREVIEW;
 import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
 
 import android.graphics.Rect;
@@ -75,7 +76,11 @@
     public void onStateEnabled(Launcher launcher) {
         RecentsView rv = launcher.getOverviewPanel();
         rv.setOverviewStateEnabled(true);
-        AbstractFloatingView.closeAllOpenViewsExcept(launcher, TYPE_QUICKSTEP_PREVIEW);
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            AbstractFloatingView.closeAllOpenViews(launcher);
+        } else {
+            AbstractFloatingView.closeAllOpenViewsExcept(launcher, TYPE_QUICKSTEP_PREVIEW);
+        }
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PortraitOverviewStateTouchHelper.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PortraitOverviewStateTouchHelper.java
new file mode 100644
index 0000000..eead4c8
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PortraitOverviewStateTouchHelper.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2019 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.uioverrides;
+
+import static com.android.launcher3.uioverrides.PortraitStatesTouchController.isTouchOverHotseat;
+
+import android.view.MotionEvent;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.util.PendingAnimation;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
+
+/**
+ * Helper class for {@link PortraitStatesTouchController} that determines swipeable regions and
+ * animations on the overview state that depend on the recents implementation.
+ */
+public final class PortraitOverviewStateTouchHelper {
+
+    RecentsView mRecentsView;
+    Launcher mLauncher;
+
+    public PortraitOverviewStateTouchHelper(Launcher launcher) {
+        mLauncher = launcher;
+        mRecentsView = launcher.getOverviewPanel();
+    }
+
+    /**
+     * Whether or not {@link PortraitStatesTouchController} should intercept the touch when on the
+     * overview state.
+     *
+     * @param ev the motion event
+     * @return true if we should intercept the motion event
+     */
+    boolean canInterceptTouch(MotionEvent ev) {
+        if (mRecentsView.getChildCount() > 0) {
+            // Allow swiping up in the gap between the hotseat and overview.
+            return ev.getY() >= mRecentsView.getChildAt(0).getBottom();
+        } else {
+            // If there are no tasks, we only intercept if we're below the hotseat height.
+            return isTouchOverHotseat(mLauncher, ev);
+        }
+    }
+
+    /**
+     * Whether or not swiping down to leave overview state should return to the currently running
+     * task app.
+     *
+     * @return true if going back should take the user to the currently running task
+     */
+    boolean shouldSwipeDownReturnToApp() {
+        TaskView taskView = mRecentsView.getTaskViewAt(mRecentsView.getNextPage());
+        return taskView != null && mRecentsView.shouldSwipeDownLaunchApp();
+    }
+
+    /**
+     * Create the animation for going from overview to the task app via swiping. Should only be
+     * called when {@link #shouldSwipeDownReturnToApp()} returns true.
+     *
+     * @param duration how long the animation should be
+     * @return the animation
+     */
+    PendingAnimation createSwipeDownToTaskAppAnimation(long duration) {
+        TaskView taskView = mRecentsView.getTaskViewAt(mRecentsView.getNextPage());
+        if (taskView == null) {
+            throw new IllegalStateException("There is no task view to animate to.");
+        }
+        return mRecentsView.createTaskLauncherAnimation(taskView, duration);
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
new file mode 100644
index 0000000..f18f43c
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2019 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.uioverrides;
+
+import static android.view.View.VISIBLE;
+
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager.StateHandler;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.util.TouchController;
+import com.android.quickstep.views.RecentsView;
+
+/**
+ * Provides recents-related {@link UiFactory} logic and classes.
+ */
+public final class RecentsUiFactory {
+
+    // Scale recents takes before animating in
+    private static final float RECENTS_PREPARE_SCALE = 1.33f;
+
+    private RecentsUiFactory() {}
+
+    /**
+     * Creates and returns a touch controller for swiping recents tasks.
+     *
+     * @param launcher the launcher activity
+     * @return the touch controller for recents tasks
+     */
+    public static TouchController createTaskSwipeController(Launcher launcher) {
+        return new LauncherTaskViewController(launcher);
+    }
+
+    /**
+     * Creates and returns the controller responsible for recents view state transitions.
+     *
+     * @param launcher the launcher activity
+     * @return state handler for recents
+     */
+    public static StateHandler createRecentsViewStateController(Launcher launcher) {
+        return new RecentsViewStateController(launcher);
+    }
+
+    /**
+     * Prepare the recents view to animate in.
+     *
+     * @param launcher the launcher activity
+     */
+    public static void prepareToShowRecents(Launcher launcher) {
+        RecentsView overview = launcher.getOverviewPanel();
+        if (overview.getVisibility() != VISIBLE || overview.getContentAlpha() == 0) {
+            SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE);
+        }
+    }
+
+    /**
+     * Clean-up logic that occurs when recents is no longer in use/visible.
+     *
+     * @param launcher the launcher activity
+     */
+    public static void resetRecents(Launcher launcher) {
+        RecentsView recents = launcher.getOverviewPanel();
+        recents.reset();
+    }
+
+    /**
+     * Recents logic that triggers when launcher state changes or launcher activity stops/resumes.
+     *
+     * @param launcher the launcher activity
+     */
+    public static void onLauncherStateOrResumeChanged(Launcher launcher) {
+        LauncherState state = launcher.getStateManager().getState();
+        if (state == NORMAL) {
+            launcher.<RecentsView>getOverviewPanel().setSwipeDownShouldLaunchApp(false);
+        }
+    }
+
+    private static final class LauncherTaskViewController extends
+            TaskViewTouchController<Launcher> {
+
+        LauncherTaskViewController(Launcher activity) {
+            super(activity);
+        }
+
+        @Override
+        protected boolean isRecentsInteractive() {
+            return mActivity.isInState(OVERVIEW);
+        }
+
+        @Override
+        protected void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
+            mActivity.getStateManager().setCurrentUserControlledAnimation(animController);
+        }
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
new file mode 100644
index 0000000..7d7946d
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2017 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.uioverrides;
+
+import static com.android.launcher3.LauncherState.FAST_OVERVIEW;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.quickstep.QuickScrubController.QUICK_SCRUB_START_INTERPOLATOR;
+import static com.android.quickstep.QuickScrubController.QUICK_SCRUB_TRANSLATION_Y_FACTOR;
+import static com.android.quickstep.views.LauncherRecentsView.TRANSLATION_Y_FACTOR;
+import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
+
+import android.animation.ValueAnimator;
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.util.FloatProperty;
+import android.view.animation.Interpolator;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager.AnimationConfig;
+import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.anim.Interpolators;
+import com.android.quickstep.views.LauncherRecentsView;
+import com.android.quickstep.views.RecentsView;
+
+/**
+ * State handler for handling UI changes for {@link LauncherRecentsView}. In addition to managing
+ * the basic view properties, this class also manages changes in the task visuals.
+ */
+@TargetApi(Build.VERSION_CODES.O)
+public final class RecentsViewStateController extends
+        BaseRecentsViewStateController<LauncherRecentsView> {
+
+    public RecentsViewStateController(Launcher launcher) {
+        super(launcher);
+    }
+
+    @Override
+    public void setState(@NonNull LauncherState state) {
+        super.setState(state);
+        if (state.overviewUi) {
+            mRecentsView.updateEmptyMessage();
+            mRecentsView.resetTaskVisuals();
+        }
+    }
+
+    @Override
+    void setStateWithAnimationInternal(@NonNull final LauncherState toState,
+            @NonNull AnimatorSetBuilder builder, @NonNull AnimationConfig config) {
+        super.setStateWithAnimationInternal(toState, builder, config);
+
+        if (!toState.overviewUi) {
+            builder.addOnFinishRunnable(mRecentsView::resetTaskVisuals);
+        }
+
+        if (toState.overviewUi) {
+            ValueAnimator updateAnim = ValueAnimator.ofFloat(0, 1);
+            updateAnim.addUpdateListener(valueAnimator -> {
+                // While animating into recents, update the visible task data as needed
+                mRecentsView.loadVisibleTaskData();
+            });
+            updateAnim.setDuration(config.duration);
+            builder.play(updateAnim);
+            mRecentsView.updateEmptyMessage();
+        }
+    }
+
+    @Override
+    Interpolator getScaleAndTransYInterpolator(@NonNull LauncherState toState,
+            @NonNull AnimatorSetBuilder builder) {
+        if (mLauncher.getStateManager().getState() == OVERVIEW && toState == FAST_OVERVIEW) {
+            return Interpolators.clampToProgress(QUICK_SCRUB_START_INTERPOLATOR, 0,
+                    QUICK_SCRUB_TRANSLATION_Y_FACTOR);
+        }
+        return super.getScaleAndTransYInterpolator(toState, builder);
+    }
+
+    @Override
+    FloatProperty<LauncherRecentsView> getTranslationYFactorProperty() {
+        return TRANSLATION_Y_FACTOR;
+    }
+
+    @Override
+    FloatProperty<RecentsView> getContentAlphaProperty() {
+        return CONTENT_ALPHA;
+    }
+}
diff --git a/quickstep/res/drawable/bg_wellbeing_toast.xml b/quickstep/res/drawable/bg_wellbeing_toast.xml
new file mode 100644
index 0000000..22d6f8a
--- /dev/null
+++ b/quickstep/res/drawable/bg_wellbeing_toast.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+    <solid android:color="#E61A73E8" />
+    <corners android:radius="@dimen/task_corner_radius" />
+</shape>
\ No newline at end of file
diff --git a/quickstep/res/drawable/hourglass_bottom.xml b/quickstep/res/drawable/hourglass_bottom.xml
new file mode 100644
index 0000000..b5ef008
--- /dev/null
+++ b/quickstep/res/drawable/hourglass_bottom.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <group>
+        <clip-path android:pathData="M0,0H24V24H0Z M 0,0"/>
+        <path
+            android:fillColor="#FFFFFFFF"
+            android:pathData="M6,2V8H6l4,4L6,16H6v6H18V16h0l-4,-4,4,-4h0V2Zm6,9.5,-4,-4V4h8V7.5Z"/>
+    </group>
+</vector>
diff --git a/quickstep/res/drawable/hourglass_top.xml b/quickstep/res/drawable/hourglass_top.xml
new file mode 100644
index 0000000..7fc77d3
--- /dev/null
+++ b/quickstep/res/drawable/hourglass_top.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <group>
+        <clip-path android:pathData="M0,0H24V24H0Z M 0,0"/>
+        <path
+            android:fillColor="#FFFFFFFF"
+            android:pathData="M6,2V8H6l4,4L6,16H6v6H18V16h0l-4,-4,4,-4h0V2ZM16,16.5V20H8V16.5l4,-4Z"/>
+    </group>
+</vector>
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index 43ae9b8..f96a66f 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -38,18 +38,26 @@
     <com.android.quickstep.views.DigitalWellBeingToast
         android:id="@+id/digital_well_being_toast"
         android:layout_width="match_parent"
-        android:layout_height="100dp"
+        android:layout_height="48dp"
         android:importantForAccessibility="noHideDescendants"
-        android:background="#800000FF"
+        android:background="@drawable/bg_wellbeing_toast"
         android:layout_gravity="bottom"
-        android:visibility="gone"
-    >
+        android:gravity="center"
+        android:visibility="gone">
+        <ImageView
+            android:id="@+id/digital_well_being_hourglass"
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:layout_marginEnd="8dp"
+        />
         <TextView
-            android:id="@+id/remaining_time"
+            android:id="@+id/digital_well_being_remaining_time"
             android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center_vertical"
+            android:layout_height="24dp"
+            android:fontFamily="sans-serif"
+            android:textSize="14sp"
             android:textColor="@android:color/white"
+            android:gravity="center_vertical"
         />
     </com.android.quickstep.views.DigitalWellBeingToast>
 </com.android.quickstep.views.TaskView>
\ No newline at end of file
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 08132aa..0c741a1 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -16,7 +16,7 @@
 * limitations under the License.
 */
 -->
-<resources>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
 
     <!-- Application name -->
     <string name="derived_app_name" translatable="false">Quickstep</string>
@@ -46,4 +46,20 @@
 
     <!-- Accessibility title for the list of recent apps [CHAR_LIMIT=none] -->
     <string name="accessibility_recent_apps">Recent apps</string>
+
+    <!-- Accessibility title for an app card in Recents for apps that have time limit set
+     [CHAR_LIMIT=none] -->
+    <string name="task_contents_description_with_remaining_time"><xliff:g id="task_description" example="GMail">%1$s</xliff:g>, <xliff:g id="remaining_time" example="7 minutes left today">%2$s</xliff:g></string>
+
+    <!-- Text to show total app usage per day if it is less than 1 minute ("&lt;" is the
+     escaped form of '<'). [CHAR LIMIT=10] -->
+    <string name="shorter_duration_less_than_one_minute">&lt; 1 minute</string>
+
+    <!-- Annotation shown on an app card in Recents, telling that the app was switched to a
+    grayscale because it ran over its time limit [CHAR LIMIT=25] -->
+    <string name="app_in_grayscale">App in grayscale</string>
+
+    <!-- Annotation shown on an app card in Recents, telling that the app has a usage limit set by
+    the user, and a given time is left for it today [CHAR LIMIT=20] -->
+    <string name="time_left_for_app"><xliff:g id="time" example="7 minutes">%1$s</xliff:g> left today</string>
 </resources>
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/LauncherInitListener.java b/quickstep/src/com/android/launcher3/LauncherInitListener.java
index 08b6bfc..c5c5323 100644
--- a/quickstep/src/com/android/launcher3/LauncherInitListener.java
+++ b/quickstep/src/com/android/launcher3/LauncherInitListener.java
@@ -44,8 +44,8 @@
     @Override
     protected boolean init(Launcher launcher, boolean alreadyOnHome) {
         if (mRemoteAnimationProvider != null) {
-            LauncherAppTransitionManagerImpl appTransitionManager =
-                    (LauncherAppTransitionManagerImpl) launcher.getAppTransitionManager();
+            QuickstepAppTransitionManagerImpl appTransitionManager =
+                    (QuickstepAppTransitionManagerImpl) launcher.getAppTransitionManager();
 
             // Set a one-time animation provider. After the first call, this will get cleared.
             // TODO: Probably also check the intended target id.
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
similarity index 79%
rename from quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
rename to quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index 11b5fea..07a5b72 100644
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -22,7 +22,6 @@
 import static com.android.launcher3.BaseActivity.PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
@@ -30,8 +29,6 @@
 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_TRANSITIONS;
-import static com.android.quickstep.TaskUtils.findTaskViewToLaunch;
-import static com.android.quickstep.TaskUtils.getRecentsWindowAnimator;
 import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
@@ -57,10 +54,12 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.InsettableFrameLayout.LayoutParams;
 import com.android.launcher3.allapps.AllAppsTransitionController;
-import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.graphics.DrawableFactory;
@@ -68,12 +67,9 @@
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.quickstep.RecentsModel;
-import com.android.quickstep.util.ClipAnimationHelper;
 import com.android.quickstep.util.MultiValueUpdateListener;
 import com.android.quickstep.util.RemoteAnimationProvider;
 import com.android.quickstep.util.RemoteAnimationTargetSet;
-import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.system.ActivityCompat;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
@@ -85,14 +81,15 @@
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
 /**
- * Manages the opening and closing app transitions from Launcher.
+ * {@link LauncherAppTransitionManager} with Quickstep-specific app transitions for launching from
+ * home and/or all-apps.
  */
 @TargetApi(Build.VERSION_CODES.O)
 @SuppressWarnings("unused")
-public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManager
+public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTransitionManager
         implements OnDeviceProfileChangeListener {
 
-    private static final String TAG = "LauncherTransition";
+    private static final String TAG = "QuickstepTransition";
 
     /** Duration of status bar animations. */
     public static final int STATUS_BAR_TRANSITION_DURATION = 120;
@@ -119,10 +116,14 @@
     private static final int LAUNCHER_RESUME_START_DELAY = 100;
     private static final int CLOSING_TRANSITION_DURATION_MS = 250;
 
+    protected static final int CONTENT_ALPHA_DURATION = 217;
+    protected static final int CONTENT_TRANSLATION_DURATION = 350;
+
     // Progress = 0: All apps is fully pulled up, Progress = 1: All apps is fully pulled down.
     public static final float ALL_APPS_PROGRESS_OFF_SCREEN = 1.3059858f;
 
-    private final Launcher mLauncher;
+    protected final Launcher mLauncher;
+
     private final DragLayer mDragLayer;
     private final AlphaProperty mDragLayerAlpha;
 
@@ -150,7 +151,7 @@
         }
     };
 
-    public LauncherAppTransitionManagerImpl(Context context) {
+    public QuickstepAppTransitionManagerImpl(Context context) {
         mLauncher = Launcher.getLauncher(context);
         mDragLayer = mLauncher.getDragLayer();
         mDragLayerAlpha = mDragLayer.getAlphaProperty(ALPHA_INDEX_TRANSITIONS);
@@ -179,12 +180,9 @@
     @Override
     public ActivityOptions getActivityLaunchOptions(Launcher launcher, View v) {
         if (hasControlRemoteAppTransitionPermission()) {
-            boolean fromRecents = mLauncher.getStateManager().getState().overviewUi
-                    && findTaskViewToLaunch(launcher, v, null) != null;
-            RecentsView recentsView = mLauncher.getOverviewPanel();
-            if (fromRecents && recentsView.getQuickScrubController().isQuickSwitch()) {
-                return ActivityOptions.makeCustomAnimation(mLauncher, R.anim.no_anim,
-                        R.anim.no_anim);
+            boolean fromRecents = isLaunchingFromRecents(v, null /* targets */);
+            if (fromRecents && isQuickSwitchInProgress()) {
+                return getQuickSwitchActivityOptions();
             }
 
             RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mHandler,
@@ -198,33 +196,10 @@
                     boolean launcherClosing =
                             launcherIsATargetWithMode(targetCompats, MODE_CLOSING);
 
-                    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);
-
-                        Rect windowTargetBounds = getWindowTargetBounds(targetCompats);
-                        boolean isAllOpeningTargetTrs = true;
-                        for (int i = 0; i < targetCompats.length; i++) {
-                            RemoteAnimationTargetCompat target = targetCompats[i];
-                            if (target.mode == MODE_OPENING) {
-                                isAllOpeningTargetTrs &= target.isTranslucent;
-                            }
-                            if (!isAllOpeningTargetTrs) break;
-                        }
-                        playIconAnimators(anim, v, windowTargetBounds, !isAllOpeningTargetTrs);
-                        if (launcherClosing) {
-                            Pair<AnimatorSet, Runnable> launcherContentAnimator =
-                                    getLauncherContentAnimator(true /* isAppOpening */);
-                            anim.play(launcherContentAnimator.first);
-                            anim.addListener(new AnimatorListenerAdapter() {
-                                @Override
-                                public void onAnimationEnd(Animator animation) {
-                                    launcherContentAnimator.second.run();
-                                }
-                            });
-                        }
-                        anim.play(getOpeningWindowAnimators(v, targetCompats, windowTargetBounds));
+                    if (isLaunchingFromRecents(v, targetCompats)) {
+                        composeRecentsLaunchAnimator(anim, v, targetCompats, launcherClosing);
+                    } else {
+                        composeIconLaunchAnimator(anim, v, targetCompats, launcherClosing);
                     }
 
                     if (launcherClosing) {
@@ -235,6 +210,8 @@
                 }
             };
 
+            // Note that this duration is a guess as we do not know if the animation will be a
+            // recents launch or not for sure until we know the opening app targets.
             int duration = fromRecents
                     ? RECENTS_LAUNCH_DURATION
                     : APP_LAUNCH_DURATION;
@@ -248,6 +225,83 @@
     }
 
     /**
+     * Whether the launch is a recents app transition and we should do a launch animation
+     * from the recents view. Note that if the remote animation targets are not provided, this
+     * may not always be correct as we may resolve the opening app to a task when the animation
+     * starts.
+     *
+     * @param v the view to launch from
+     * @param targets apps that are opening/closing
+     * @return true if the app is launching from recents, false if it most likely is not
+     */
+    protected abstract boolean isLaunchingFromRecents(@NonNull View v,
+            @Nullable RemoteAnimationTargetCompat[] targets);
+
+    /**
+     * Whether a quick scrub is in progress.
+     *
+     * @return true if in progress
+     */
+    protected abstract boolean isQuickSwitchInProgress();
+
+    /**
+     * Get activity options for a quick switch launch that include the launch animation.
+     *
+     * @return the activity options for a quick switch recents launch
+     */
+    protected abstract ActivityOptions getQuickSwitchActivityOptions();
+
+    /**
+     * Composes the animations for a launch from the recents list.
+     *
+     * @param anim the animator set to add to
+     * @param v the launching view
+     * @param targets the apps that are opening/closing
+     * @param launcherClosing true if the launcher app is closing
+     */
+    protected abstract void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
+            @NonNull RemoteAnimationTargetCompat[] targets, boolean launcherClosing);
+
+    /**
+     * Compose the animations for a launch from the app icon.
+     *
+     * @param anim the animation to add to
+     * @param v the launching view with the icon
+     * @param targets the list of opening/closing apps
+     * @param launcherClosing true if launcher is closing
+     */
+    private void composeIconLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
+            @NonNull RemoteAnimationTargetCompat[] targets, boolean launcherClosing) {
+        // Set the state animation first so that any state listeners are called
+        // before our internal listeners.
+        mLauncher.getStateManager().setCurrentAnimation(anim);
+
+        Rect windowTargetBounds = getWindowTargetBounds(targets);
+        boolean isAllOpeningTargetTrs = true;
+        for (int i = 0; i < targets.length; i++) {
+            RemoteAnimationTargetCompat target = targets[i];
+            if (target.mode == MODE_OPENING) {
+                isAllOpeningTargetTrs &= target.isTranslucent;
+            }
+            if (!isAllOpeningTargetTrs) break;
+        }
+        playIconAnimators(anim, v, windowTargetBounds, !isAllOpeningTargetTrs);
+        if (launcherClosing) {
+            Pair<AnimatorSet, Runnable> launcherContentAnimator =
+                    getLauncherContentAnimator(true /* isAppOpening */,
+                            new float[] {0, mContentTransY});
+            anim.play(launcherContentAnimator.first);
+            anim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    launcherContentAnimator.second.run();
+                }
+            });
+        }
+        anim.play(getOpeningWindowAnimators(v, targets, windowTargetBounds));
+    }
+
+    /**
      * Return the window bounds of the opening target.
      * In multiwindow mode, we need to get the final size of the opening app window target to help
      * figure out where the floating view should animate to.
@@ -277,90 +331,20 @@
     }
 
     /**
-     * Composes the animations for a launch from the recents list if possible.
-     */
-    private boolean composeRecentsLaunchAnimator(View v,
-            RemoteAnimationTargetCompat[] targets, AnimatorSet target) {
-        // Ensure recents is actually visible
-        if (!mLauncher.getStateManager().getState().overviewUi) {
-            return false;
-        }
-
-        RecentsView recentsView = mLauncher.getOverviewPanel();
-        boolean launcherClosing = launcherIsATargetWithMode(targets, MODE_CLOSING);
-        boolean skipLauncherChanges = !launcherClosing;
-        boolean isLaunchingFromQuickscrub =
-                recentsView.getQuickScrubController().isWaitingForTaskLaunch();
-
-        TaskView taskView = findTaskViewToLaunch(mLauncher, v, targets);
-        if (taskView == null) {
-            return false;
-        }
-
-        int duration = isLaunchingFromQuickscrub
-                ? RECENTS_QUICKSCRUB_LAUNCH_DURATION
-                : RECENTS_LAUNCH_DURATION;
-
-        ClipAnimationHelper helper = new ClipAnimationHelper(mLauncher);
-        target.play(getRecentsWindowAnimator(taskView, skipLauncherChanges, targets, helper)
-                .setDuration(duration));
-
-        Animator childStateAnimation = null;
-        // Found a visible recents task that matches the opening app, lets launch the app from there
-        Animator launcherAnim;
-        final AnimatorListenerAdapter windowAnimEndListener;
-        if (launcherClosing) {
-            launcherAnim = recentsView.createAdjacentPageAnimForTaskLaunch(taskView, helper);
-            launcherAnim.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
-            launcherAnim.setDuration(duration);
-
-            // Make sure recents gets fixed up by resetting task alphas and scales, etc.
-            windowAnimEndListener = new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    mLauncher.getStateManager().moveToRestState();
-                    mLauncher.getStateManager().reapplyState();
-                }
-            };
-        } else {
-            AnimatorPlaybackController controller =
-                    mLauncher.getStateManager().createAnimationToNewWorkspace(NORMAL, duration);
-            controller.dispatchOnStart();
-            childStateAnimation = controller.getTarget();
-            launcherAnim = controller.getAnimationPlayer().setDuration(duration);
-            windowAnimEndListener = new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    mLauncher.getStateManager().goToState(NORMAL, false);
-                }
-            };
-        }
-        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, childStateAnimation);
-        target.addListener(windowAnimEndListener);
-        return true;
-    }
-
-    /**
      * Content is everything on screen except the background and the floating view (if any).
      *
      * @param isAppOpening True when this is called when an app is opening.
      *                     False when this is called when an app is closing.
+     * @param trans Array that contains the start and end translation values for the content.
      */
-    private Pair<AnimatorSet, Runnable> getLauncherContentAnimator(boolean isAppOpening) {
+    private Pair<AnimatorSet, Runnable> getLauncherContentAnimator(boolean isAppOpening,
+            float[] trans) {
         AnimatorSet launcherAnimator = new AnimatorSet();
         Runnable endListener;
 
         float[] alphas = isAppOpening
                 ? new float[] {1, 0}
                 : new float[] {0, 1};
-        float[] trans = isAppOpening
-                ? new float[] {0, mContentTransY}
-                : new float[] {-mContentTransY, 0};
 
         if (mLauncher.isInState(ALL_APPS)) {
             // All Apps in portrait mode is full screen, so we only animate AllAppsContainerView.
@@ -371,7 +355,7 @@
             appsView.setTranslationY(trans[0]);
 
             ObjectAnimator alpha = ObjectAnimator.ofFloat(appsView, View.ALPHA, alphas);
-            alpha.setDuration(217);
+            alpha.setDuration(CONTENT_ALPHA_DURATION);
             alpha.setInterpolator(LINEAR);
             appsView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
             alpha.addListener(new AnimatorListenerAdapter() {
@@ -382,7 +366,7 @@
             });
             ObjectAnimator transY = ObjectAnimator.ofFloat(appsView, View.TRANSLATION_Y, trans);
             transY.setInterpolator(AGGRESSIVE_EASE);
-            transY.setDuration(350);
+            transY.setDuration(CONTENT_TRANSLATION_DURATION);
 
             launcherAnimator.play(alpha);
             launcherAnimator.play(transY);
@@ -396,32 +380,19 @@
             AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
             launcherAnimator.play(ObjectAnimator.ofFloat(allAppsController, ALL_APPS_PROGRESS,
                     allAppsController.getProgress(), ALL_APPS_PROGRESS_OFF_SCREEN));
-
-            RecentsView overview = mLauncher.getOverviewPanel();
-            ObjectAnimator alpha = ObjectAnimator.ofFloat(overview,
-                    RecentsView.CONTENT_ALPHA, alphas);
-            alpha.setDuration(217);
-            alpha.setInterpolator(LINEAR);
-            launcherAnimator.play(alpha);
-
-            ObjectAnimator transY = ObjectAnimator.ofFloat(overview, View.TRANSLATION_Y, trans);
-            transY.setInterpolator(AGGRESSIVE_EASE);
-            transY.setDuration(350);
-            launcherAnimator.play(transY);
-
-            endListener = mLauncher.getStateManager()::reapplyState;
+            endListener = composeViewContentAnimator(launcherAnimator, alphas, trans);
         } else {
             mDragLayerAlpha.setValue(alphas[0]);
             ObjectAnimator alpha =
                     ObjectAnimator.ofFloat(mDragLayerAlpha, MultiValueAlpha.VALUE, alphas);
-            alpha.setDuration(217);
+            alpha.setDuration(CONTENT_ALPHA_DURATION);
             alpha.setInterpolator(LINEAR);
             launcherAnimator.play(alpha);
 
             mDragLayer.setTranslationY(trans[0]);
             ObjectAnimator transY = ObjectAnimator.ofFloat(mDragLayer, View.TRANSLATION_Y, trans);
             transY.setInterpolator(AGGRESSIVE_EASE);
-            transY.setDuration(350);
+            transY.setDuration(CONTENT_TRANSLATION_DURATION);
             launcherAnimator.play(transY);
 
             mDragLayer.getScrim().hideSysUiScrim(true);
@@ -429,21 +400,38 @@
             mLauncher.getWorkspace().getPageIndicator().pauseAnimations();
             mDragLayer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
 
-            endListener = () -> {
-                resetContentView();
-                mDragLayer.getScrim().hideSysUiScrim(false);
-            };
+            endListener = this::resetContentView;
         }
         return new Pair<>(launcherAnimator, endListener);
     }
 
     /**
+     * Compose recents view alpha and translation Y animation when launcher opens/closes apps.
+     *
+     * @param anim the animator set to add to
+     * @param alphas the alphas to animate to over time
+     * @param trans the translation Y values to animator to over time
+     * @return listener to run when the animation ends
+     */
+    protected abstract Runnable composeViewContentAnimator(@NonNull AnimatorSet anim,
+            float[] alphas, float[] trans);
+
+    /**
      * Animators for the "floating view" of the view used to launch the target.
      */
     private void playIconAnimators(AnimatorSet appOpenAnimator, View v, Rect windowTargetBounds,
             boolean toggleVisibility) {
         final boolean isBubbleTextView = v instanceof BubbleTextView;
-        mFloatingView = new View(mLauncher);
+        if (mFloatingView == null) {
+            mFloatingView = new View(mLauncher);
+        } else {
+            mFloatingView.setTranslationX(0);
+            mFloatingView.setTranslationY(0);
+            mFloatingView.setScaleX(1);
+            mFloatingView.setScaleY(1);
+            mFloatingView.setAlpha(1);
+            mFloatingView.setBackground(null);
+        }
         if (isBubbleTextView && v.getTag() instanceof ItemInfoWithIcon ) {
             // Create a copy of the app icon
             mFloatingView.setBackground(DrawableFactory.INSTANCE.get(mLauncher)
@@ -481,19 +469,17 @@
                 : viewLocationLeft;
         LayoutParams lp = new LayoutParams(rect.width(), rect.height());
         lp.ignoreInsets = true;
-        lp.setMarginStart(viewLocationStart);
+        lp.leftMargin = viewLocationStart;
         lp.topMargin = viewLocationTop;
         mFloatingView.setLayoutParams(lp);
 
         // Set the properties here already to make sure they'are available when running the first
         // animation frame.
-        mFloatingView.setLeft(viewLocationLeft);
-        mFloatingView.setTop(viewLocationTop);
-        mFloatingView.setRight(viewLocationLeft + rect.width());
-        mFloatingView.setBottom(viewLocationTop + rect.height());
+        mFloatingView.layout(viewLocationLeft, viewLocationTop,
+                viewLocationLeft + rect.width(), viewLocationTop + rect.height());
 
         // Swap the two views in place.
-        ((ViewGroup) mDragLayer.getParent()).addView(mFloatingView);
+        ((ViewGroup) mDragLayer.getParent()).getOverlay().add(mFloatingView);
         if (toggleVisibility) {
             v.setVisibility(View.INVISIBLE);
         }
@@ -562,7 +548,7 @@
                     ((BubbleTextView) v).setStayPressed(false);
                 }
                 v.setVisibility(View.VISIBLE);
-                ((ViewGroup) mDragLayer.getParent()).removeView(mFloatingView);
+                ((ViewGroup) mDragLayer.getParent()).getOverlay().remove(mFloatingView);
             }
         });
     }
@@ -684,10 +670,13 @@
             RemoteAnimationDefinitionCompat definition = new RemoteAnimationDefinitionCompat();
             definition.addRemoteAnimation(WindowManagerWrapper.TRANSIT_WALLPAPER_OPEN,
                     WindowManagerWrapper.ACTIVITY_TYPE_STANDARD,
-                    new RemoteAnimationAdapterCompat(getWallpaperOpenRunner(),
+                    new RemoteAnimationAdapterCompat(getWallpaperOpenRunner(false /* fromUnlock */),
                             CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
 
-            // TODO: Transition for unlock to home TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER
+            definition.addRemoteAnimation(
+                    WindowManagerWrapper.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
+                    new RemoteAnimationAdapterCompat(getWallpaperOpenRunner(true /* fromUnlock */),
+                            CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
             new ActivityCompat(mLauncher).registerRemoteAnimations(definition);
         }
     }
@@ -700,7 +689,7 @@
      * @return Runner that plays when user goes to Launcher
      *         ie. pressing home, swiping up from nav bar.
      */
-    private RemoteAnimationRunnerCompat getWallpaperOpenRunner() {
+    private RemoteAnimationRunnerCompat getWallpaperOpenRunner(boolean fromUnlock) {
         return new LauncherAnimationRunner(mHandler, false /* startAtFrontOfQueue */) {
             @Override
             public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
@@ -726,7 +715,9 @@
 
                 if (anim == null) {
                     anim = new AnimatorSet();
-                    anim.play(getClosingWindowAnimators(targetCompats));
+                    anim.play(fromUnlock
+                            ? getUnlockWindowAnimator(targetCompats)
+                            : getClosingWindowAnimators(targetCompats));
 
                     // 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
@@ -740,7 +731,21 @@
                             || mLauncher.isForceInvisible()) {
                         // Only register the content animation for cancellation when state changes
                         mLauncher.getStateManager().setCurrentAnimation(anim);
-                        createLauncherResumeAnimation(anim);
+                        if (fromUnlock) {
+                            Pair<AnimatorSet, Runnable> contentAnimator =
+                                    getLauncherContentAnimator(false /* isAppOpening */,
+                                            new float[] {mContentTransY, 0});
+                            contentAnimator.first.setStartDelay(0);
+                            anim.play(contentAnimator.first);
+                            anim.addListener(new AnimatorListenerAdapter() {
+                                @Override
+                                public void onAnimationEnd(Animator animation) {
+                                    contentAnimator.second.run();
+                                }
+                            });
+                        } else {
+                            createLauncherResumeAnimation(anim);
+                        }
                     }
                 }
 
@@ -751,6 +756,31 @@
     }
 
     /**
+     * Animator that controls the transformations of the windows when unlocking the device.
+     */
+    private Animator getUnlockWindowAnimator(RemoteAnimationTargetCompat[] targets) {
+        SyncRtSurfaceTransactionApplierCompat surfaceApplier =
+                new SyncRtSurfaceTransactionApplierCompat(mDragLayer);
+        ValueAnimator unlockAnimator = ValueAnimator.ofFloat(0, 1);
+        unlockAnimator.setDuration(CLOSING_TRANSITION_DURATION_MS);
+        float cornerRadius = RecentsModel.INSTANCE.get(mLauncher).getWindowCornerRadius();
+        unlockAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                SurfaceParams[] params = new SurfaceParams[targets.length];
+                for (int i = targets.length - 1; i >= 0; i--) {
+                    RemoteAnimationTargetCompat target = targets[i];
+                    params[i] = new SurfaceParams(target.leash, 1f, null,
+                            target.sourceContainerBounds,
+                            RemoteAnimationProvider.getLayer(target, MODE_OPENING), cornerRadius);
+                }
+                surfaceApplier.scheduleApply(params);
+            }
+        });
+        return unlockAnimator;
+    }
+
+    /**
      * Animator that controls the transformations of the windows the targets that are closing.
      */
     private Animator getClosingWindowAnimators(RemoteAnimationTargetCompat[] targets) {
@@ -804,7 +834,8 @@
     private void createLauncherResumeAnimation(AnimatorSet anim) {
         if (mLauncher.isInState(LauncherState.ALL_APPS)) {
             Pair<AnimatorSet, Runnable> contentAnimator =
-                    getLauncherContentAnimator(false /* isAppOpening */);
+                    getLauncherContentAnimator(false /* isAppOpening */,
+                            new float[] {-mContentTransY, 0});
             contentAnimator.first.setStartDelay(LAUNCHER_RESUME_START_DELAY);
             anim.play(contentAnimator.first);
             anim.addListener(new AnimatorListenerAdapter() {
@@ -828,6 +859,8 @@
             workspaceAnimator.setDuration(333);
             workspaceAnimator.setInterpolator(Interpolators.DEACCEL_1_7);
 
+            mDragLayer.getScrim().hideSysUiScrim(true);
+
             // Pause page indicator animations as they lead to layer trashing.
             mLauncher.getWorkspace().getPageIndicator().pauseAnimations();
             mDragLayer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
@@ -847,6 +880,7 @@
         mDragLayerAlpha.setValue(1f);
         mDragLayer.setLayerType(View.LAYER_TYPE_NONE, null);
         mDragLayer.setTranslationY(0f);
+        mDragLayer.getScrim().hideSysUiScrim(false);
     }
 
     private boolean hasControlRemoteAppTransitionPermission() {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
index 1906286..25e0af2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
@@ -18,6 +18,7 @@
 import static com.android.launcher3.AbstractFloatingView.TYPE_QUICKSTEP_PREVIEW;
 import static com.android.launcher3.LauncherAnimUtils.ALL_APPS_TRANSITION_MS;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
@@ -45,7 +46,11 @@
 
     @Override
     public void onStateEnabled(Launcher launcher) {
-        AbstractFloatingView.closeAllOpenViewsExcept(launcher, TYPE_QUICKSTEP_PREVIEW);
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            AbstractFloatingView.closeAllOpenViews(launcher);
+        } else {
+            AbstractFloatingView.closeAllOpenViewsExcept(launcher, TYPE_QUICKSTEP_PREVIEW);
+        }
         dispatchWindowStateChanged(launcher);
     }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/BackgroundAppState.java
index fdb13b1..8d28f33 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BackgroundAppState.java
@@ -41,7 +41,8 @@
         if (launcher.getDeviceProfile().isVerticalBarLayout()) {
             return super.getVerticalProgress(launcher);
         }
-        int transitionLength = LayoutUtils.getShelfTrackingDistance(launcher.getDeviceProfile());
+        int transitionLength = LayoutUtils.getShelfTrackingDistance(launcher,
+                launcher.getDeviceProfile());
         AllAppsTransitionController controller = launcher.getAllAppsController();
         float scrollRange = Math.max(controller.getShiftRange(), 1);
         float progressDelta = (transitionLength / scrollRange);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
new file mode 100644
index 0000000..df9dbe4
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2019 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.uioverrides;
+
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
+import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+
+import android.util.FloatProperty;
+import android.view.View;
+import android.view.animation.Interpolator;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager.AnimationConfig;
+import com.android.launcher3.LauncherStateManager.StateHandler;
+import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.anim.PropertySetter;
+
+/**
+ * State handler for recents view. Manages UI changes and animations for recents view based off the
+ * current {@link LauncherState}.
+ *
+ * @param <T> the recents view
+ */
+public abstract class BaseRecentsViewStateController<T extends View>
+        implements StateHandler {
+    protected final T mRecentsView;
+    protected final Launcher mLauncher;
+
+    public BaseRecentsViewStateController(@NonNull Launcher launcher) {
+        mLauncher = launcher;
+        mRecentsView = launcher.getOverviewPanel();
+    }
+
+    @Override
+    public void setState(@NonNull LauncherState state) {
+        float[] scaleTranslationYFactor = state.getOverviewScaleAndTranslationYFactor(mLauncher);
+        SCALE_PROPERTY.set(mRecentsView, scaleTranslationYFactor[0]);
+        getTranslationYFactorProperty().set(mRecentsView, scaleTranslationYFactor[1]);
+        getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0);
+    }
+
+    @Override
+    public final void setStateWithAnimation(@NonNull final LauncherState toState,
+            @NonNull AnimatorSetBuilder builder, @NonNull AnimationConfig config) {
+        if (!config.playAtomicComponent()) {
+            // The entire recents animation is played atomically.
+            return;
+        }
+        setStateWithAnimationInternal(toState, builder, config);
+    }
+
+    /**
+     * Core logic for animating the recents view UI.
+     *
+     * @param toState state to animate to
+     * @param builder animator set builder
+     * @param config current animation config
+     */
+    void setStateWithAnimationInternal(@NonNull final LauncherState toState,
+            @NonNull AnimatorSetBuilder builder, @NonNull AnimationConfig config) {
+        PropertySetter setter = config.getPropertySetter(builder);
+        float[] scaleTranslationYFactor = toState.getOverviewScaleAndTranslationYFactor(mLauncher);
+        Interpolator scaleAndTransYInterpolator = getScaleAndTransYInterpolator(toState, builder);
+        setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleTranslationYFactor[0],
+                scaleAndTransYInterpolator);
+        setter.setFloat(mRecentsView, getTranslationYFactorProperty(), scaleTranslationYFactor[1],
+                scaleAndTransYInterpolator);
+        setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
+                builder.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
+    }
+
+    /**
+     * Get the interpolator to use for the scale and translation Y animation for the view.
+     *
+     * @param toState state to animate to
+     * @param builder animator set builder
+     * @return interpolator for scale and trans Y recents view animation
+     */
+    Interpolator getScaleAndTransYInterpolator(@NonNull final LauncherState toState,
+            @NonNull AnimatorSetBuilder builder) {
+        return builder.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR);
+    }
+
+    /**
+     * Get property for translation Y factor for the recents view.
+     *
+     * @return the float property for the recents view
+     */
+    abstract FloatProperty getTranslationYFactorProperty();
+
+    /**
+     * Get property for content alpha for the recents view.
+     *
+     * @return the float property for the view's content alpha
+     */
+    abstract FloatProperty getContentAlphaProperty();
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
index 8684c58..ea0e552 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
@@ -47,8 +47,6 @@
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.TouchInteractionService;
 import com.android.quickstep.util.LayoutUtils;
-import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskView;
 
 /**
  * Touch controller for handling various state transitions in portrait UI.
@@ -67,14 +65,16 @@
      */
     private static final float RECENTS_FADE_THRESHOLD = 0.88f;
 
+    private final PortraitOverviewStateTouchHelper mOverviewPortraitStateTouchHelper;
+
     private InterpolatorWrapper mAllAppsInterpolatorWrapper = new InterpolatorWrapper();
 
     // If true, we will finish the current animation instantly on second touch.
     private boolean mFinishFastOnSecondTouch;
 
-
     public PortraitStatesTouchController(Launcher l) {
         super(l, SwipeDetector.VERTICAL);
+        mOverviewPortraitStateTouchHelper = new PortraitOverviewStateTouchHelper(l);
     }
 
     @Override
@@ -98,22 +98,18 @@
             }
             return false;
         }
-        RecentsView recentsView = mLauncher.getOverviewPanel();
         if (mLauncher.isInState(ALL_APPS)) {
             // In all-apps only listen if the container cannot scroll itself
             if (!mLauncher.getAppsView().shouldContainerScroll(ev)) {
                 return false;
             }
-        } else if (mLauncher.isInState(OVERVIEW) && recentsView.getChildCount() > 0) {
-            // Allow swiping up in the gap between the hotseat and overview.
-            if (ev.getY() < recentsView.getChildAt(0).getBottom()) {
+        } else if (mLauncher.isInState(OVERVIEW)) {
+            if (!mOverviewPortraitStateTouchHelper.canInterceptTouch(ev)) {
                 return false;
             }
         } else {
             // For all other states, only listen if the event originated below the hotseat height
-            DeviceProfile dp = mLauncher.getDeviceProfile();
-            int hotseatHeight = dp.hotseatBarSizePx + dp.getInsets().bottom;
-            if (ev.getY() < (mLauncher.getDragLayer().getHeight() - hotseatHeight)) {
+            if (!isTouchOverHotseat(mLauncher, ev)) {
                 return false;
             }
         }
@@ -197,13 +193,12 @@
 
         cancelPendingAnim();
 
-        RecentsView recentsView = mLauncher.getOverviewPanel();
-        TaskView taskView = recentsView.getTaskViewAt(recentsView.getNextPage());
-        if (recentsView.shouldSwipeDownLaunchApp() && mFromState == OVERVIEW && mToState == NORMAL
-                && taskView != null) {
+        if (mFromState == OVERVIEW && mToState == NORMAL
+                && mOverviewPortraitStateTouchHelper.shouldSwipeDownReturnToApp()) {
             // Reset the state manager, when changing the interaction mode
             mLauncher.getStateManager().goToState(OVERVIEW, false /* animate */);
-            mPendingAnimation = recentsView.createTaskLauncherAnimation(taskView, maxAccuracy);
+            mPendingAnimation = mOverviewPortraitStateTouchHelper
+                    .createSwipeDownToTaskAppAnimation(maxAccuracy);
             mPendingAnimation.anim.setInterpolator(Interpolators.LINEAR);
 
             Runnable onCancelRunnable = () -> {
@@ -213,7 +208,8 @@
             mCurrentAnimation = AnimatorPlaybackController.wrap(mPendingAnimation.anim, maxAccuracy,
                     onCancelRunnable);
             mLauncher.getStateManager().setCurrentUserControlledAnimation(mCurrentAnimation);
-            totalShift = LayoutUtils.getShelfTrackingDistance(mLauncher.getDeviceProfile());
+            totalShift = LayoutUtils.getShelfTrackingDistance(mLauncher,
+                    mLauncher.getDeviceProfile());
         } else {
             mCurrentAnimation = mLauncher.getStateManager()
                     .createAnimationToNewWorkspace(mToState, builder, maxAccuracy, this::clearState,
@@ -268,6 +264,19 @@
         }
     }
 
+    /**
+     * Whether the motion event is over the hotseat.
+     *
+     * @param launcher the launcher activity
+     * @param ev the event to check
+     * @return true if the event is over the hotseat
+     */
+    static boolean isTouchOverHotseat(Launcher launcher, MotionEvent ev) {
+        DeviceProfile dp = launcher.getDeviceProfile();
+        int hotseatHeight = dp.hotseatBarSizePx + dp.getInsets().bottom;
+        return (ev.getY() >= (launcher.getDragLayer().getHeight() - hotseatHeight));
+    }
+
     private static class InterpolatorWrapper implements Interpolator {
 
         public TimeInterpolator baseInterpolator = LINEAR;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
deleted file mode 100644
index abd2846..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2017 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.uioverrides;
-
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.LauncherState.FAST_OVERVIEW;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
-import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.quickstep.QuickScrubController.QUICK_SCRUB_START_INTERPOLATOR;
-import static com.android.quickstep.QuickScrubController.QUICK_SCRUB_TRANSLATION_Y_FACTOR;
-import static com.android.quickstep.views.LauncherRecentsView.TRANSLATION_Y_FACTOR;
-import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
-
-import android.animation.ValueAnimator;
-import android.annotation.TargetApi;
-import android.os.Build;
-import android.view.animation.Interpolator;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.AnimationConfig;
-import com.android.launcher3.LauncherStateManager.StateHandler;
-import com.android.launcher3.anim.AnimatorSetBuilder;
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.anim.PropertySetter;
-import com.android.quickstep.views.LauncherRecentsView;
-
-@TargetApi(Build.VERSION_CODES.O)
-public class RecentsViewStateController implements StateHandler {
-
-    private final Launcher mLauncher;
-    private final LauncherRecentsView mRecentsView;
-
-    public RecentsViewStateController(Launcher launcher) {
-        mLauncher = launcher;
-        mRecentsView = launcher.getOverviewPanel();
-    }
-
-    @Override
-    public void setState(LauncherState state) {
-        mRecentsView.setContentAlpha(state.overviewUi ? 1 : 0);
-        float[] scaleTranslationYFactor = state.getOverviewScaleAndTranslationYFactor(mLauncher);
-        SCALE_PROPERTY.set(mRecentsView, scaleTranslationYFactor[0]);
-        mRecentsView.setTranslationYFactor(scaleTranslationYFactor[1]);
-        if (state.overviewUi) {
-            mRecentsView.updateEmptyMessage();
-            mRecentsView.resetTaskVisuals();
-        }
-    }
-
-    @Override
-    public void setStateWithAnimation(final LauncherState toState,
-            AnimatorSetBuilder builder, AnimationConfig config) {
-        if (!config.playAtomicComponent()) {
-            // The entire recents animation is played atomically.
-            return;
-        }
-        PropertySetter setter = config.getPropertySetter(builder);
-        float[] scaleTranslationYFactor = toState.getOverviewScaleAndTranslationYFactor(mLauncher);
-        Interpolator scaleAndTransYInterpolator = builder.getInterpolator(
-                ANIM_OVERVIEW_SCALE, LINEAR);
-        if (mLauncher.getStateManager().getState() == OVERVIEW && toState == FAST_OVERVIEW) {
-            scaleAndTransYInterpolator = Interpolators.clampToProgress(
-                    QUICK_SCRUB_START_INTERPOLATOR, 0, QUICK_SCRUB_TRANSLATION_Y_FACTOR);
-        }
-        setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleTranslationYFactor[0],
-                scaleAndTransYInterpolator);
-        setter.setFloat(mRecentsView, TRANSLATION_Y_FACTOR, scaleTranslationYFactor[1],
-                scaleAndTransYInterpolator);
-        setter.setFloat(mRecentsView, CONTENT_ALPHA, toState.overviewUi ? 1 : 0,
-                builder.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
-
-        if (!toState.overviewUi) {
-            builder.addOnFinishRunnable(mRecentsView::resetTaskVisuals);
-        }
-
-        if (toState.overviewUi) {
-            ValueAnimator updateAnim = ValueAnimator.ofFloat(0, 1);
-            updateAnim.addUpdateListener(valueAnimator -> {
-                // While animating into recents, update the visible task data as needed
-                mRecentsView.loadVisibleTaskData();
-            });
-            updateAnim.setDuration(config.duration);
-            builder.play(updateAnim);
-            mRecentsView.updateEmptyMessage();
-        }
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
index 753f73a..50af4a1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
@@ -18,6 +18,7 @@
 import static com.android.launcher3.AbstractFloatingView.TYPE_ACCESSIBLE;
 import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -49,7 +50,7 @@
     private static final String TAG = "OverviewSwipeController";
 
     // Progress after which the transition is assumed to be a success in case user does not fling
-    private static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
+    public static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
 
     protected final T mActivity;
     private final SwipeDetector mDetector;
@@ -231,6 +232,12 @@
             mFlingBlockCheck.onEvent();
         }
         mCurrentAnimation.setPlayFraction(totalDisplacement * mProgressMultiplier);
+
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            if (mRecentsView.getCurrentPage() != 0 || isGoingUp) {
+                mRecentsView.redrawLiveTile(true);
+            }
+        }
         return true;
     }
 
@@ -267,6 +274,13 @@
         anim.setFloatValues(nextFrameProgress, goingToEnd ? 1f : 0f);
         anim.setDuration(animationDuration);
         anim.setInterpolator(scrollInterpolatorForVelocity(velocity));
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            anim.addUpdateListener(valueAnimator -> {
+                if (mRecentsView.getCurrentPage() != 0 || mCurrentAnimationIsGoingUp) {
+                    mRecentsView.redrawLiveTile(true);
+                }
+            });
+        }
         anim.start();
     }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index 4e79fed..ff9d601 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -16,10 +16,8 @@
 
 package com.android.launcher3.uioverrides;
 
-import static android.view.View.VISIBLE;
 import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
 import static com.android.launcher3.AbstractFloatingView.TYPE_HIDE_BACK_BUTTON;
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
@@ -36,18 +34,18 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppTransitionManagerImpl;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager;
 import com.android.launcher3.LauncherStateManager.StateHandler;
+import com.android.launcher3.QuickstepAppTransitionManagerImpl;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.TouchController;
+import com.android.launcher3.util.UiThreadHelper;
+import com.android.launcher3.util.UiThreadHelper.AsyncCommand;
 import com.android.quickstep.OverviewInteractionState;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.util.RemoteFadeOutAnimationListener;
-import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.ActivityCompat;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
@@ -58,6 +56,9 @@
 
 public class UiFactory {
 
+    private static final AsyncCommand SET_SHELF_HEIGHT_CMD = (visible, height) ->
+            WindowManagerWrapper.getInstance().setShelfHeight(visible != 0, height);
+
     public static TouchController[] createTouchControllers(Launcher launcher) {
         boolean swipeUpEnabled = OverviewInteractionState.INSTANCE.get(launcher)
                 .isSwipeUpGestureEnabled();
@@ -78,7 +79,11 @@
                 && !launcher.getDeviceProfile().isVerticalBarLayout()) {
             list.add(new StatusBarTouchController(launcher));
         }
-        list.add(new LauncherTaskViewController(launcher));
+        TouchController taskSwipeController =
+                RecentsUiFactory.createTaskSwipeController(launcher);
+        if (taskSwipeController != null) {
+            list.add(taskSwipeController);
+        }
         return list.toArray(new TouchController[list.size()]);
     }
 
@@ -88,7 +93,8 @@
 
     public static StateHandler[] getStateHandler(Launcher launcher) {
         return new StateHandler[] {launcher.getAllAppsController(), launcher.getWorkspace(),
-                new RecentsViewStateController(launcher), new BackButtonAlphaHandler(launcher)};
+                RecentsUiFactory.createRecentsViewStateController(launcher),
+                new BackButtonAlphaHandler(launcher)};
     }
 
     /**
@@ -108,8 +114,7 @@
     }
 
     public static void resetOverview(Launcher launcher) {
-        RecentsView recents = launcher.getOverviewPanel();
-        recents.reset();
+        RecentsUiFactory.resetRecents(launcher);
     }
 
     public static void onCreate(Launcher launcher) {
@@ -175,15 +180,13 @@
         LauncherState state = launcher.getStateManager().getState();
         if (!OverviewInteractionState.INSTANCE.get(launcher).swipeGestureInitializing()) {
             DeviceProfile profile = launcher.getDeviceProfile();
-            WindowManagerWrapper.getInstance().setShelfHeight(
-                    (state == NORMAL || state == OVERVIEW) && launcher.isUserActive()
-                            && !profile.isVerticalBarLayout(),
-                    profile.hotseatBarSizePx);
+            boolean visible = (state == NORMAL || state == OVERVIEW) && launcher.isUserActive()
+                    && !profile.isVerticalBarLayout();
+            UiThreadHelper.runAsyncCommand(launcher, SET_SHELF_HEIGHT_CMD,
+                    visible ? 1 : 0, profile.hotseatBarSizePx);
         }
 
-        if (state == NORMAL) {
-            launcher.<RecentsView>getOverviewPanel().setSwipeDownShouldLaunchApp(false);
-        }
+        RecentsUiFactory.onLauncherStateOrResumeChanged(launcher);
     }
 
     public static void onTrimMemory(Context context, int level) {
@@ -195,8 +198,8 @@
 
     public static void useFadeOutAnimationForLauncherStart(Launcher launcher,
             CancellationSignal cancellationSignal) {
-        LauncherAppTransitionManagerImpl appTransitionManager =
-                (LauncherAppTransitionManagerImpl) launcher.getAppTransitionManager();
+        QuickstepAppTransitionManagerImpl appTransitionManager =
+                (QuickstepAppTransitionManagerImpl) launcher.getAppTransitionManager();
         appTransitionManager.setRemoteAnimationProvider((targets) -> {
 
             // On the first call clear the reference.
@@ -237,26 +240,6 @@
     }
 
     public static void prepareToShowOverview(Launcher launcher) {
-        RecentsView overview = launcher.getOverviewPanel();
-        if (overview.getVisibility() != VISIBLE || overview.getContentAlpha() == 0) {
-            SCALE_PROPERTY.set(overview, 1.33f);
-        }
-    }
-
-    private static class LauncherTaskViewController extends TaskViewTouchController<Launcher> {
-
-        public LauncherTaskViewController(Launcher activity) {
-            super(activity);
-        }
-
-        @Override
-        protected boolean isRecentsInteractive() {
-            return mActivity.isInState(OVERVIEW);
-        }
-
-        @Override
-        protected void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
-            mActivity.getStateManager().setCurrentUserControlledAnimation(animController);
-        }
+        RecentsUiFactory.prepareToShowRecents(launcher);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index 4646fd7f..c3df9c7 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -21,7 +21,7 @@
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.FAST_OVERVIEW;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
+import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS_SPRING;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
 import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
@@ -44,6 +44,8 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.view.View;
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
@@ -53,9 +55,9 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.TestProtocol;
-import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.SpringObjectAnimator;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragLayer;
@@ -77,9 +79,6 @@
 import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 
-import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
-
 /**
  * Utility class which abstracts out the logical differences between Launcher and RecentsActivity.
  */
@@ -150,11 +149,13 @@
      */
     int getContainerType();
 
+    boolean isInLiveTileMode();
+
     class LauncherActivityControllerHelper implements ActivityControlHelper<Launcher> {
 
         @Override
         public LayoutListener createLayoutListener(Launcher activity) {
-            return new LauncherLayoutListener(activity);
+            return LauncherLayoutListener.resetAndGet(activity);
         }
 
         @Override
@@ -216,7 +217,7 @@
                 int hotseatInset = dp.isSeascape() ? targetInsets.left : targetInsets.right;
                 return dp.hotseatBarSizePx + hotseatInset;
             } else {
-                return LayoutUtils.getShelfTrackingDistance(dp);
+                return LayoutUtils.getShelfTrackingDistance(context, dp);
             }
         }
 
@@ -295,8 +296,9 @@
 
             AnimatorSet anim = new AnimatorSet();
             if (!activity.getDeviceProfile().isVerticalBarLayout()) {
-                AllAppsTransitionController controller = activity.getAllAppsController();
-                ObjectAnimator shiftAnim = ObjectAnimator.ofFloat(controller, ALL_APPS_PROGRESS,
+                Animator shiftAnim = new SpringObjectAnimator<>(activity.getAllAppsController(),
+                        ALL_APPS_PROGRESS_SPRING, "allAppsSpringFromACH",
+                        activity.getAllAppsController().getShiftRange(),
                         fromState.getVerticalProgress(activity),
                         endState.getVerticalProgress(activity));
                 shiftAnim.setInterpolator(LINEAR);
@@ -442,6 +444,13 @@
             return launcher != null ? launcher.getStateManager().getState().containerType
                     : LauncherLogProto.ContainerType.APP;
         }
+
+        @Override
+        public boolean isInLiveTileMode() {
+            Launcher launcher = getCreatedActivity();
+            return launcher != null && launcher.getStateManager().getState() == OVERVIEW &&
+                    launcher.isStarted();
+        }
     }
 
     class FallbackActivityControllerHelper implements ActivityControlHelper<RecentsActivity> {
@@ -627,6 +636,11 @@
         public int getContainerType() {
             return LauncherLogProto.ContainerType.SIDELOADED_LAUNCHER;
         }
+
+        @Override
+        public boolean isInLiveTileMode() {
+            return false;
+        }
     }
 
     interface LayoutListener {
diff --git a/quickstep/src/com/android/quickstep/LongSwipeHelper.java b/quickstep/src/com/android/quickstep/LongSwipeHelper.java
index 16214dd..80d37ae 100644
--- a/quickstep/src/com/android/quickstep/LongSwipeHelper.java
+++ b/quickstep/src/com/android/quickstep/LongSwipeHelper.java
@@ -19,6 +19,7 @@
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
 import static com.android.quickstep.WindowTransformSwipeHandler.MAX_SWIPE_DURATION;
 import static com.android.quickstep.WindowTransformSwipeHandler.MIN_OVERSHOOT_DURATION;
 
@@ -41,7 +42,6 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.FlingBlockCheck;
 import com.android.quickstep.views.RecentsView;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
 /**
  * Utility class to handle long swipe from an app.
@@ -113,7 +113,7 @@
                     * MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER));
             duration = Math.min(MAX_SWIPE_DURATION, expectedDuration);
 
-            if (blockedFling && !toAllApps) {
+            if (blockedFling && !toAllApps && !QUICKSTEP_SPRINGS.get()) {
                 Interpolators.OvershootParams overshoot = new OvershootParams(currentFraction,
                         currentFraction, endProgress, velocityPxPerMs, (int) mMaxSwipeDistance);
                 duration = (overshoot.duration + duration);
@@ -145,7 +145,12 @@
         ValueAnimator animator = mAnimator.getAnimationPlayer();
         animator.setDuration(duration).setInterpolator(interpolator);
         animator.setFloatValues(currentFraction, endProgress);
-        animator.start();
+
+        if (QUICKSTEP_SPRINGS.get()) {
+            mAnimator.dispatchOnStartWithVelocity(endProgress, velocityPxPerMs);
+        } else {
+            animator.start();
+        }
     }
 
     private void onSwipeAnimationComplete(boolean toAllApps, boolean isFling, Runnable callback) {
diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
index 95be188..cd71f3d 100644
--- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
@@ -21,11 +21,9 @@
 import static android.view.MotionEvent.ACTION_POINTER_UP;
 import static android.view.MotionEvent.ACTION_UP;
 import static android.view.MotionEvent.INVALID_POINTER_ID;
-
 import static com.android.launcher3.util.RaceConditionTracker.ENTER;
 import static com.android.launcher3.util.RaceConditionTracker.EXIT;
-import static com.android.systemui.shared.system.ActivityManagerWrapper
-        .CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
 
 import android.annotation.TargetApi;
@@ -74,6 +72,7 @@
 
     private static final long LAUNCHER_DRAW_TIMEOUT_MS = 150;
     public static final String DOWN_EVT = "OtherActivityTouchConsumer.DOWN";
+    private static final String UP_EVT = "OtherActivityTouchConsumer.UP";
 
     private final SparseArray<RecentsAnimationState> mAnimationStates = new SparseArray<>();
     private final RunningTaskInfo mRunningTask;
@@ -101,7 +100,7 @@
 
     private VelocityTracker mVelocityTracker;
     private MotionEventQueue mEventQueue;
-    private boolean mIsGoingToHome;
+    private boolean mIsGoingToLauncher;
 
     public OtherActivityTouchConsumer(Context base, RunningTaskInfo runningTaskInfo,
             RecentsModel recentsModel, Intent homeIntent, ActivityControlHelper activityControl,
@@ -192,21 +191,31 @@
 
                 if (mPassedInitialSlop && mInteractionHandler != null) {
                     // Move
-                    mInteractionHandler.updateDisplacement(displacement - mStartDisplacement);
+                    dispatchMotion(ev, displacement - mStartDisplacement);
                 }
                 break;
             }
             case ACTION_CANCEL:
                 // TODO: Should be different than ACTION_UP
             case ACTION_UP: {
+                RaceConditionTracker.onEvent(UP_EVT, ENTER);
                 TraceHelper.endSection("TouchInt");
 
                 finishTouchTracking(ev);
+                RaceConditionTracker.onEvent(UP_EVT, EXIT);
                 break;
             }
         }
     }
 
+    private void dispatchMotion(MotionEvent ev, float displacement) {
+        mInteractionHandler.updateDisplacement(displacement);
+        boolean isLandscape = isNavBarOnLeft() || isNavBarOnRight();
+        if (!isLandscape) {
+            mInteractionHandler.dispatchMotionEventToRecentsView(ev);
+        }
+    }
+
     private void notifyGestureStarted() {
         if (mInteractionHandler == null) {
             return;
@@ -297,15 +306,16 @@
      */
     private void finishTouchTracking(MotionEvent ev) {
         if (mPassedInitialSlop && mInteractionHandler != null) {
-            mInteractionHandler.updateDisplacement(getDisplacement(ev) - mStartDisplacement);
+            dispatchMotion(ev, getDisplacement(ev) - mStartDisplacement);
 
             mVelocityTracker.computeCurrentVelocity(1000,
                     ViewConfiguration.get(this).getScaledMaximumFlingVelocity());
 
-            float velocity = isNavBarOnRight() ? mVelocityTracker.getXVelocity(mActivePointerId)
-                    : isNavBarOnLeft() ? -mVelocityTracker.getXVelocity(mActivePointerId)
+            float velocityX = mVelocityTracker.getXVelocity(mActivePointerId);
+            float velocity = isNavBarOnRight() ? velocityX
+                    : isNavBarOnLeft() ? -velocityX
                             : mVelocityTracker.getYVelocity(mActivePointerId);
-            mInteractionHandler.onGestureEnded(velocity);
+            mInteractionHandler.onGestureEnded(velocity, velocityX);
         } else {
             // Since we start touch tracking on DOWN, we may reach this state without actually
             // starting the gesture. In that case, just cleanup immediately.
@@ -326,7 +336,7 @@
         if (mInteractionHandler != null) {
             final WindowTransformSwipeHandler handler = mInteractionHandler;
             mInteractionHandler = null;
-            mIsGoingToHome = handler.mIsGoingToHome;
+            mIsGoingToLauncher = handler.mIsGoingToRecents;
             mMainThreadExecutor.execute(handler::reset);
         }
     }
@@ -376,9 +386,9 @@
             // Deferred gesture, start the animation and gesture tracking once we pass the actual
             // touch slop
             startTouchTrackingForWindowAnimation(ev.getEventTime());
-            mPassedInitialSlop = true;
-            mStartDisplacement = getDisplacement(ev);
         }
+        mPassedInitialSlop = true;
+        mStartDisplacement = getDisplacement(ev);
         notifyGestureStarted();
     }
 
@@ -410,7 +420,7 @@
 
     @Override
     public boolean forceToLauncherConsumer() {
-        return mIsGoingToHome;
+        return mIsGoingToLauncher;
     }
 
     @Override
@@ -421,6 +431,7 @@
 
     private class RecentsAnimationState implements RecentsAnimationListener {
 
+        private static final String ANIMATION_START_EVT = "RecentsAnimationState.onAnimationStart";
         private final int id;
 
         private RecentsAnimationControllerCompat mController;
@@ -439,11 +450,13 @@
                 RecentsAnimationControllerCompat controller,
                 RemoteAnimationTargetCompat[] apps, Rect homeContentInsets,
                 Rect minimizedHomeBounds) {
+            RaceConditionTracker.onEvent(ANIMATION_START_EVT, ENTER);
             mController = controller;
             mTargets = new RemoteAnimationTargetSet(apps, MODE_CLOSING);
             mHomeContentInsets = homeContentInsets;
             mMinimizedHomeBounds = minimizedHomeBounds;
             mEventQueue.onCommand(id);
+            RaceConditionTracker.onEvent(ANIMATION_START_EVT, EXIT);
         }
 
         @Override
diff --git a/quickstep/src/com/android/quickstep/QuickScrubController.java b/quickstep/src/com/android/quickstep/QuickScrubController.java
index da5c4fa..db0150e 100644
--- a/quickstep/src/com/android/quickstep/QuickScrubController.java
+++ b/quickstep/src/com/android/quickstep/QuickScrubController.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_TASK_STABILIZER;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -124,6 +125,12 @@
         mActivityControlHelper = controlHelper;
         mTouchInteractionLog = touchInteractionLog;
 
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            if (mRecentsView.getRunningTaskView() != null) {
+                mRecentsView.getRunningTaskView().setShowScreenshot(false);
+            }
+        }
+
         if (mIsQuickSwitch) {
             mShouldSwitchToNext = true;
             mPrevProgressDelta = 0;
@@ -342,6 +349,7 @@
         if (action != null) {
             action.run();
         }
+        mRecentsView.setEnableDrawingLiveTile(true);
     }
 
     public void onTaskRemoved(int taskId) {
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index deedd32..89c7aba 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -18,9 +18,11 @@
 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
 
-import static com.android.launcher3.LauncherAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
-import static com.android.launcher3.LauncherAppTransitionManagerImpl.STATUS_BAR_TRANSITION_DURATION;
-import static com.android.launcher3.LauncherAppTransitionManagerImpl.STATUS_BAR_TRANSITION_PRE_DELAY;
+import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
+import static com.android.launcher3.QuickstepAppTransitionManagerImpl
+        .STATUS_BAR_TRANSITION_DURATION;
+import static com.android.launcher3.QuickstepAppTransitionManagerImpl
+        .STATUS_BAR_TRANSITION_PRE_DELAY;
 import static com.android.quickstep.TaskUtils.getRecentsWindowAnimator;
 import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
index 2f3cb5f..60bd9fb 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
@@ -97,11 +97,11 @@
     }
 
     /**
-     * @param onFinishComplete A callback that runs after the animation controller has finished
-     *                         on the background thread.
+     * @param onFinishComplete A callback that runs on the main thread after the animation
+     *                         controller has finished on the background thread.
      */
-    public void finish(boolean toHome, Runnable onFinishComplete) {
-        if (!toHome) {
+    public void finish(boolean toRecents, Runnable onFinishComplete) {
+        if (!toRecents) {
             mExecutorService.submit(() -> finishBg(false, onFinishComplete));
             return;
         }
@@ -119,16 +119,17 @@
         });
     }
 
-    protected void finishBg(boolean toHome, Runnable onFinishComplete) {
+    protected void finishBg(boolean toRecents, Runnable onFinishComplete) {
         RecentsAnimationControllerCompat controller = mController;
         mController = null;
-        TraceHelper.endSection("RecentsController", "Finish " + controller + ", toHome=" + toHome);
+        TraceHelper.endSection("RecentsController", "Finish " + controller
+                + ", toRecents=" + toRecents);
         if (controller != null) {
             controller.setInputConsumerEnabled(false);
-            controller.finish(toHome);
+            controller.finish(toRecents);
 
             if (onFinishComplete != null) {
-                onFinishComplete.run();
+                mMainThreadExecutor.execute(onFinishComplete);
             }
         }
     }
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 21da566..e61c00a 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -68,7 +68,6 @@
     private float mWindowCornerRadius = -1;
     private Boolean mSupportsRoundedCornersOnWindows;
 
-
     private RecentsModel(Context context) {
         mContext = context;
 
diff --git a/quickstep/src/com/android/quickstep/TaskListStabilizer.java b/quickstep/src/com/android/quickstep/TaskListStabilizer.java
index 0d23973..3eb26b4 100644
--- a/quickstep/src/com/android/quickstep/TaskListStabilizer.java
+++ b/quickstep/src/com/android/quickstep/TaskListStabilizer.java
@@ -17,79 +17,153 @@
 
 import static com.android.launcher3.config.FeatureFlags.ENABLE_TASK_STABILIZER;
 
+import android.app.ActivityManager.RecentTaskInfo;
+import android.content.ComponentName;
+import android.os.Process;
 import android.os.SystemClock;
-import android.util.SparseArray;
+import android.util.Log;
 
 import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.IntSet;
 import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task.TaskKey;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.TaskStackChangeListener;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
 public class TaskListStabilizer {
 
     private static final long TASK_CACHE_TIMEOUT_MS = 5000;
 
-    private final SparseArray<Task> mTempMap = new SparseArray<>();
-    private final IntArray mTempArray = new IntArray();
-    private final IntSet mTempSet = new IntSet();
+    private static final int INVALID_TASK_ID = -1;
 
-    private final IntArray mLastStableOrder = new IntArray();
-    private final IntSet mLastSet = new IntSet();
-    private final IntArray mLastUnstableOrder = new IntArray();
+    private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
 
-    private long mLastReorderTime;
+        @Override
+        public void onTaskCreated(int taskId, ComponentName componentName) {
+            onTaskCreatedInternal(taskId);
+        }
 
-    public ArrayList<Task> reorder(ArrayList<Task> tasks) {
+        @Override
+        public void onTaskMovedToFront(int taskId) {
+            onTaskMovedToFrontInternal(taskId);
+        }
+
+        @Override
+        public void onTaskRemoved(int taskId) {
+            onTaskRemovedInternal(taskId);
+        }
+    };
+
+    // Task ids ordered based on recency, 0th index is the latest task
+    private final IntArray mOrderedTaskIds;
+
+    // Wrapper objects used for sorting tasks
+    private final ArrayList<TaskWrapper> mTaskWrappers = new ArrayList<>();
+
+    // Information about recent task re-order which has not been applied yet
+    private int mScheduledMoveTaskId = INVALID_TASK_ID;
+    private long mScheduledMoveTime = 0;
+
+    public TaskListStabilizer() {
+        if (ENABLE_TASK_STABILIZER.get()) {
+            // Initialize the task ids map
+            List<RecentTaskInfo> rawTasks = ActivityManagerWrapper.getInstance().getRecentTasks(
+                    Integer.MAX_VALUE, Process.myUserHandle().getIdentifier());
+            mOrderedTaskIds = new IntArray(rawTasks.size());
+            for (RecentTaskInfo info : rawTasks) {
+                mOrderedTaskIds.add(new TaskKey(info).id);
+            }
+
+            ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
+        } else {
+            mOrderedTaskIds = null;
+        }
+    }
+
+    private synchronized void onTaskCreatedInternal(int taskId) {
+        applyScheduledMoveUnchecked();
+        mOrderedTaskIds.add(taskId);
+    }
+
+    private synchronized void onTaskRemovedInternal(int taskId) {
+        applyScheduledMoveUnchecked();
+        mOrderedTaskIds.removeValue(taskId);
+    }
+
+    private void applyScheduledMoveUnchecked() {
+        if (mScheduledMoveTaskId != INVALID_TASK_ID) {
+            // Mode the scheduled task to front
+            mOrderedTaskIds.removeValue(mScheduledMoveTaskId);
+            mOrderedTaskIds.add(mScheduledMoveTaskId);
+            mScheduledMoveTaskId = INVALID_TASK_ID;
+        }
+    }
+
+    /**
+     * Checks if the scheduled move has timed out and moves the task to front accordingly.
+     */
+    private void applyScheduledMoveIfTime() {
+        if (mScheduledMoveTaskId != INVALID_TASK_ID
+                && (SystemClock.uptimeMillis() - mScheduledMoveTime) > TASK_CACHE_TIMEOUT_MS) {
+            applyScheduledMoveUnchecked();
+        }
+    }
+
+    private synchronized void onTaskMovedToFrontInternal(int taskId) {
+        applyScheduledMoveIfTime();
+        mScheduledMoveTaskId = taskId;
+        mScheduledMoveTime = SystemClock.uptimeMillis();
+    }
+
+
+    public synchronized ArrayList<Task> reorder(ArrayList<Task> tasks) {
         if (!ENABLE_TASK_STABILIZER.get()) {
             return tasks;
         }
 
-        // Create task id array
-        int count = tasks.size();
-        mTempArray.clear();
-        mTempSet.clear();
-        mTempMap.clear();
+        applyScheduledMoveIfTime();
 
-        for (int i = 0; i < count; i++) {
-            Task t = tasks.get(i);
-            mTempMap.put(t.key.id, t);
-
-            mTempSet.add(t.key.id);
-            mTempArray.add(t.key.id);
+        // Ensure that we have enough wrappers
+        int taskCount = tasks.size();
+        for (int i = taskCount - mTaskWrappers.size(); i > 0; i--) {
+            mTaskWrappers.add(new TaskWrapper());
         }
 
-        if (mLastSet.equals(mTempSet) && isStabilizationQuickEnough()) {
-            if (mLastStableOrder.equals(mTempArray)) {
-                // Everything is same
-                return tasks;
-            }
+        List<TaskWrapper> listToSort = mTaskWrappers.size() == taskCount
+                ? mTaskWrappers : mTaskWrappers.subList(0, taskCount);
+        int missingTaskIndex = -taskCount;
 
-            if (!mLastUnstableOrder.equals(mTempArray)) {
-                // Fast reordering, record the current time.
-                mLastUnstableOrder.copyFrom(mTempArray);
-                mLastReorderTime = SystemClock.uptimeMillis();
-            }
+        for (int i = 0; i < taskCount; i++){
+            TaskWrapper wrapper = listToSort.get(i);
+            wrapper.task = tasks.get(i);
+            wrapper.index = mOrderedTaskIds.indexOf(wrapper.task.key.id);
 
-            // Reorder the tasks based on the last stable order.
-            ArrayList<Task> sorted = new ArrayList<>(count);
-            for (int i = 0; i < count; i++) {
-                sorted.add(mTempMap.get(mLastStableOrder.get(i)));
+            // Ensure that missing tasks are put in the front, in the order they appear in the
+            // original list
+            if (wrapper.index < 0) {
+                wrapper.index = missingTaskIndex;
+                missingTaskIndex++;
             }
-            return sorted;
         }
+        Collections.sort(listToSort);
 
-        // Cache the data
-        mLastStableOrder.copyFrom(mTempArray);
-        mLastUnstableOrder.copyFrom(mTempArray);
-        mLastSet.copyFrom(mTempSet);
-
-        mLastReorderTime = SystemClock.uptimeMillis();
-
-        return tasks;
+        ArrayList<Task> result = new ArrayList<>(taskCount);
+        for (int i = 0; i < taskCount; i++) {
+            result.add(listToSort.get(i).task);
+        }
+        return result;
     }
 
-    private boolean isStabilizationQuickEnough() {
-        return (SystemClock.uptimeMillis() - mLastReorderTime) < TASK_CACHE_TIMEOUT_MS;
+    private static class TaskWrapper implements Comparable<TaskWrapper> {
+        Task task;
+        int index;
+
+        @Override
+        public int compareTo(TaskWrapper other) {
+            return Integer.compare(index, other.index);
+        }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index cb214af..cc49d46 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -16,16 +16,13 @@
 
 package com.android.quickstep;
 
-import android.content.Context;
 import android.graphics.Matrix;
 import android.view.View;
 
-import androidx.annotation.AnyThread;
-
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.R;
-import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.ResourceBasedOverride;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.Task;
@@ -34,11 +31,12 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import androidx.annotation.AnyThread;
+
 /**
  * Factory class to create and add an overlays on the TaskView
  */
 public class TaskOverlayFactory implements ResourceBasedOverride {
-    private static TaskOverlayFactory sInstance;
 
     /** Note that these will be shown in order from top to bottom, if available for the task. */
     private static final TaskSystemShortcut[] MENU_OPTIONS = new TaskSystemShortcut[]{
@@ -49,14 +47,9 @@
             new TaskSystemShortcut.Freeform()
     };
 
-    public static TaskOverlayFactory get(Context context) {
-        Preconditions.assertUIThread();
-        if (sInstance == null) {
-            sInstance = Overrides.getObject(TaskOverlayFactory.class,
-                    context.getApplicationContext(), R.string.task_overlay_factory_class);
-        }
-        return sInstance;
-    }
+    public static final MainThreadInitializedObject<TaskOverlayFactory> INSTANCE =
+            new MainThreadInitializedObject<>(c -> Overrides.getObject(TaskOverlayFactory.class,
+                    c, R.string.task_overlay_factory_class));
 
     @AnyThread
     public boolean needAssist() {
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index a42ee09..8b6867f 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -22,6 +22,7 @@
 import static android.view.MotionEvent.ACTION_POINTER_INDEX_SHIFT;
 import static android.view.MotionEvent.ACTION_UP;
 
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.systemui.shared.system.ActivityManagerWrapper
         .CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
@@ -202,7 +203,7 @@
         mEventQueue = new MotionEventQueue(mMainThreadChoreographer, TouchConsumer.NO_OP);
         mOverviewInteractionState = OverviewInteractionState.INSTANCE.get(this);
         mOverviewCallbacks = OverviewCallbacks.get(this);
-        mTaskOverlayFactory = TaskOverlayFactory.get(this);
+        mTaskOverlayFactory = TaskOverlayFactory.INSTANCE.get(this);
         mTouchInteractionLog = new TouchInteractionLog();
         mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
         mInputConsumer.registerInputConsumer();
@@ -252,6 +253,11 @@
                 mOverviewCommandHelper.getActivityControlHelper().isResumed()) {
             return OverviewTouchConsumer.newInstance(
                     mOverviewCommandHelper.getActivityControlHelper(), false, mTouchInteractionLog);
+        } else if (ENABLE_QUICKSTEP_LIVE_TILE.get() &&
+                mOverviewCommandHelper.getActivityControlHelper().isInLiveTileMode()) {
+            return OverviewTouchConsumer.newInstance(
+                    mOverviewCommandHelper.getActivityControlHelper(), false, mTouchInteractionLog,
+                    false /* waitForWindowAvailable */);
         } else {
             if (tracker == null) {
                 tracker = VelocityTracker.obtain();
@@ -298,9 +304,11 @@
         private float mLastProgress = 0;
         private boolean mStartPending = false;
         private boolean mEndPending = false;
+        private boolean mWaitForWindowAvailable;
 
         OverviewTouchConsumer(ActivityControlHelper<T> activityHelper, T activity,
-                boolean startingInActivityBounds, TouchInteractionLog touchInteractionLog) {
+                boolean startingInActivityBounds, TouchInteractionLog touchInteractionLog,
+                boolean waitForWindowAvailable) {
             mActivityHelper = activityHelper;
             mActivity = activity;
             mTarget = activity.getDragLayer();
@@ -311,6 +319,8 @@
                     .getQuickScrubController();
             mTouchInteractionLog = touchInteractionLog;
             mTouchInteractionLog.setTouchConsumer(this);
+
+            mWaitForWindowAvailable = waitForWindowAvailable;
         }
 
         @Override
@@ -433,7 +443,11 @@
                 }
             };
 
-            mActivityHelper.executeOnWindowAvailable(mActivity, action);
+            if (mWaitForWindowAvailable) {
+                mActivityHelper.executeOnWindowAvailable(mActivity, action);
+            } else {
+                action.run();
+            }
         }
 
         @Override
@@ -461,12 +475,19 @@
 
         public static TouchConsumer newInstance(ActivityControlHelper activityHelper,
                 boolean startingInActivityBounds, TouchInteractionLog touchInteractionLog) {
+            return newInstance(activityHelper, startingInActivityBounds, touchInteractionLog,
+                    true /* waitForWindowAvailable */);
+        }
+
+        public static TouchConsumer newInstance(ActivityControlHelper activityHelper,
+                boolean startingInActivityBounds, TouchInteractionLog touchInteractionLog,
+                boolean waitForWindowAvailable) {
             BaseDraggingActivity activity = activityHelper.getCreatedActivity();
             if (activity == null) {
                 return TouchConsumer.NO_OP;
             }
             return new OverviewTouchConsumer(activityHelper, activity, startingInActivityBounds,
-                    touchInteractionLog);
+                    touchInteractionLog, waitForWindowAvailable);
         }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index e951750..33c7c4d 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -22,6 +22,10 @@
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.util.RaceConditionTracker.ENTER;
+import static com.android.launcher3.util.RaceConditionTracker.EXIT;
 import static com.android.quickstep.QuickScrubController.QUICK_SCRUB_FROM_APP_START_DURATION;
 import static com.android.quickstep.QuickScrubController.QUICK_SWITCH_FROM_APP_START_DURATION;
 import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
@@ -46,10 +50,12 @@
 import android.os.UserHandle;
 import android.util.Log;
 import android.view.HapticFeedbackConstants;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewTreeObserver.OnDrawListener;
 import android.view.WindowManager;
 import android.view.animation.Interpolator;
+
 import androidx.annotation.AnyThread;
 import androidx.annotation.UiThread;
 import androidx.annotation.WorkerThread;
@@ -70,6 +76,7 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
+import com.android.launcher3.util.RaceConditionTracker;
 import com.android.launcher3.util.TraceHelper;
 import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
 import com.android.quickstep.ActivityControlHelper.AnimationFactory;
@@ -109,7 +116,7 @@
 
     // Interaction finish states
     private static final int STATE_SCALED_CONTROLLER_RECENTS = 1 << 5;
-    private static final int STATE_SCALED_CONTROLLER_APP = 1 << 6;
+    private static final int STATE_SCALED_CONTROLLER_LAST_TASK = 1 << 6;
 
     private static final int STATE_HANDLER_INVALIDATED = 1 << 7;
     private static final int STATE_GESTURE_STARTED_QUICKSTEP = 1 << 8;
@@ -127,7 +134,8 @@
     private static final int STATE_SCREENSHOT_VIEW_SHOWN = 1 << 17;
 
     private static final int STATE_RESUME_LAST_TASK = 1 << 18;
-    private static final int STATE_ASSIST_DATA_RECEIVED = 1 << 19;
+    private static final int STATE_START_NEW_TASK = 1 << 19;
+    private static final int STATE_ASSIST_DATA_RECEIVED = 1 << 20;
 
 
     private static final int LAUNCHER_UI_STATES =
@@ -153,7 +161,7 @@
             "STATE_ACTIVITY_MULTIPLIER_COMPLETE",
             "STATE_APP_CONTROLLER_RECEIVED",
             "STATE_SCALED_CONTROLLER_RECENTS",
-            "STATE_SCALED_CONTROLLER_APP",
+            "STATE_SCALED_CONTROLLER_LAST_TASK",
             "STATE_HANDLER_INVALIDATED",
             "STATE_GESTURE_STARTED_QUICKSTEP",
             "STATE_GESTURE_STARTED_QUICKSCRUB",
@@ -166,6 +174,7 @@
             "STATE_SCREENSHOT_CAPTURED",
             "STATE_SCREENSHOT_VIEW_SHOWN",
             "STATE_RESUME_LAST_TASK",
+            "STATE_START_NEW_TASK",
             "STATE_ASSIST_DATA_RECEIVED",
     };
 
@@ -173,15 +182,16 @@
     public static final long MIN_SWIPE_DURATION = 80;
     public static final long MIN_OVERSHOOT_DURATION = 120;
 
-    public static final float MIN_PROGRESS_FOR_OVERVIEW = 0.5f;
+    public static final float MIN_PROGRESS_FOR_OVERVIEW = 0.7f;
     private static final float SWIPE_DURATION_MULTIPLIER =
             Math.min(1 / MIN_PROGRESS_FOR_OVERVIEW, 1 / (1 - MIN_PROGRESS_FOR_OVERVIEW));
+    private static final String SCREENSHOT_CAPTURED_EVT = "ScreenshotCaptured";
 
     private final ClipAnimationHelper mClipAnimationHelper;
     private final ClipAnimationHelper.TransformParams mTransformParams;
 
     protected Runnable mGestureEndCallback;
-    protected boolean mIsGoingToHome;
+    protected boolean mIsGoingToRecents;
     private DeviceProfile mDp;
     private int mTransitionDragLength;
 
@@ -190,6 +200,7 @@
     // 1 => preview snapShot is completely aligned with the recents view and hotseat is completely
     // visible.
     private final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
+    private boolean mDispatchedDownEvent;
     // To avoid UI jump when gesture is started, we offset the animation by the threshold.
     private float mShiftAtGestureStart = 0;
 
@@ -298,10 +309,12 @@
         mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_APP_CONTROLLER_RECEIVED,
                 this::sendRemoteAnimationsToAnimationFactory);
 
-        mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_SCALED_CONTROLLER_APP,
+        mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_SCALED_CONTROLLER_LAST_TASK,
                 this::resumeLastTaskForQuickstep);
         mStateCallback.addCallback(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED,
                 this::resumeLastTask);
+        mStateCallback.addCallback(STATE_START_NEW_TASK | STATE_APP_CONTROLLER_RECEIVED,
+                this::startNewTask);
 
         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
                         | STATE_ACTIVITY_MULTIPLIER_COMPLETE
@@ -310,7 +323,7 @@
 
         mStateCallback.addCallback(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
                         | STATE_SCALED_CONTROLLER_RECENTS,
-                this::finishCurrentTransitionToHome);
+                this::finishCurrentTransitionToRecents);
 
         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
                         | STATE_ACTIVITY_MULTIPLIER_COMPLETE | STATE_SCALED_CONTROLLER_RECENTS
@@ -327,7 +340,7 @@
         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
                 this::invalidateHandlerWithLauncher);
         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED
-                | STATE_SCALED_CONTROLLER_APP,
+                | STATE_SCALED_CONTROLLER_LAST_TASK,
                 this::notifyTransitionCancelled);
 
         mStateCallback.addCallback(QUICK_SCRUB_START_UI_STATE, this::onQuickScrubStartUi);
@@ -339,9 +352,11 @@
         mStateCallback.addCallback(LONG_SWIPE_ENTER_STATE, this::checkLongSwipeCanEnter);
         mStateCallback.addCallback(LONG_SWIPE_START_STATE, this::checkLongSwipeCanStart);
 
-        mStateCallback.addChangeHandler(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT
-                | STATE_SCREENSHOT_VIEW_SHOWN | STATE_CAPTURE_SCREENSHOT,
-                (b) -> mRecentsView.setRunningTaskHidden(!b));
+        if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            mStateCallback.addChangeHandler(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT
+                            | STATE_SCREENSHOT_VIEW_SHOWN | STATE_CAPTURE_SCREENSHOT,
+                    (b) -> mRecentsView.setRunningTaskHidden(!b));
+        }
     }
 
     private void executeOnUiThread(Runnable action) {
@@ -410,6 +425,13 @@
         SyncRtSurfaceTransactionApplierCompat.create(mRecentsView, (applier) -> {
             mSyncTransactionApplier = applier;
         });
+        mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
+            if (!mBgLongSwipeMode) {
+                updateFinalShift();
+            }
+        });
+        mRecentsView.setRecentsAnimationWrapper(mRecentsAnimationWrapper);
+        mRecentsView.setClipAnimationHelper(mClipAnimationHelper);
         mQuickScrubController = mRecentsView.getQuickScrubController();
         mLayoutListener = mActivityControlHelper.createLayoutListener(mActivity);
 
@@ -462,6 +484,7 @@
     }
 
     private void setupRecentsViewUi() {
+        mRecentsView.setEnableDrawingLiveTile(false);
         mRecentsView.showTask(mRunningTaskId);
         mRecentsView.setRunningTaskHidden(true);
         mRecentsView.setRunningTaskIconScaledDown(true);
@@ -535,15 +558,39 @@
         } else {
             offsetX = res.getDimensionPixelSize(R.dimen.recents_page_spacing)
                     + tempRect.rect.width();
-            float distanceToReachEdge = mDp.widthPx / 2 + tempRect.rect.width() / 2 +
-                    res.getDimensionPixelSize(R.dimen.recents_page_spacing);
-            float interpolation = Math.min(1, offsetX / distanceToReachEdge);
-            scale = TaskView.getCurveScaleForInterpolation(interpolation);
+            scale = getTaskCurveScaleForOffsetX(offsetX, tempRect.rect.width());
         }
         mClipAnimationHelper.offsetTarget(scale, Utilities.isRtl(res) ? -offsetX : offsetX, offsetY,
                 QuickScrubController.QUICK_SCRUB_START_INTERPOLATOR);
     }
 
+    private float getTaskCurveScaleForOffsetX(float offsetX, float taskWidth) {
+        float distanceToReachEdge = mDp.widthPx / 2 + taskWidth / 2 +
+                mContext.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing);
+        float interpolation = Math.min(1, offsetX / distanceToReachEdge);
+        return TaskView.getCurveScaleForInterpolation(interpolation);
+    }
+
+    @WorkerThread
+    public void dispatchMotionEventToRecentsView(MotionEvent event) {
+        if (mRecentsView == null) {
+            return;
+        }
+        // Pass the motion events to RecentsView to allow scrolling during swipe up.
+        if (mDispatchedDownEvent) {
+            mRecentsView.dispatchTouchEvent(event);
+        } else {
+            // The first event we dispatch should be ACTION_DOWN.
+            mDispatchedDownEvent = true;
+            MotionEvent downEvent = MotionEvent.obtain(event);
+            downEvent.setAction(MotionEvent.ACTION_DOWN);
+            int flags = downEvent.getEdgeFlags();
+            downEvent.setEdgeFlags(flags | TouchInteractionService.EDGE_NAV_BAR);
+            mRecentsView.dispatchTouchEvent(downEvent);
+            downEvent.recycle();
+        }
+    }
+
     @WorkerThread
     public void updateDisplacement(float displacement) {
         // We are moving in the negative x/y direction
@@ -588,11 +635,21 @@
 
         RecentsAnimationControllerCompat controller = mRecentsAnimationWrapper.getController();
         if (controller != null) {
+            float offsetX = 0;
+            if (mRecentsView != null && mInteractionType == INTERACTION_NORMAL) {
+                int startScroll = mRecentsView.getScrollForPage(mRecentsView.indexOfChild(
+                        mRecentsView.getRunningTaskView()));
+                offsetX = startScroll - mRecentsView.getScrollX();
+                offsetX *= mRecentsView.getScaleX();
+            }
+            float offsetScale = getTaskCurveScaleForOffsetX(offsetX,
+                    mClipAnimationHelper.getTargetRect().width());
             SyncRtSurfaceTransactionApplierCompat syncTransactionApplier
                     = Looper.myLooper() == mMainThreadHandler.getLooper()
                             ? mSyncTransactionApplier
                             : null;
-            mTransformParams.setProgress(shift).setSyncTransactionApplier(syncTransactionApplier);
+            mTransformParams.setProgress(shift).setOffsetX(offsetX).setOffsetScale(offsetScale)
+                    .setSyncTransactionApplier(syncTransactionApplier);
             mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targetSet,
                     mTransformParams);
 
@@ -607,10 +664,13 @@
     }
 
     private void updateFinalShiftUi() {
-        if (mRecentsAnimationWrapper.getController() != null && mLayoutListener != null) {
-            mLayoutListener.update(mCurrentShift.value > 1, mUiLongSwipeMode,
-                    mClipAnimationHelper.getCurrentRectWithInsets(),
-                    mClipAnimationHelper.getCurrentCornerRadius());
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            if (mRecentsAnimationWrapper.getController() != null && mLayoutListener != null) {
+                mLayoutListener.open();
+                mLayoutListener.update(mCurrentShift.value > 1, mUiLongSwipeMode,
+                        mClipAnimationHelper.getCurrentRectWithInsets(),
+                        mClipAnimationHelper.getCurrentCornerRadius());
+            }
         }
 
         final boolean passed = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW;
@@ -621,10 +681,17 @@
                     HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
             }
         }
-        // Update insets of the next previous task, as we might switch to it.
-        TaskView nextTaskView = mRecentsView == null ? null : mRecentsView.getNextTaskView();
-        if (mInteractionType == INTERACTION_NORMAL && nextTaskView != null) {
-            nextTaskView.setFullscreenProgress(1 - mCurrentShift.value);
+        // Update insets of the adjacent tasks, as we might switch to them.
+        int runningTaskIndex = mRecentsView == null ? -1 : mRecentsView.getRunningTaskIndex();
+        if (mInteractionType == INTERACTION_NORMAL && runningTaskIndex >= 0) {
+            TaskView nextTaskView = mRecentsView.getTaskViewAt(runningTaskIndex + 1);
+            TaskView prevTaskView = mRecentsView.getTaskViewAt(runningTaskIndex - 1);
+            if (nextTaskView != null) {
+                nextTaskView.setFullscreenProgress(1 - mCurrentShift.value);
+            }
+            if (prevTaskView != null) {
+                prevTaskView.setFullscreenProgress(1 - mCurrentShift.value);
+            }
         }
 
         if (mLauncherTransitionController == null || mLauncherTransitionController
@@ -714,7 +781,7 @@
     }
 
     @WorkerThread
-    public void onGestureEnded(float endVelocity) {
+    public void onGestureEnded(float endVelocity, float velocityX) {
         float flingThreshold = mContext.getResources()
                 .getDimension(R.dimen.quickstep_fling_threshold_velocity);
         boolean isFling = mGestureStarted && Math.abs(endVelocity) > flingThreshold;
@@ -723,9 +790,9 @@
         mLogAction = isFling ? Touch.FLING : Touch.SWIPE;
 
         if (mBgLongSwipeMode) {
-            executeOnUiThread(() -> onLongSwipeGestureFinishUi(endVelocity, isFling));
+            executeOnUiThread(() -> onLongSwipeGestureFinishUi(endVelocity, isFling, velocityX));
         } else {
-            handleNormalGestureEnd(endVelocity, isFling);
+            handleNormalGestureEnd(endVelocity, isFling, velocityX);
         }
     }
 
@@ -735,38 +802,47 @@
         if (mLauncherTransitionController != null) {
             mLauncherTransitionController.getAnimationPlayer().end();
         }
-        // Hide the task view, if not already hidden
-        setTargetAlphaProvider(WindowTransformSwipeHandler::getHiddenTargetAlpha);
+        if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            // Hide the task view, if not already hidden
+            setTargetAlphaProvider(WindowTransformSwipeHandler::getHiddenTargetAlpha);
+        }
 
         return OverviewTouchConsumer.newInstance(mActivityControlHelper, true,
                 mTouchInteractionLog);
     }
 
-    private void handleNormalGestureEnd(float endVelocity, boolean isFling) {
+    private void handleNormalGestureEnd(float endVelocity, boolean isFling, float velocityX) {
         float velocityPxPerMs = endVelocity / 1000;
+        float velocityXPxPerMs = velocityX / 1000;
         long duration = MAX_SWIPE_DURATION;
         float currentShift = mCurrentShift.value;
-        final boolean goingToHome;
+        final boolean goingToRecents;
         float endShift;
         final float startShift;
         Interpolator interpolator = DEACCEL;
+        final int nextPage = mRecentsView != null ? mRecentsView.getNextPage() : -1;
+        final int runningTaskIndex = mRecentsView != null ? mRecentsView.getRunningTaskIndex() : -1;
+        boolean goingToNewTask = mRecentsView != null && nextPage != runningTaskIndex;
+        final boolean reachedOverviewThreshold = currentShift >= MIN_PROGRESS_FOR_OVERVIEW;
         if (!isFling) {
-            goingToHome = currentShift >= MIN_PROGRESS_FOR_OVERVIEW && mGestureStarted;
-            endShift = goingToHome ? 1 : 0;
+            goingToRecents = reachedOverviewThreshold && mGestureStarted;
+            endShift = goingToRecents ? 1 : 0;
             long expectedDuration = Math.abs(Math.round((endShift - currentShift)
                     * MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER));
             duration = Math.min(MAX_SWIPE_DURATION, expectedDuration);
             startShift = currentShift;
-            interpolator = goingToHome ? OVERSHOOT_1_2 : DEACCEL;
+            interpolator = goingToRecents ? OVERSHOOT_1_2 : DEACCEL;
         } else {
-            goingToHome = endVelocity < 0;
-            endShift = goingToHome ? 1 : 0;
+            // If user scrolled to a new task, only go to recents if they already passed
+            // the overview threshold. Otherwise, we'll snap to the new task and launch it.
+            goingToRecents = endVelocity < 0 && (!goingToNewTask || reachedOverviewThreshold);
+            endShift = goingToRecents ? 1 : 0;
             startShift = Utilities.boundToRange(currentShift - velocityPxPerMs
                     * SINGLE_FRAME_MS / mTransitionDragLength, 0, 1);
             float minFlingVelocity = mContext.getResources()
                     .getDimension(R.dimen.quickstep_fling_min_velocity);
             if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) {
-                if (goingToHome) {
+                if (goingToRecents) {
                     Interpolators.OvershootParams overshoot = new Interpolators.OvershootParams(
                             startShift, endShift, endShift, velocityPxPerMs, mTransitionDragLength);
                     endShift = overshoot.end;
@@ -784,11 +860,31 @@
                 }
             }
         }
-        if (goingToHome) {
+        if (goingToRecents) {
             mRecentsAnimationWrapper.enableTouchProxy();
+        } else if (goingToNewTask) {
+            // We aren't goingToRecents, and user scrolled/flung to a new task; snap to the closest
+            // task in that direction and launch it (in startNewTask()).
+            int taskToLaunch = runningTaskIndex + (nextPage > runningTaskIndex ? 1 : - 1);
+            if (taskToLaunch >= mRecentsView.getTaskViewCount()) {
+                // Scrolled to Clear all button, snap back to current task and resume it.
+                mRecentsView.snapToPage(runningTaskIndex, Math.toIntExact(duration));
+                goingToNewTask = false;
+            } else {
+                float distance = Math.abs(mRecentsView.getScrollForPage(taskToLaunch)
+                        - mRecentsView.getScrollX());
+                int durationX = (int) Math.abs(distance / velocityXPxPerMs);
+                if (durationX > MAX_SWIPE_DURATION) {
+                    durationX = Math.toIntExact(MAX_SWIPE_DURATION);
+                }
+                interpolator = Interpolators.scrollInterpolatorForVelocity(velocityXPxPerMs);
+                mRecentsView.snapToPage(taskToLaunch, durationX, interpolator);
+                duration = Math.max(duration, durationX);
+            }
         }
 
-        animateToProgress(startShift, endShift, duration, interpolator, goingToHome);
+        animateToProgress(startShift, endShift, duration, interpolator, goingToRecents,
+                goingToNewTask, velocityPxPerMs);
     }
 
     private void doLogGesture(boolean toLauncher) {
@@ -813,23 +909,28 @@
     }
 
     /** Animates to the given progress, where 0 is the current app and 1 is overview. */
-    private void animateToProgress(float start, float end, long duration,
-            Interpolator interpolator, boolean goingToHome) {
+    private void animateToProgress(float start, float end, long duration, Interpolator interpolator,
+            boolean goingToRecents, boolean goingToNewTask, float velocityPxPerMs) {
         mRecentsAnimationWrapper.runOnInit(() -> animateToProgressInternal(start, end, duration,
-                interpolator, goingToHome));
+                interpolator, goingToRecents, goingToNewTask, velocityPxPerMs));
     }
 
     private void animateToProgressInternal(float start, float end, long duration,
-            Interpolator interpolator, boolean goingToHome) {
-        mIsGoingToHome = goingToHome;
+            Interpolator interpolator, boolean goingToRecents, boolean goingToNewTask,
+            float velocityPxPerMs) {
+        mIsGoingToRecents = goingToRecents;
         ObjectAnimator anim = mCurrentShift.animateToValue(start, end).setDuration(duration);
         anim.setInterpolator(interpolator);
         anim.addListener(new AnimationSuccessListener() {
             @Override
             public void onAnimationSuccess(Animator animator) {
-                setStateOnUiThread(mIsGoingToHome
-                        ? (STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
-                        | STATE_SCREENSHOT_VIEW_SHOWN) : STATE_SCALED_CONTROLLER_APP);
+                int recentsState = STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
+                        | STATE_SCREENSHOT_VIEW_SHOWN;
+                setStateOnUiThread(mIsGoingToRecents
+                        ? recentsState
+                        : goingToNewTask
+                            ? STATE_START_NEW_TASK
+                            : STATE_SCALED_CONTROLLER_LAST_TASK);
             }
         });
         anim.start();
@@ -854,7 +955,12 @@
                 mLauncherTransitionController.dispatchSetInterpolator(Interpolators.mapToProgress(
                         interpolator, adjustedStart, end));
                 mLauncherTransitionController.getAnimationPlayer().setDuration(adjustedDuration);
-                mLauncherTransitionController.getAnimationPlayer().start();
+
+                if (QUICKSTEP_SPRINGS.get()) {
+                    mLauncherTransitionController.dispatchOnStartWithVelocity(end, velocityPxPerMs);
+                } else {
+                    mLauncherTransitionController.getAnimationPlayer().start();
+                }
             }
         });
     }
@@ -868,10 +974,22 @@
 
     @UiThread
     private void resumeLastTask() {
-        mRecentsAnimationWrapper.finish(false /* toHome */, null);
+        mRecentsAnimationWrapper.finish(false /* toRecents */, null);
         mTouchInteractionLog.finishRecentsAnimation(false);
     }
 
+    @UiThread
+    private void startNewTask() {
+        // Launch the task user scrolled to (mRecentsView.getNextPage()).
+        mRecentsAnimationWrapper.finish(true /* toRecents */, () -> {
+            mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false,
+                    result -> setStateOnUiThread(STATE_HANDLER_INVALIDATED),
+                    mMainThreadHandler);
+        });
+        mTouchInteractionLog.finishRecentsAnimation(false);
+        doLogGesture(false /* toLauncher */);
+    }
+
     public void reset() {
         if (mInteractionType != INTERACTION_QUICK_SCRUB) {
             // Only invalidate the handler if we are not quick scrubbing, otherwise, it will be
@@ -889,6 +1007,10 @@
 
         mActivityInitListener.unregister();
         mTaskSnapshot = null;
+
+        if (mRecentsView != null) {
+            mRecentsView.setOnScrollChangeListener(null);
+        }
     }
 
     private void invalidateHandlerWithLauncher() {
@@ -913,57 +1035,68 @@
     }
 
     public void layoutListenerClosed() {
+        mRecentsView.setRunningTaskHidden(false);
         if (mWasLauncherAlreadyVisible && mLauncherTransitionController != null) {
             mLauncherTransitionController.setPlayFraction(1);
         }
-        mRecentsView.setRunningTaskHidden(false);
+        mRecentsView.setEnableDrawingLiveTile(true);
     }
 
     private void switchToScreenshot() {
-        boolean finishTransitionPosted = false;
-        RecentsAnimationControllerCompat controller = mRecentsAnimationWrapper.getController();
-        if (controller != null) {
-            // Update the screenshot of the task
-            if (mTaskSnapshot == null) {
-                mTaskSnapshot = controller.screenshotTask(mRunningTaskId);
-            }
-            TaskView taskView = mRecentsView.updateThumbnail(mRunningTaskId, mTaskSnapshot);
-            if (taskView != null) {
-                // Defer finishing the animation until the next launcher frame with the
-                // new thumbnail
-                finishTransitionPosted = new WindowCallbacksCompat(taskView) {
-
-                    // The number of frames to defer until we actually finish the animation
-                    private int mDeferFrameCount = 2;
-
-                    @Override
-                    public void onPostDraw(Canvas canvas) {
-                        if (mDeferFrameCount > 0) {
-                            mDeferFrameCount--;
-                            // Workaround, detach and reattach to invalidate the root node for
-                            // another draw
-                            detach();
-                            attach();
-                            taskView.invalidate();
-                            return;
-                        }
-
-                        setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
-                        detach();
-                    }
-                }.attach();
-            }
-        }
-        if (!finishTransitionPosted) {
-            // If we haven't posted a draw callback, set the state immediately.
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
+        } else {
+            boolean finishTransitionPosted = false;
+            RecentsAnimationControllerCompat controller = mRecentsAnimationWrapper.getController();
+            if (controller != null) {
+                // Update the screenshot of the task
+                if (mTaskSnapshot == null) {
+                    mTaskSnapshot = controller.screenshotTask(mRunningTaskId);
+                }
+                TaskView taskView = mRecentsView.updateThumbnail(mRunningTaskId, mTaskSnapshot);
+                if (taskView != null) {
+                    // Defer finishing the animation until the next launcher frame with the
+                    // new thumbnail
+                    finishTransitionPosted = new WindowCallbacksCompat(taskView) {
+
+                        // The number of frames to defer until we actually finish the animation
+                        private int mDeferFrameCount = 2;
+
+                        @Override
+                        public void onPostDraw(Canvas canvas) {
+                            if (mDeferFrameCount > 0) {
+                                mDeferFrameCount--;
+                                // Workaround, detach and reattach to invalidate the root node for
+                                // another draw
+                                detach();
+                                attach();
+                                taskView.invalidate();
+                                return;
+                            }
+
+                            setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
+                            detach();
+                        }
+                    }.attach();
+                }
+            }
+            if (!finishTransitionPosted) {
+                // If we haven't posted a draw callback, set the state immediately.
+                RaceConditionTracker.onEvent(SCREENSHOT_CAPTURED_EVT, ENTER);
+                setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
+                RaceConditionTracker.onEvent(SCREENSHOT_CAPTURED_EVT, EXIT);
+            }
         }
     }
 
-    private void finishCurrentTransitionToHome() {
-        synchronized (mRecentsAnimationWrapper) {
-            mRecentsAnimationWrapper.finish(true /* toHome */,
-                    () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
+    private void finishCurrentTransitionToRecents() {
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
+        } else {
+            synchronized (mRecentsAnimationWrapper) {
+                mRecentsAnimationWrapper.finish(true /* toRecents */,
+                        () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
+            }
         }
         mTouchInteractionLog.finishRecentsAnimation(true);
     }
@@ -999,7 +1132,8 @@
         long duration = FeatureFlags.QUICK_SWITCH.get()
                 ? QUICK_SWITCH_FROM_APP_START_DURATION
                 : QUICK_SCRUB_FROM_APP_START_DURATION;
-        animateToProgress(mCurrentShift.value, 1f, duration, LINEAR, true /* goingToHome */);
+        animateToProgress(mCurrentShift.value, 1f, duration, LINEAR, true /* goingToRecents */,
+                false /* goingToNewTask */, 1f);
     }
 
     private void onQuickScrubStartUi() {
@@ -1012,7 +1146,6 @@
             mLauncherTransitionController.getAnimationPlayer().end();
             mLauncherTransitionController = null;
         }
-        mLayoutListener.finish();
 
         mActivityControlHelper.onQuickInteractionStart(mActivity, mRunningTaskInfo, false,
                 mTouchInteractionLog);
@@ -1025,6 +1158,7 @@
         if (mQuickScrubBlocked) {
             return;
         }
+        mLayoutListener.finish();
         mQuickScrubController.onFinishedTransitionToQuickScrub();
 
         mRecentsView.animateUpRunningTaskIconScale();
@@ -1149,17 +1283,19 @@
         mLongSwipeController = mActivityControlHelper.getLongSwipeController(
                 mActivity, mRunningTaskId);
         onLongSwipeDisplacementUpdated();
-        setTargetAlphaProvider(WindowTransformSwipeHandler::getHiddenTargetAlpha);
+        if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            setTargetAlphaProvider(WindowTransformSwipeHandler::getHiddenTargetAlpha);
+        }
     }
 
-    private void onLongSwipeGestureFinishUi(float velocity, boolean isFling) {
+    private void onLongSwipeGestureFinishUi(float velocity, boolean isFling, float velocityX) {
         if (!mUiLongSwipeMode || mLongSwipeController == null) {
             mUiLongSwipeMode = false;
-            handleNormalGestureEnd(velocity, isFling);
+            handleNormalGestureEnd(velocity, isFling, velocityX);
             return;
         }
         mUiLongSwipeMode = false;
-        finishCurrentTransitionToHome();
+        finishCurrentTransitionToRecents();
         mLongSwipeController.end(velocity, isFling,
                 () -> setStateOnUiThread(STATE_HANDLER_INVALIDATED));
 
diff --git a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
index 64fbe96..a82a2c5 100644
--- a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
+++ b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
@@ -16,6 +16,7 @@
 package com.android.quickstep.util;
 
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.quickstep.QuickScrubController.QUICK_SCRUB_TRANSLATION_Y_FACTOR;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
@@ -73,6 +74,9 @@
     // if the aspect ratio of the target is smaller than the aspect ratio of the source rect. In
     // app window coordinates.
     private final RectF mSourceWindowClipInsets = new RectF();
+    // The insets to be used for clipping the app window. For live tile, we don't transform the clip
+    // relative to the target rect.
+    private final RectF mSourceWindowClipInsetsForLiveTile = new RectF();
 
     // The bounds of launcher (not including insets) in device coordinates
     public final Rect mHomeStackBounds = new Rect();
@@ -150,6 +154,7 @@
                 Math.max(scaledTargetRect.top, 0),
                 Math.max(mSourceStackBounds.width() - scaledTargetRect.right, 0),
                 Math.max(mSourceStackBounds.height() - scaledTargetRect.bottom, 0));
+        mSourceWindowClipInsetsForLiveTile.set(mSourceWindowClipInsets);
         mSourceRect.set(scaledTargetRect);
     }
 
@@ -158,55 +163,53 @@
     }
 
     public RectF applyTransform(RemoteAnimationTargetSet targetSet, TransformParams params) {
-        RectF currentRect;
-        mTmpRectF.set(mTargetRect);
-        Utilities.scaleRectFAboutCenter(mTmpRectF, mTargetScale);
-        float offsetYProgress = mOffsetYInterpolator.getInterpolation(params.progress);
-        float progress = mInterpolator.getInterpolation(params.progress);
-        currentRect = mRectFEvaluator.evaluate(progress, mSourceRect, mTmpRectF);
+        if (params.currentRect == null) {
+            RectF currentRect;
+            mTmpRectF.set(mTargetRect);
+            Utilities.scaleRectFAboutCenter(mTmpRectF, mTargetScale * params.offsetScale);
+            float offsetYProgress = mOffsetYInterpolator.getInterpolation(params.progress);
+            float progress = mInterpolator.getInterpolation(params.progress);
+            currentRect = mRectFEvaluator.evaluate(progress, mSourceRect, mTmpRectF);
+            currentRect.offset(params.offsetX, 0);
 
-        synchronized (mTargetOffset) {
-            // Stay lined up with the center of the target, since it moves for quick scrub.
-            currentRect.offset(mTargetOffset.x * mOffsetScale * progress,
-                    mTargetOffset.y  * offsetYProgress);
+            synchronized (mTargetOffset) {
+                // Stay lined up with the center of the target, since it moves for quick scrub.
+                currentRect.offset(mTargetOffset.x * mOffsetScale * progress,
+                        mTargetOffset.y  * offsetYProgress);
+            }
+
+            final RectF sourceWindowClipInsets = params.forLiveTile
+                    ? mSourceWindowClipInsetsForLiveTile : mSourceWindowClipInsets;
+            mClipRectF.left = sourceWindowClipInsets.left * progress;
+            mClipRectF.top = sourceWindowClipInsets.top * progress;
+            mClipRectF.right =
+                    mSourceStackBounds.width() - (sourceWindowClipInsets.right * progress);
+            mClipRectF.bottom =
+                    mSourceStackBounds.height() - (sourceWindowClipInsets.bottom * progress);
+            params.setCurrentRectAndTargetAlpha(currentRect, 1);
         }
 
-        mClipRectF.left = mSourceWindowClipInsets.left * progress;
-        mClipRectF.top = mSourceWindowClipInsets.top * progress;
-        mClipRectF.right =
-                mSourceStackBounds.width() - (mSourceWindowClipInsets.right * progress);
-        mClipRectF.bottom =
-                mSourceStackBounds.height() - (mSourceWindowClipInsets.bottom * progress);
-
         SurfaceParams[] surfaceParams = new SurfaceParams[targetSet.unfilteredApps.length];
         for (int i = 0; i < targetSet.unfilteredApps.length; i++) {
             RemoteAnimationTargetCompat app = targetSet.unfilteredApps[i];
             mTmpMatrix.setTranslate(app.position.x, app.position.y);
             Rect crop = app.sourceContainerBounds;
             float alpha = 1f;
-            int layer;
+            int layer = RemoteAnimationProvider.getLayer(app, mBoostModeTargetLayers);
             float cornerRadius = 0f;
-            float scale = currentRect.width() / crop.width();
+            float scale = params.currentRect.width() / crop.width();
             if (app.mode == targetSet.targetMode) {
                 if (app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
-                    mTmpMatrix.setRectToRect(mSourceRect, currentRect, ScaleToFit.FILL);
+                    mTmpMatrix.setRectToRect(mSourceRect, params.currentRect, ScaleToFit.FILL);
                     mTmpMatrix.postTranslate(app.position.x, app.position.y);
                     mClipRectF.roundOut(crop);
                     if (mSupportsRoundedCornersOnWindows) {
-                        cornerRadius = Utilities.mapRange(progress, mWindowCornerRadius,
+                        cornerRadius = Utilities.mapRange(params.progress, mWindowCornerRadius,
                                 mTaskCornerRadius);
                     }
-                    mCurrentCornerRadius = cornerRadius;
                 }
-
-                if (app.isNotInRecents
-                        || app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
-                    alpha = 1 - progress;
-                }
-
-                alpha = mTaskAlphaCallback.apply(app, alpha);
-                layer = RemoteAnimationProvider.getLayer(app, mBoostModeTargetLayers);
-            } else {
+                alpha = mTaskAlphaCallback.apply(app, params.targetAlpha);
+            } else if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
                 crop = null;
                 layer = Integer.MAX_VALUE;
             }
@@ -217,7 +220,7 @@
                     cornerRadius / scale);
         }
         applySurfaceParams(params.syncTransactionApplier, surfaceParams);
-        return currentRect;
+        return params.currentRect;
     }
 
     public RectF getCurrentRectWithInsets() {
@@ -361,14 +364,48 @@
 
     public static class TransformParams {
         float progress;
+        float offsetX;
+        float offsetScale;
+        @Nullable RectF currentRect;
+        float targetAlpha;
+        boolean forLiveTile;
+
         SyncRtSurfaceTransactionApplierCompat syncTransactionApplier;
 
         public TransformParams() {
             progress = 0;
+            offsetX = 0;
+            offsetScale = 1;
+            currentRect = null;
+            targetAlpha = 0;
+            forLiveTile = false;
         }
 
         public TransformParams setProgress(float progress) {
             this.progress = progress;
+            this.currentRect = null;
+            return this;
+        }
+
+        public TransformParams setCurrentRectAndTargetAlpha(RectF currentRect, float targetAlpha) {
+            this.currentRect = currentRect;
+            this.targetAlpha = targetAlpha;
+            this.progress = 1;
+            return this;
+        }
+
+        public TransformParams setOffsetX(float offsetX) {
+            this.offsetX = offsetX;
+            return this;
+        }
+
+        public TransformParams setOffsetScale(float offsetScale) {
+            this.offsetScale = offsetScale;
+            return this;
+        }
+
+        public TransformParams setForLiveTile(boolean forLiveTile) {
+            this.forLiveTile = forLiveTile;
             return this;
         }
 
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index 6ca0dce..ed585c1 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -21,14 +21,15 @@
 import android.content.res.Resources;
 import android.graphics.Rect;
 
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
-
-import java.lang.annotation.Retention;
-
 import androidx.annotation.AnyThread;
 import androidx.annotation.IntDef;
 
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
+
+import java.lang.annotation.Retention;
+
 public class LayoutUtils {
 
     private static final int MULTI_WINDOW_STRATEGY_HALF_SCREEN = 1;
@@ -112,7 +113,14 @@
                 Math.round(x + outWidth), Math.round(y + outHeight));
     }
 
-    public static int getShelfTrackingDistance(DeviceProfile dp) {
+    public static int getShelfTrackingDistance(Context context, DeviceProfile dp) {
+        if (FeatureFlags.SWIPE_HOME.get()) {
+            // Track the bottom of the window rather than the top of the shelf.
+            int shelfHeight = dp.hotseatBarSizePx + dp.getInsets().bottom;
+            int spaceBetweenShelfAndRecents = (int) context.getResources().getDimension(
+                    R.dimen.task_card_vert_space);
+            return shelfHeight + spaceBetweenShelfAndRecents;
+        }
         // Start from a third of bottom inset to provide some shelf overlap.
         return dp.hotseatBarSizePx + dp.getInsets().bottom / 3 - dp.edgeMarginPx * 2;
     }
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java b/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java
index 6ee305f..10283bf 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java
@@ -53,6 +53,7 @@
     private final RecentsView mParent;
     private final View mIconView;
     private final int[] mIconPos;
+    private final TaskView mTaskView;
 
     private final TaskThumbnailView mThumbnailView;
 
@@ -65,6 +66,7 @@
 
     public TaskViewDrawable(TaskView tv, RecentsView parent) {
         mParent = parent;
+        mTaskView = tv;
         mIconView = tv.getIconView();
         mIconPos = new int[2];
         mIconScale = mIconView.getScaleX();
@@ -139,4 +141,8 @@
     public int getOpacity() {
         return PixelFormat.TRANSLUCENT;
     }
+
+    public TaskView getTaskView() {
+        return mTaskView;
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
index a92295e..9ad750b 100644
--- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -20,18 +20,32 @@
 import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.res.Resources;
+import android.icu.text.MeasureFormat;
+import android.icu.text.MeasureFormat.FormatWidth;
+import android.icu.util.Measure;
+import android.icu.util.MeasureUnit;
+import android.os.UserHandle;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
+import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import androidx.annotation.StringRes;
+
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.systemui.shared.recents.model.Task;
 
+import java.lang.reflect.Method;
+import java.time.Duration;
+import java.util.Locale;
+
 public final class DigitalWellBeingToast extends LinearLayout {
     public interface InitializeCallback {
         void call(float saturation, String contentDescription);
@@ -40,42 +54,138 @@
     private static final String TAG = DigitalWellBeingToast.class.getSimpleName();
 
     private Task mTask;
+    private ImageView mImage;
+    private TextView mText;
 
     public DigitalWellBeingToast(Context context, AttributeSet attrs) {
         super(context, attrs);
         setLayoutDirection(Utilities.isRtl(getResources()) ?
                 View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
         setOnClickListener((view) -> openAppUsageSettings());
+    }
 
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mText = findViewById(R.id.digital_well_being_remaining_time);
+        mImage = findViewById(R.id.digital_well_being_hourglass);
     }
 
     public void initialize(Task task, InitializeCallback callback) {
         mTask = task;
         Utilities.THREAD_POOL_EXECUTOR.execute(() -> {
-            final long appUsageLimitTimeMs = -1;
-            final long appRemainingTimeMs = -1;
-            final boolean isGroupLimit = true;
+            long appUsageLimitTimeMs = -1;
+            long appRemainingTimeMs = -1;
+
+            try {
+                final Method getAppUsageLimit = LauncherApps.class.getMethod(
+                        "getAppUsageLimit",
+                        String.class,
+                        UserHandle.class);
+                final Object usageLimit = getAppUsageLimit.invoke(
+                        getContext().getSystemService(LauncherApps.class),
+                        task.getTopComponent().getPackageName(),
+                        UserHandle.of(task.key.userId));
+
+                if (usageLimit != null) {
+                    final Class appUsageLimitClass = usageLimit.getClass();
+                    appUsageLimitTimeMs = (long) appUsageLimitClass.getMethod("getTotalUsageLimit").
+                            invoke(usageLimit);
+                    appRemainingTimeMs = (long) appUsageLimitClass.getMethod("getUsageRemaining").
+                            invoke(usageLimit);
+                }
+            } catch (Exception e) {
+                // Do nothing
+            }
+
+            final long appUsageLimitTimeMsFinal = appUsageLimitTimeMs;
+            final long appRemainingTimeMsFinal = appRemainingTimeMs;
+
             post(() -> {
-                final TextView remainingTimeText = findViewById(R.id.remaining_time);
-                if (appUsageLimitTimeMs < 0) {
+                if (appUsageLimitTimeMsFinal < 0) {
                     setVisibility(GONE);
                 } else {
                     setVisibility(VISIBLE);
-                    remainingTimeText.setText(getText(appRemainingTimeMs, isGroupLimit));
+                    mText.setText(getText(appRemainingTimeMsFinal));
+                    mImage.setImageResource(appRemainingTimeMsFinal > 0 ?
+                            R.drawable.hourglass_top : R.drawable.hourglass_bottom);
                 }
 
                 callback.call(
-                        appUsageLimitTimeMs >= 0 && appRemainingTimeMs < 0 ? 0 : 1,
-                        getContentDescriptionForTask(task, appRemainingTimeMs, isGroupLimit));
+                        appUsageLimitTimeMsFinal >= 0 && appRemainingTimeMsFinal <= 0 ? 0 : 1,
+                        getContentDescriptionForTask(
+                                task, appUsageLimitTimeMsFinal, appRemainingTimeMsFinal));
             });
         });
     }
 
-    public static String getText(long remainingTime, boolean isGroupLimit) {
-        return remainingTime < 0 ?
-                "Grayed" :
-                "Remaining time:" + (remainingTime + 59999) / 60000
-                        + " min " + (isGroupLimit ? "for group" : "for the app");
+    private String getReadableDuration(
+            Duration duration,
+            FormatWidth formatWidthHourAndMinute,
+            @StringRes int durationLessThanOneMinuteStringId,
+            boolean forceFormatWidth) {
+        int hours = Math.toIntExact(duration.toHours());
+        int minutes = Math.toIntExact(duration.minusHours(hours).toMinutes());
+
+        // Apply formatWidthHourAndMinute if both the hour part and the minute part are non-zero.
+        if (hours > 0 && minutes > 0) {
+            return MeasureFormat.getInstance(Locale.getDefault(), formatWidthHourAndMinute)
+                    .formatMeasures(
+                            new Measure(hours, MeasureUnit.HOUR),
+                            new Measure(minutes, MeasureUnit.MINUTE));
+        }
+
+        // Apply formatWidthHourOrMinute if only the hour part is non-zero (unless forced).
+        if (hours > 0) {
+            return MeasureFormat.getInstance(
+                    Locale.getDefault(),
+                    forceFormatWidth ? formatWidthHourAndMinute : FormatWidth.WIDE)
+                    .formatMeasures(new Measure(hours, MeasureUnit.HOUR));
+        }
+
+        // Apply formatWidthHourOrMinute if only the minute part is non-zero (unless forced).
+        if (minutes > 0) {
+            return MeasureFormat.getInstance(
+                    Locale.getDefault()
+                    , forceFormatWidth ? formatWidthHourAndMinute : FormatWidth.WIDE)
+                    .formatMeasures(new Measure(minutes, MeasureUnit.MINUTE));
+        }
+
+        // Use a specific string for usage less than one minute but non-zero.
+        if (duration.compareTo(Duration.ZERO) > 0) {
+            return getResources().getString(durationLessThanOneMinuteStringId);
+        }
+
+        // Otherwise, return 0-minute string.
+        return MeasureFormat.getInstance(
+                Locale.getDefault(), forceFormatWidth ? formatWidthHourAndMinute : FormatWidth.WIDE)
+                .formatMeasures(new Measure(0, MeasureUnit.MINUTE));
+    }
+
+    private String getReadableDuration(
+            Duration duration,
+            FormatWidth formatWidthHourAndMinute,
+            @StringRes int durationLessThanOneMinuteStringId) {
+        return getReadableDuration(
+                duration,
+                formatWidthHourAndMinute,
+                durationLessThanOneMinuteStringId,
+                /* forceFormatWidth= */ false);
+    }
+
+    private String getShorterReadableDuration(Duration duration) {
+        return getReadableDuration(
+                duration, FormatWidth.NARROW, R.string.shorter_duration_less_than_one_minute);
+    }
+
+    private String getText(long remainingTime) {
+        final Resources resources = getResources();
+        return (remainingTime <= 0) ?
+                resources.getString(R.string.app_in_grayscale) :
+                resources.getString(
+                        R.string.time_left_for_app,
+                        getShorterReadableDuration(Duration.ofMillis(remainingTime)));
     }
 
     public void openAppUsageSettings() {
@@ -98,12 +208,12 @@
     }
 
     private String getContentDescriptionForTask(
-            Task task, long appRemainingTimeMs, boolean isGroupLimit) {
-        return appRemainingTimeMs > 0 ?
+            Task task, long appUsageLimitTimeMs, long appRemainingTimeMs) {
+        return appUsageLimitTimeMs >= 0 ?
                 getResources().getString(
                         R.string.task_contents_description_with_remaining_time,
                         task.titleDescription,
-                        getText(appRemainingTimeMs, isGroupLimit)) :
+                        getText(appRemainingTimeMs)) :
                 task.titleDescription;
     }
 }
diff --git a/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java b/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java
index 8ec5361..a8205cd 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java
@@ -16,6 +16,7 @@
 package com.android.quickstep.views;
 
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
 
 import android.graphics.Canvas;
@@ -39,24 +40,44 @@
 public class LauncherLayoutListener extends AbstractFloatingView
         implements Insettable, LayoutListener {
 
+    public static LauncherLayoutListener resetAndGet(Launcher launcher) {
+        LauncherRecentsView lrv = launcher.getOverviewPanel();
+        LauncherLayoutListener listener = lrv.mLauncherLayoutListener;
+        if (listener.isOpen()) {
+            listener.close(false);
+        }
+        listener.setHandler(null);
+        return listener;
+    }
+
     private final Launcher mLauncher;
     private final Paint mPaint = new Paint();
     private WindowTransformSwipeHandler mHandler;
     private RectF mCurrentRect;
     private float mCornerRadius;
 
-    public LauncherLayoutListener(Launcher launcher) {
+    private boolean mWillNotDraw;
+
+    /**
+     * package private
+     */
+    LauncherLayoutListener(Launcher launcher) {
         super(launcher, null);
         mLauncher = launcher;
         mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
         setLayoutParams(new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+
+        mWillNotDraw = willNotDraw();
+        super.setWillNotDraw(false);
     }
 
     @Override
     public void update(boolean shouldFinish, boolean isLongSwipe, RectF currentRect,
-            float cornerRadius) {
-        if (shouldFinish) {
-            finish();
+                  float cornerRadius) {
+        if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            if (shouldFinish) {
+                finish();
+            }
             return;
         }
 
@@ -68,6 +89,12 @@
     }
 
     @Override
+    public void setWillNotDraw(boolean willNotDraw) {
+        // Prevent super call as that causes additional relayout.
+        mWillNotDraw = willNotDraw;
+    }
+
+    @Override
     public void setHandler(WindowTransformSwipeHandler handler) {
         mHandler = handler;
     }
@@ -124,6 +151,8 @@
 
     @Override
     protected void onDraw(Canvas canvas) {
-        canvas.drawRoundRect(mCurrentRect, mCornerRadius, mCornerRadius, mPaint);
+        if (!mWillNotDraw) {
+            canvas.drawRoundRect(mCurrentRect, mCornerRadius, mCornerRadius, mPaint);
+        }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 697bb4f..f8eced0 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -15,10 +15,12 @@
  */
 package com.android.quickstep.views;
 
-import static com.android.launcher3.LauncherAppTransitionManagerImpl.ALL_APPS_PROGRESS_OFF_SCREEN;
+import static com.android.launcher3.AbstractFloatingView.TYPE_QUICKSTEP_PREVIEW;
 import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.QuickstepAppTransitionManagerImpl.ALL_APPS_PROGRESS_OFF_SCREEN;
 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
@@ -32,6 +34,8 @@
 import android.view.View;
 import android.view.ViewDebug;
 
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.BaseActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
@@ -40,6 +44,7 @@
 import com.android.launcher3.views.ScrimView;
 import com.android.quickstep.OverviewInteractionState;
 import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.quickstep.util.ClipAnimationHelper.TransformParams;
 import com.android.quickstep.util.LayoutUtils;
 
 /**
@@ -62,9 +67,16 @@
                 }
             };
 
+    /**
+     * A ratio representing the view's relative placement within its padded space. For example, 0
+     * is top aligned and 0.5 is centered vertically.
+     */
     @ViewDebug.ExportedProperty(category = "launcher")
     private float mTranslationYFactor;
 
+    private final TransformParams mTransformParams = new TransformParams();
+    final LauncherLayoutListener mLauncherLayoutListener;
+
     public LauncherRecentsView(Context context) {
         this(context, null);
     }
@@ -76,11 +88,17 @@
     public LauncherRecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         setContentAlpha(0);
+        mLauncherLayoutListener = new LauncherLayoutListener(BaseActivity.fromContext(context));
     }
 
     @Override
     protected void startHome() {
-        mActivity.getStateManager().goToState(NORMAL);
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            takeScreenshotAndFinishRecentsAnimation(true,
+                    () -> mActivity.getStateManager().goToState(NORMAL));
+        } else {
+            mActivity.getStateManager().goToState(NORMAL);
+        }
     }
 
     @Override
@@ -92,6 +110,9 @@
     public void setTranslationYFactor(float translationFactor) {
         mTranslationYFactor = translationFactor;
         setTranslationY(computeTranslationYForFactor(mTranslationYFactor));
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            redrawLiveTile(false);
+        }
     }
 
     public float computeTranslationYForFactor(float translationYFactor) {
@@ -168,4 +189,45 @@
     public boolean shouldUseMultiWindowTaskSizeStrategy() {
         return mActivity.isInMultiWindowModeCompat();
     }
+
+    @Override
+    public void scrollTo(int x, int y) {
+        super.scrollTo(x, y);
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile) {
+            redrawLiveTile(true);
+        }
+    }
+
+    @Override
+    public void redrawLiveTile(boolean mightNeedToRefill) {
+        AbstractFloatingView layoutListener = AbstractFloatingView.getTopOpenViewWithType(
+                mActivity, TYPE_QUICKSTEP_PREVIEW);
+        if (layoutListener != null && layoutListener.isOpen()) {
+            return;
+        }
+        if (mRecentsAnimationWrapper == null || mClipAnimationHelper == null) {
+            return;
+        }
+        TaskView taskView = getRunningTaskView();
+        if (taskView != null) {
+            taskView.getThumbnail().getGlobalVisibleRect(mTempRect);
+            int offsetX = (int) (mTaskWidth * taskView.getScaleX() * getScaleX()
+                    - mTempRect.width());
+            int offsetY = (int) (mTaskHeight * taskView.getScaleY() * getScaleY()
+                    - mTempRect.height());
+            if (((mCurrentPage != 0) || mightNeedToRefill) && offsetX > 0) {
+                mTempRect.right += offsetX;
+            }
+            if (mightNeedToRefill && offsetY > 0) {
+                mTempRect.top -= offsetY;
+            }
+            mTempRectF.set(mTempRect);
+            mTransformParams.setCurrentRectAndTargetAlpha(mTempRectF, taskView.getAlpha())
+                    .setSyncTransactionApplier(mSyncTransactionApplier);
+            if (mRecentsAnimationWrapper.targetSet != null) {
+                mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targetSet,
+                        mTransformParams);
+            }
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 34b5748..5cbae65 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -17,14 +17,17 @@
 package com.android.quickstep.views;
 
 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
+import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.launcher3.uioverrides.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS;
+import static com.android.quickstep.util.ClipAnimationHelper.TransformParams;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
-import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
-
+import static com.android.quickstep.TouchInteractionService.EDGE_NAV_BAR;
 import android.animation.Animator;
 import android.animation.AnimatorSet;
 import android.animation.LayoutTransition;
@@ -40,6 +43,7 @@
 import android.graphics.Canvas;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.Handler;
@@ -60,11 +64,13 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.ListView;
+
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
+import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -75,8 +81,10 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.PendingAnimation;
 import com.android.launcher3.util.Themes;
+import com.android.launcher3.util.ViewPool;
 import com.android.quickstep.OverviewCallbacks;
 import com.android.quickstep.QuickScrubController;
+import com.android.quickstep.RecentsAnimationWrapper;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.TaskThumbnailCache;
 import com.android.quickstep.TaskUtils;
@@ -87,7 +95,10 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.BackgroundExecutor;
 import com.android.systemui.shared.system.PackageManagerWrapper;
+import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
 import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.WindowCallbacksCompat;
 
 import java.util.ArrayList;
 import java.util.function.Consumer;
@@ -97,7 +108,8 @@
  */
 @TargetApi(Build.VERSION_CODES.P)
 public abstract class RecentsView<T extends BaseActivity> extends PagedView implements Insettable,
-        TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback {
+        TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback,
+        InvariantDeviceProfile.OnIDPChangeListener {
 
     private static final String TAG = RecentsView.class.getSimpleName();
 
@@ -114,7 +126,14 @@
                 }
             };
 
-    private final Rect mTempRect = new Rect();
+    protected RecentsAnimationWrapper mRecentsAnimationWrapper;
+    protected ClipAnimationHelper mClipAnimationHelper;
+    protected SyncRtSurfaceTransactionApplierCompat mSyncTransactionApplier;
+    protected int mTaskWidth;
+    protected int mTaskHeight;
+    protected boolean mEnableDrawingLiveTile = false;
+    protected final Rect mTempRect = new Rect();
+    protected final RectF mTempRectF = new RectF();
 
     private static final int DISMISS_TASK_DURATION = 300;
     private static final int ADDITION_TASK_DURATION = 200;
@@ -136,6 +155,10 @@
     // Keeps track of the previously known visible tasks for purposes of loading/unloading task data
     private final SparseBooleanArray mHasVisibleTaskData = new SparseBooleanArray();
 
+    private final InvariantDeviceProfile mIdp;
+
+    private final ViewPool<TaskView> mTaskViewPool;
+
     /**
      * TODO: Call reloadIdNeeded in onTaskStackChanged.
      */
@@ -278,10 +301,15 @@
         mActivity = (T) BaseActivity.fromContext(context);
         mQuickScrubController = new QuickScrubController(mActivity, this);
         mModel = RecentsModel.INSTANCE.get(context);
+        mIdp = InvariantDeviceProfile.INSTANCE.get(context);
+
         mClearAllButton = (ClearAllButton) LayoutInflater.from(context)
                 .inflate(R.layout.overview_clear_all_button, this, false);
         mClearAllButton.setOnClickListener(this::dismissAllTasks);
 
+        mTaskViewPool = new ViewPool<>(context, this, R.layout.task, 20 /* max size */,
+                10 /* initial size */);
+
         mIsRtl = !Utilities.isRtl(getResources());
         setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
         mTaskTopMargin = getResources()
@@ -320,12 +348,23 @@
     }
 
     @Override
+    public void onIdpChanged(int changeFlags, InvariantDeviceProfile idp) {
+        if ((changeFlags & CHANGE_FLAG_ICON_PARAMS) == 0) {
+            return;
+        }
+        mModel.getIconCache().clear();
+        reset();
+    }
+
+    @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         updateTaskStackListenerState();
         mModel.getThumbnailCache().getHighResLoadingState().addCallback(this);
         mActivity.addMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
         ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
+        mSyncTransactionApplier = new SyncRtSurfaceTransactionApplierCompat(this);
+        mIdp.addOnChangeListener(this);
     }
 
     @Override
@@ -335,6 +374,8 @@
         mModel.getThumbnailCache().getHighResLoadingState().removeCallback(this);
         mActivity.removeMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
         ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
+        mSyncTransactionApplier = null;
+        mIdp.removeOnChangeListener(this);
     }
 
     @Override
@@ -349,6 +390,7 @@
                 mHasVisibleTaskData.delete(task.key.id);
                 taskView.onTaskListVisibilityChanged(false /* visible */);
             }
+            mTaskViewPool.recycle(taskView);
         }
     }
 
@@ -421,7 +463,8 @@
                         final boolean clearAllButtonDeadZoneConsumed =
                                 mClearAllButton.getAlpha() == 1
                                         && mClearAllButtonDeadZoneRect.contains(x, y);
-                        if (!clearAllButtonDeadZoneConsumed
+                        final boolean cameFromNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
+                        if (!clearAllButtonDeadZoneConsumed && !cameFromNavBar
                                 && !mTaskViewDeadZoneRect.contains(x + getScrollX(), y)) {
                             mTouchDownToStartHome = true;
                         }
@@ -451,10 +494,6 @@
 
         int oldChildCount = getChildCount();
 
-        // Ensure there are as many views as there are tasks in the stack (adding and trimming as
-        // necessary)
-        final LayoutInflater inflater = LayoutInflater.from(getContext());
-
         // Unload existing visible task data
         unloadVisibleTaskData();
 
@@ -467,7 +506,7 @@
                 removeView(mClearAllButton);
             }
             for (int i = getChildCount(); i < requiredTaskCount; i++) {
-                addView(inflater.inflate(R.layout.task, this, false));
+                addView(mTaskViewPool.getView());
             }
             while (getChildCount() > requiredTaskCount) {
                 removeView(getChildAt(getChildCount() - 1));
@@ -548,6 +587,8 @@
         mInsets.set(insets);
         DeviceProfile dp = mActivity.getDeviceProfile();
         getTaskSize(dp, mTempRect);
+        mTaskWidth = mTempRect.width();
+        mTaskHeight = mTempRect.height();
 
         // Keep this logic in sync with ActivityControlHelper.getTranslationYForQuickScrub.
         mTempRect.top -= mTaskTopMargin;
@@ -681,11 +722,15 @@
     protected abstract void startHome();
 
     public void reset() {
+        setRunningTaskViewShowScreenshot(false);
         mRunningTaskId = -1;
         mRunningTaskTileHidden = false;
         mIgnoreResetTaskId = -1;
         mTaskListChangeId = -1;
 
+        mRecentsAnimationWrapper = null;
+        mClipAnimationHelper = null;
+
         unloadVisibleTaskData();
         setCurrentPage(0);
 
@@ -712,8 +757,7 @@
     public void showTask(int runningTaskId) {
         if (getChildCount() == 0) {
             // Add an empty view for now until the task plan is loaded and applied
-            final TaskView taskView = (TaskView) LayoutInflater.from(getContext())
-                    .inflate(R.layout.task, this, false);
+            final TaskView taskView = mTaskViewPool.getView();
             addView(taskView);
             addView(mClearAllButton);
 
@@ -732,6 +776,11 @@
         return getTaskView(mRunningTaskId);
     }
 
+    public int getRunningTaskIndex() {
+        TaskView tv = getRunningTaskView();
+        return tv == null ? -1 : indexOfChild(tv);
+    }
+
     /**
      * Hides the tile associated with {@link #mRunningTaskId}
      */
@@ -752,17 +801,27 @@
 
         setRunningTaskIconScaledDown(false);
         setRunningTaskHidden(false);
+        setRunningTaskViewShowScreenshot(true);
         mRunningTaskId = runningTaskId;
+        setRunningTaskViewShowScreenshot(false);
         setRunningTaskIconScaledDown(runningTaskIconScaledDown);
         setRunningTaskHidden(runningTaskTileHidden);
 
-        TaskView tv = getRunningTaskView();
-        setCurrentPage(tv == null ? 0 : indexOfChild(tv));
+        setCurrentPage(getRunningTaskIndex());
 
         // Load the tasks (if the loading is already
         mTaskListChangeId = mModel.getTasks(this::applyLoadPlan);
     }
 
+    private void setRunningTaskViewShowScreenshot(boolean showScreenshot) {
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            TaskView runningTaskView = getRunningTaskView();
+            if (runningTaskView != null) {
+                runningTaskView.setShowScreenshot(showScreenshot);
+            }
+        }
+    }
+
     public void showNextTask() {
         TaskView runningTaskView = getRunningTaskView();
         if (runningTaskView == null) {
@@ -970,26 +1029,40 @@
         }
 
         mPendingAnimation = pendingAnimation;
-        mPendingAnimation.addEndListener((onEndListener) -> {
-           if (onEndListener.isSuccess) {
-               if (shouldRemoveTask) {
-                   removeTask(taskView.getTask(), draggedIndex, onEndListener, true);
-               }
-               int pageToSnapTo = mCurrentPage;
-               if (draggedIndex < pageToSnapTo || pageToSnapTo == (getTaskViewCount() - 1)) {
-                   pageToSnapTo -= 1;
-               }
-               removeView(taskView);
+        mPendingAnimation.addEndListener(new Consumer<PendingAnimation.OnEndListener>() {
+            @Override
+            public void accept(PendingAnimation.OnEndListener onEndListener) {
+                if (ENABLE_QUICKSTEP_LIVE_TILE.get() &&
+                        taskView.isRunningTask() && onEndListener.isSuccess) {
+                    finishRecentsAnimation(true /* toHome */, () -> onEnd(onEndListener));
+                } else {
+                    onEnd(onEndListener);
+                }
+            }
 
-               if (getTaskViewCount() == 0) {
-                   removeView(mClearAllButton);
-                   startHome();
-               } else {
-                   snapToPageImmediately(pageToSnapTo);
-               }
-           }
-           resetTaskVisuals();
-           mPendingAnimation = null;
+            private void onEnd(PendingAnimation.OnEndListener onEndListener) {
+                if (onEndListener.isSuccess) {
+                    if (shouldRemoveTask) {
+                        removeTask(taskView.getTask(), draggedIndex, onEndListener, true);
+                    }
+
+                    int pageToSnapTo = mCurrentPage;
+                    if (draggedIndex < pageToSnapTo ||
+                            pageToSnapTo == (getTaskViewCount() - 1)) {
+                        pageToSnapTo -= 1;
+                    }
+                    removeView(taskView);
+
+                    if (getTaskViewCount() == 0) {
+                        removeView(mClearAllButton);
+                        startHome();
+                    } else {
+                        snapToPageImmediately(pageToSnapTo);
+                    }
+                }
+                resetTaskVisuals();
+                mPendingAnimation = null;
+            }
         });
         return pendingAnimation;
     }
@@ -1337,20 +1410,38 @@
         ObjectAnimator drawableAnim =
                 ObjectAnimator.ofFloat(drawable, TaskViewDrawable.PROGRESS, 1, 0);
         drawableAnim.setInterpolator(LINEAR);
-        drawableAnim.addUpdateListener((animator) -> {
-            // Once we pass a certain threshold, update the sysui flags to match the target tasks'
-            // flags
-            mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW,
-                    animator.getAnimatedFraction() > UPDATE_SYSUI_FLAGS_THRESHOLD
-                            ? targetSysUiFlags
-                            : 0);
+        drawableAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            TransformParams mParams = new TransformParams();
 
-            // Passing the threshold from taskview to fullscreen app will vibrate
-            final boolean passed = animator.getAnimatedFraction() >= MIN_PROGRESS_FOR_OVERVIEW;
-            if (passed != passedOverviewThreshold[0]) {
-                passedOverviewThreshold[0] = passed;
-                performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
-                        HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+            @Override
+            public void onAnimationUpdate(ValueAnimator animator) {
+                // Once we pass a certain threshold, update the sysui flags to match the target
+                // tasks' flags
+                mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW,
+                        animator.getAnimatedFraction() > UPDATE_SYSUI_FLAGS_THRESHOLD
+                                ? targetSysUiFlags
+                                : 0);
+                if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+                    if (mRecentsAnimationWrapper.targetSet != null
+                            && drawable.getTaskView().isRunningTask()) {
+                        mParams.setProgress(1 - animator.getAnimatedFraction())
+                                .setSyncTransactionApplier(mSyncTransactionApplier)
+                                .setForLiveTile(true);
+                        drawable.getClipAnimationHelper().applyTransform(
+                                mRecentsAnimationWrapper.targetSet, mParams);
+                    } else {
+                        redrawLiveTile(true);
+                    }
+                }
+
+                // Passing the threshold from taskview to fullscreen app will vibrate
+                final boolean passed = animator.getAnimatedFraction() >=
+                        SUCCESS_TRANSITION_PROGRESS;
+                if (passed != passedOverviewThreshold[0]) {
+                    passedOverviewThreshold[0] = passed;
+                    performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
+                            HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+                }
             }
         });
 
@@ -1449,4 +1540,74 @@
     protected boolean isPageOrderFlipped() {
         return true;
     }
+
+    public void setEnableDrawingLiveTile(boolean enableDrawingLiveTile) {
+        mEnableDrawingLiveTile = enableDrawingLiveTile;
+    }
+
+    public void redrawLiveTile(boolean mightNeedToRefill) { }
+
+    public void setRecentsAnimationWrapper(RecentsAnimationWrapper recentsAnimationWrapper) {
+        mRecentsAnimationWrapper = recentsAnimationWrapper;
+    }
+
+    public void setClipAnimationHelper(ClipAnimationHelper clipAnimationHelper) {
+        mClipAnimationHelper = clipAnimationHelper;
+    }
+
+    public void finishRecentsAnimation(boolean toHome, Runnable onFinishComplete) {
+        if (mRecentsAnimationWrapper == null) {
+            if (onFinishComplete != null) {
+                onFinishComplete.run();
+            }
+            return;
+        }
+
+        mRecentsAnimationWrapper.finish(toHome, onFinishComplete);
+    }
+
+    public void takeScreenshotAndFinishRecentsAnimation(boolean toHome, Runnable onFinishComplete) {
+        if (mRecentsAnimationWrapper == null || getRunningTaskView() == null) {
+            if (onFinishComplete != null) {
+                onFinishComplete.run();
+            }
+            return;
+        }
+
+        RecentsAnimationControllerCompat controller = mRecentsAnimationWrapper.getController();
+        if (controller != null) {
+            // Update the screenshot of the task
+            ThumbnailData taskSnapshot = controller.screenshotTask(mRunningTaskId);
+            TaskView taskView = updateThumbnail(mRunningTaskId, taskSnapshot);
+            if (taskView != null) {
+                taskView.setShowScreenshot(true);
+                // Defer finishing the animation until the next launcher frame with the
+                // new thumbnail
+                new WindowCallbacksCompat(taskView) {
+
+                    // The number of frames to defer until we actually finish the animation
+                    private int mDeferFrameCount = 2;
+
+                    @Override
+                    public void onPostDraw(Canvas canvas) {
+                        if (mDeferFrameCount > 0) {
+                            mDeferFrameCount--;
+                            // Workaround, detach and reattach to invalidate the root node for
+                            // another draw
+                            detach();
+                            attach();
+                            taskView.invalidate();
+                            return;
+                        }
+
+                        detach();
+                        mRecentsAnimationWrapper.finish(toHome, () -> {
+                            onFinishComplete.run();
+                            mRunningTaskId = -1;
+                        });
+                    }
+                }.attach();
+            }
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index 667165b..bea646a 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -16,6 +16,7 @@
 
 package com.android.quickstep.views;
 
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.quickstep.views.TaskThumbnailView.DIM_ALPHA;
 
 import android.animation.Animator;
@@ -206,7 +207,13 @@
                 R.layout.task_view_menu_option, this, false);
         menuOption.setIconAndLabelFor(
                 menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text));
-        menuOptionView.setOnClickListener(onClickListener);
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            menuOptionView.setOnClickListener(
+                    view -> mTaskView.getRecentsView().takeScreenshotAndFinishRecentsAnimation(true,
+                            () -> onClickListener.onClick(view)));
+        } else {
+            menuOptionView.setOnClickListener(onClickListener);
+        }
         mOptionLayout.addView(menuOptionView);
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index c2403a3..8169d73 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -16,6 +16,7 @@
 
 package com.android.quickstep.views;
 
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN;
 
 import android.content.Context;
@@ -29,6 +30,8 @@
 import android.graphics.ColorMatrixColorFilter;
 import android.graphics.Matrix;
 import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.graphics.Shader;
 import android.util.AttributeSet;
@@ -76,6 +79,8 @@
     private final boolean mIsDarkTextTheme;
     private final Paint mPaint = new Paint();
     private final Paint mBackgroundPaint = new Paint();
+    private final Paint mClearPaint = new Paint();
+    private final Paint mDimmingPaintAfterClearing = new Paint();
 
     private final Matrix mMatrix = new Matrix();
 
@@ -102,15 +107,21 @@
     public TaskThumbnailView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         mCornerRadius = getResources().getDimension(R.dimen.task_corner_radius);
-        mOverlay = TaskOverlayFactory.get(context).createOverlay(this);
+        mOverlay = TaskOverlayFactory.INSTANCE.get(context).createOverlay(this);
         mPaint.setFilterBitmap(true);
         mBackgroundPaint.setColor(Color.WHITE);
+        mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+        mDimmingPaintAfterClearing.setColor(Color.BLACK);
         mActivity = BaseActivity.fromContext(context);
         mIsDarkTextTheme = Themes.getAttrBoolean(mActivity, R.attr.isWorkspaceDarkText);
     }
 
-    public void bind() {
+    public void bind(Task task) {
         mOverlay.reset();
+        mTask = task;
+        int color = task == null ? Color.BLACK : task.colorBackground | 0xFF000000;
+        mPaint.setColor(color);
+        mBackgroundPaint.setColor(color);
     }
 
     /**
@@ -118,10 +129,6 @@
      */
     public void setThumbnail(Task task, ThumbnailData thumbnailData) {
         mTask = task;
-        int color = task == null ? Color.BLACK : task.colorBackground | 0xFF000000;
-        mPaint.setColor(color);
-        mBackgroundPaint.setColor(color);
-
         if (thumbnailData != null && thumbnailData.thumbnail != null) {
             Bitmap bm = thumbnailData.thumbnail;
             bm.prepareToDraw();
@@ -213,6 +220,15 @@
 
     public void drawOnCanvas(Canvas canvas, float x, float y, float width, float height,
             float cornerRadius) {
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            if (mTask != null && getTaskView().isRunningTask() && !getTaskView().showScreenshot()) {
+                canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mClearPaint);
+                canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius,
+                        mDimmingPaintAfterClearing);
+                return;
+            }
+        }
+
         // Draw the background in all cases, except when the thumbnail data is opaque
         final boolean drawBackgroundOnly = mTask == null || mTask.isLocked || mBitmapShader == null
                 || mThumbnailData == null;
@@ -233,12 +249,17 @@
         }
     }
 
+    protected TaskView getTaskView() {
+        return (TaskView) getParent();
+    }
+
     private void updateThumbnailPaintFilter() {
         int mul = (int) ((1 - mDimAlpha * mDimAlphaMultiplier) * 255);
+        ColorFilter filter = getColorFilter(mul, mIsDarkTextTheme, mSaturation);
+        mBackgroundPaint.setColorFilter(filter);
+        mDimmingPaintAfterClearing.setAlpha(255 - mul);
         if (mBitmapShader != null) {
-            ColorFilter filter = getColorFilter(mul, mIsDarkTextTheme, mSaturation);
             mPaint.setColorFilter(filter);
-            mBackgroundPaint.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 617ecf8..63c6805 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -20,6 +20,7 @@
 import static com.android.launcher3.BaseActivity.fromContext;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -45,9 +46,12 @@
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.util.PendingAnimation;
+import com.android.launcher3.util.ViewPool.Reusable;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.TaskIconCache;
 import com.android.quickstep.TaskOverlayFactory;
@@ -66,7 +70,7 @@
 /**
  * A task in the Recents view.
  */
-public class TaskView extends FrameLayout implements PageCallbacks {
+public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
 
     private static final String TAG = TaskView.class.getSimpleName();
 
@@ -87,6 +91,7 @@
 
     public static final long SCALE_ICON_DURATION = 120;
     private static final long DIM_ANIM_DURATION = 700;
+    private static final long TASK_LAUNCH_ANIM_DURATION = 200;
 
     public static final Property<TaskView, Float> ZOOM_SCALE =
             new FloatProperty<TaskView>("zoomScale") {
@@ -118,7 +123,7 @@
             new FloatProperty<TaskView>("focusTransition") {
                 @Override
                 public void setValue(TaskView taskView, float v) {
-                    taskView.setIconAndDimTransitionProgress(v);
+                    taskView.setIconAndDimTransitionProgress(v, false /* invert */);
                 }
 
                 @Override
@@ -157,6 +162,8 @@
     private Animator mIconAndDimAnimator;
     private float mFocusTransitionProgress = 1;
 
+    private boolean mShowScreenshot;
+
     // The current background requests to load the task thumbnail and icon
     private TaskThumbnailCache.ThumbnailLoadRequest mThumbnailLoadRequest;
     private TaskIconCache.IconLoadRequest mIconLoadRequest;
@@ -175,7 +182,15 @@
             if (getTask() == null) {
                 return;
             }
-            launchTask(true /* animate */);
+            if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+                if (isRunningTask()) {
+                    createLaunchAnimationForRunningTask().start();
+                } else {
+                    launchTask(true /* animate */);
+                }
+            } else {
+                launchTask(true /* animate */);
+            }
 
             fromContext(context).getUserEventDispatcher().logTaskLaunchOrDismiss(
                     Touch.TAP, Direction.NONE, getRecentsView().indexOfChild(this),
@@ -199,7 +214,7 @@
      */
     public void bind(Task task) {
         mTask = task;
-        mSnapshotView.bind();
+        mSnapshotView.bind(task);
     }
 
     public Task getTask() {
@@ -218,6 +233,19 @@
         return mSnapshotView.getTaskOverlay();
     }
 
+    public AnimatorPlaybackController createLaunchAnimationForRunningTask() {
+        final PendingAnimation pendingAnimation =
+                getRecentsView().createTaskLauncherAnimation(this, TASK_LAUNCH_ANIM_DURATION);
+        pendingAnimation.anim.setInterpolator(Interpolators.ZOOM_IN);
+        AnimatorPlaybackController currentAnimation = AnimatorPlaybackController
+                .wrap(pendingAnimation.anim, TASK_LAUNCH_ANIM_DURATION, null);
+        currentAnimation.setEndAction(() -> {
+            pendingAnimation.finish(true, Touch.SWIPE);
+            launchTask(false);
+        });
+        return currentAnimation;
+    }
+
     public void launchTask(boolean animate) {
         launchTask(animate, (result) -> {
             if (!result) {
@@ -228,6 +256,21 @@
 
     public void launchTask(boolean animate, Consumer<Boolean> resultCallback,
             Handler resultCallbackHandler) {
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            if (isRunningTask()) {
+                getRecentsView().finishRecentsAnimation(false,
+                        () -> resultCallbackHandler.post(() -> resultCallback.accept(true)));
+            } else {
+                getRecentsView().takeScreenshotAndFinishRecentsAnimation(true,
+                        () -> launchTaskInternal(animate, resultCallback, resultCallbackHandler));
+            }
+        } else {
+            launchTaskInternal(animate, resultCallback, resultCallbackHandler);
+        }
+    }
+
+    private void launchTaskInternal(boolean animate, Consumer<Boolean> resultCallback,
+            Handler resultCallbackHandler) {
         if (mTask != null) {
             final ActivityOptions opts;
             if (animate) {
@@ -315,11 +358,17 @@
         }
     }
 
-    private void setIconAndDimTransitionProgress(float progress) {
+    private void setIconAndDimTransitionProgress(float progress, boolean invert) {
+        if (invert) {
+            progress = 1 - progress;
+        }
         mFocusTransitionProgress = progress;
         mSnapshotView.setDimAlphaMultipler(progress);
-        float scale = FAST_OUT_SLOW_IN.getInterpolation(Utilities.boundToRange(
-                progress * DIM_ANIM_DURATION / SCALE_ICON_DURATION, 0, 1));
+        float iconScalePercentage = (float) SCALE_ICON_DURATION / DIM_ANIM_DURATION;
+        float lowerClamp = invert ? 1f - iconScalePercentage : 0;
+        float upperClamp = invert ? 1 : iconScalePercentage;
+        float scale = Interpolators.clampToProgress(FAST_OUT_SLOW_IN, lowerClamp, upperClamp)
+                .getInterpolation(progress);
         mIconView.setScaleX(scale);
         mIconView.setScaleY(scale);
     }
@@ -340,19 +389,27 @@
     }
 
     protected void setIconScaleAndDim(float iconScale) {
+        setIconScaleAndDim(iconScale, false);
+    }
+
+    private void setIconScaleAndDim(float iconScale, boolean invert) {
         if (mIconAndDimAnimator != null) {
             mIconAndDimAnimator.cancel();
         }
-        setIconAndDimTransitionProgress(iconScale);
+        setIconAndDimTransitionProgress(iconScale, invert);
     }
 
-    public void resetVisualProperties() {
+    private void resetViewTransforms() {
         setZoomScale(1);
         setTranslationX(0f);
         setTranslationY(0f);
         setTranslationZ(0);
         setAlpha(1f);
         setIconScaleAndDim(1);
+    }
+
+    public void resetVisualProperties() {
+        resetViewTransforms();
         if (!getRecentsView().getQuickScrubController().isQuickSwitch()) {
             // Reset full screen progress unless we are doing back to back quick switch.
             setFullscreenProgress(0);
@@ -360,6 +417,12 @@
     }
 
     @Override
+    public void onRecycle() {
+        resetViewTransforms();
+        setFullscreenProgress(0);
+    }
+
+    @Override
     public void onPageScroll(ScrollState scrollState) {
         float curveInterpolation =
                 CURVE_INTERPOLATOR.getInterpolation(scrollState.linearInterpolation);
@@ -500,7 +563,7 @@
         return super.performAccessibilityAction(action, arguments);
     }
 
-    private RecentsView getRecentsView() {
+    public RecentsView getRecentsView() {
         return (RecentsView) getParent();
     }
 
@@ -523,7 +586,8 @@
         }
         mFullscreenProgress = progress;
         boolean isFullscreen = mFullscreenProgress > 0;
-        mIconView.setVisibility(isFullscreen ? INVISIBLE : VISIBLE);
+        setIconScaleAndDim(progress, true /* invert */);
+        mIconView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE);
         setClipChildren(!isFullscreen);
         setClipToPadding(!isFullscreen);
         getThumbnail().invalidate();
@@ -532,4 +596,19 @@
     public float getFullscreenProgress() {
         return mFullscreenProgress;
     }
+
+    public boolean isRunningTask() {
+        return this == getRecentsView().getRunningTaskView();
+    }
+
+    public void setShowScreenshot(boolean showScreenshot) {
+        mShowScreenshot = showScreenshot;
+    }
+
+    public boolean showScreenshot() {
+        if (!isRunningTask()) {
+            return true;
+        }
+        return mShowScreenshot;
+    }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTests.java b/quickstep/tests/src/com/android/quickstep/TaplTests.java
index 6a1123e..347b7ac 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTests.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTests.java
@@ -104,7 +104,7 @@
 
         clearLauncherData();
 
-        mDevice.pressHome();
+        mLauncher.pressHome();
         waitForState("Launcher internal state didn't switch to Home", LauncherState.NORMAL);
         waitForResumed("Launcher internal state is still Background");
     }
@@ -424,7 +424,7 @@
         executeOnLauncher(launcher -> assertTrue("Flinging backward didn't scroll widgets",
                 getWidgetsScroll(launcher) < flingForwardY));
 
-        mDevice.pressHome();
+        mLauncher.pressHome();
         waitForLauncherCondition("Widgets were not closed",
                 launcher -> getWidgetsView(launcher) == null);
     }
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index ab62107..573da2d 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -40,11 +40,11 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Der blev ikke fundet nogen apps, som matcher \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Søg efter flere apps"</string>
     <string name="label_application" msgid="8531721983832654978">"App"</string>
-    <string name="notifications_header" msgid="1404149926117359025">"Underretninger"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Notifikationer"</string>
     <string name="long_press_shortcut_to_add" msgid="4524750017792716791">"Hold en genvej nede for at samle den op."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="3327314059613154633">"Tryk to gange, og hold en genvej nede for at samle den op eller bruge tilpassede handlinger."</string>
     <string name="out_of_space" msgid="4691004494942118364">"Der er ikke mere plads på denne startskærm."</string>
-    <string name="hotseat_out_of_space" msgid="7448809638125333693">"Der er ikke mere plads i bakken Foretrukne"</string>
+    <string name="hotseat_out_of_space" msgid="7448809638125333693">"Der er ikke mere plads i bakken Favoritter"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Liste med apps"</string>
     <string name="all_apps_button_personal_label" msgid="1315764287305224468">"Liste over personlige apps"</string>
     <string name="all_apps_button_work_label" msgid="7270707118948892488">"Liste over apps til arbejdet"</string>
@@ -66,8 +66,8 @@
     <string name="folder_hint_text" msgid="6617836969016293992">"Unavngiven mappe"</string>
     <string name="disabled_app_label" msgid="6673129024321402780">"<xliff:g id="APP_NAME">%1$s</xliff:g> er deaktiveret"</string>
     <plurals name="dotted_app_label" formatted="false" msgid="5194538107138265416">
-      <item quantity="one"><xliff:g id="APP_NAME_2">%1$s</xliff:g>, har <xliff:g id="NOTIFICATION_COUNT_3">%2$d</xliff:g> underretning</item>
-      <item quantity="other"><xliff:g id="APP_NAME_2">%1$s</xliff:g>, har <xliff:g id="NOTIFICATION_COUNT_3">%2$d</xliff:g> underretninger</item>
+      <item quantity="one"><xliff:g id="APP_NAME_2">%1$s</xliff:g>, har <xliff:g id="NOTIFICATION_COUNT_3">%2$d</xliff:g> notifikation</item>
+      <item quantity="other"><xliff:g id="APP_NAME_2">%1$s</xliff:g>, har <xliff:g id="NOTIFICATION_COUNT_3">%2$d</xliff:g> notifikationer</item>
     </plurals>
     <string name="default_scroll_format" msgid="7475544710230993317">"Side %1$d ud af %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Startskærm %1$d ud af %2$d"</string>
@@ -84,13 +84,13 @@
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Deaktiveret af din administrator"</string>
     <string name="allow_rotation_title" msgid="7728578836261442095">"Tillad rotation af startskærmen"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Når telefonen roteres"</string>
-    <string name="notification_dots_title" msgid="9062440428204120317">"Underretningscirkler"</string>
+    <string name="notification_dots_title" msgid="9062440428204120317">"Notifikationscirkler"</string>
     <string name="notification_dots_desc_on" msgid="1679848116452218908">"Til"</string>
     <string name="notification_dots_desc_off" msgid="1760796511504341095">"Fra"</string>
-    <string name="title_missing_notification_access" msgid="7503287056163941064">"Kræver adgang til underretninger"</string>
-    <string name="msg_missing_notification_access" msgid="281113995110910548">"Hvis du vil se underretningscirkler, skal du aktivere appunderretninger for <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="title_missing_notification_access" msgid="7503287056163941064">"Kræver adgang til notifikationer"</string>
+    <string name="msg_missing_notification_access" msgid="281113995110910548">"Hvis du vil se notifikationscirkler, skal du aktivere appnotifikationer for <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"Skift indstillinger"</string>
-    <string name="notification_dots_service_title" msgid="4284221181793592871">"Vis underretningscirkler"</string>
+    <string name="notification_dots_service_title" msgid="4284221181793592871">"Vis notifikationscirkler"</string>
     <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Føj ikon til startskærmen"</string>
     <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"For nye apps"</string>
     <string name="package_state_unknown" msgid="7592128424511031410">"Ukendt"</string>
@@ -126,16 +126,16 @@
     <string name="action_decrease_height" msgid="282377193880900022">"Reducer højden"</string>
     <string name="widget_resized" msgid="9130327887929620">"Størrelsen for widgetten er ændret til bredde <xliff:g id="NUMBER_0">%1$s</xliff:g> og højde <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
     <string name="action_deep_shortcut" msgid="2864038805849372848">"Genveje"</string>
-    <string name="shortcuts_menu_with_notifications_description" msgid="2676582286544232849">"Genveje og underretninger"</string>
+    <string name="shortcuts_menu_with_notifications_description" msgid="2676582286544232849">"Genveje og notifikationer"</string>
     <string name="action_dismiss_notification" msgid="5909461085055959187">"Afvis"</string>
-    <string name="notification_dismissed" msgid="6002233469409822874">"Underretningen blev afvist"</string>
+    <string name="notification_dismissed" msgid="6002233469409822874">"Notifikationen blev afvist"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Personlige"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Arbejde"</string>
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Arbejdsprofil"</string>
     <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Find arbejdsapps her"</string>
     <string name="bottom_work_tab_user_education_body" msgid="2818107472360579152">"Alle arbejdsapps har et badge og beskyttes af din organisation. Flyt apps til din startskærm, så du nemmere kan få adgang til dem."</string>
     <string name="work_mode_on_label" msgid="4781128097185272916">"Administreret af din organisation"</string>
-    <string name="work_mode_off_label" msgid="3194894777601421047">"Underretninger og apps er slået fra"</string>
+    <string name="work_mode_off_label" msgid="3194894777601421047">"Notifikationer og apps er slået fra"</string>
     <string name="bottom_work_tab_user_education_close_button" msgid="4224492243977802135">"Luk"</string>
     <string name="bottom_work_tab_user_education_closed" msgid="1098340939861869465">"Lukket"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Mislykket: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 9d0ad45..d26661d 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -91,7 +91,7 @@
     <string name="msg_missing_notification_access" msgid="281113995110910548">"Um dir Benachrichtigungspunkte anzeigen zu lassen, aktiviere die Benachrichtigungen für die App \"<xliff:g id="NAME">%1$s</xliff:g>\""</string>
     <string name="title_change_settings" msgid="1376365968844349552">"Einstellungen ändern"</string>
     <string name="notification_dots_service_title" msgid="4284221181793592871">"App-Benachrichtigungspunkte anzeigen"</string>
-    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Symbol zu Startbildschirm hinzufügen"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Symbol zum Startbildschirm hinzufügen"</string>
     <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Für neue Apps"</string>
     <string name="package_state_unknown" msgid="7592128424511031410">"Unbekannt"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Entfernen"</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index f93de69..253c4a2 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -39,7 +39,7 @@
     <string name="all_apps_loading_message" msgid="5813968043155271636">"अॅप्स लोड करत आहे…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" शी जुळणारे कोणतेही अॅप्स आढळले नाहीत"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"अधिक अॅप्स शोधा"</string>
-    <string name="label_application" msgid="8531721983832654978">"अॅप"</string>
+    <string name="label_application" msgid="8531721983832654978">"ॲप"</string>
     <string name="notifications_header" msgid="1404149926117359025">"सूचना"</string>
     <string name="long_press_shortcut_to_add" msgid="4524750017792716791">"शॉर्टकट निवडण्यासाठी स्पर्श करा आणि धरून ठेवा."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="3327314059613154633">"शॉर्टकट निवडण्यासाठी किंवा कस्टम क्रिया वापरण्यासाठी दोनदा टॅप करा आणि धरून ठेवा."</string>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 8c4dd1e..53877ff 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -33,9 +33,11 @@
     <attr name="workspaceKeyShadowColor" format="color" />
     <attr name="workspaceStatusBarScrim" format="reference" />
     <attr name="widgetsTheme" format="reference" />
-    <attr name="folderDotColor" format="color" />
     <attr name="loadingIconColor" format="color" />
 
+    <attr name="folderDotColor" format="color" />
+    <attr name="folderIconRadius" format="float" />
+
     <!-- BubbleTextView specific attributes. -->
     <declare-styleable name="BubbleTextView">
         <attr name="layoutHorizontal" format="boolean" />
diff --git a/res/values/strings.xml b/res/values/strings.xml
index a1089c6..51350c0 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -336,6 +336,4 @@
 
     <!-- Failed action error message: e.g. Failed: Pause -->
     <string name="remote_action_failed">Failed: <xliff:g id="what" example="Pause">%1$s</xliff:g></string>
-
-    <string name="task_contents_description_with_remaining_time" translatable="false"><xliff:g id="task_description" example="GMail">%1$s</xliff:g>, <xliff:g id="remaining_time" example="7 minutes">%2$s</xliff:g></string>
 </resources>
diff --git a/res/xml/folder_shapes.xml b/res/xml/folder_shapes.xml
new file mode 100644
index 0000000..e60d333
--- /dev/null
+++ b/res/xml/folder_shapes.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2019 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.
+-->
+<shapes xmlns:launcher="http://schemas.android.com/apk/res-auto" >
+
+    <Circle launcher:folderIconRadius="1" />
+
+    <!-- Default icon for AOSP -->
+    <RoundedSquare launcher:folderIconRadius="0.16" />
+
+    <!-- Rounded icon from RRO -->
+    <RoundedSquare launcher:folderIconRadius="0.6" />
+
+    <!-- Square icon -->
+    <RoundedSquare launcher:folderIconRadius="0" />
+
+    <TearDrop launcher:folderIconRadius="0.3" />
+    <Squircle launcher:folderIconRadius="0.2" />
+
+</shapes>
\ No newline at end of file
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 1b953d4..5137774 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -226,7 +226,7 @@
     /**
      * Used to set the override visibility state, used only to handle the transition home with the
      * recents animation.
-     * @see LauncherAppTransitionManagerImpl#getWallpaperOpenRunner()
+     * @see QuickstepAppTransitionManagerImpl#getWallpaperOpenRunner()
      */
     public void addForceInvisibleFlag(@InvisibilityFlags int flag) {
         mForceInvisible |= flag;
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 5c842a5..dafd5bb 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -30,12 +30,16 @@
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.Log;
+import android.util.SparseArray;
+import android.util.TypedValue;
 import android.util.Xml;
 import android.view.Display;
 import android.view.WindowManager;
 
 import com.android.launcher3.util.ConfigMonitor;
+import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.Themes;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -44,6 +48,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
 public class InvariantDeviceProfile {
@@ -91,6 +96,8 @@
     public int fillResIconDpi;
     public float iconTextSize;
 
+    private SparseArray<TypedValue> mExtraAttrs;
+
     /**
      * Number of icons inside the hotseat area.
      */
@@ -122,6 +129,7 @@
         numHotseatIcons = p.numHotseatIcons;
         defaultLayoutId = p.defaultLayoutId;
         demoModeLayoutId = p.demoModeLayoutId;
+        mExtraAttrs = p.mExtraAttrs;
     }
 
     @TargetApi(23)
@@ -131,6 +139,13 @@
                 APPLY_CONFIG_AT_RUNTIME.get() ? this::onConfigChanged : this::killProcess);
     }
 
+    public InvariantDeviceProfile(Context context, String gridName) {
+        String newName = initGrid(context, gridName);
+        if (newName == null || !newName.equals(gridName)) {
+            throw new IllegalArgumentException("Unknown grid name");
+        }
+    }
+
     /**
      * Retrieve system defined or RRO overriden icon shape.
      */
@@ -142,7 +157,7 @@
         return context.getResources().getString(CONFIG_ICON_MASK_RES_ID);
     }
 
-    private void initGrid(Context context, String gridName) {
+    private String initGrid(Context context, String gridName) {
         WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
         Display display = wm.getDefaultDisplay();
         DisplayMetrics dm = new DisplayMetrics();
@@ -171,6 +186,8 @@
         demoModeLayoutId = closestProfile.demoModeLayoutId;
         numFolderRows = closestProfile.numFolderRows;
         numFolderColumns = closestProfile.numFolderColumns;
+        mExtraAttrs = closestProfile.extraAttrs;
+
         if (!closestProfile.name.equals(gridName)) {
             Utilities.getPrefs(context).edit()
                     .putString(KEY_IDP_GRID_NAME, closestProfile.name).apply();
@@ -208,19 +225,28 @@
         } else {
             defaultWallpaperSize = new Point(Math.max(smallSide * 2, largeSide), largeSide);
         }
+        return closestProfile.name;
+    }
+
+    @Nullable
+    public TypedValue getAttrValue(int attr) {
+        return mExtraAttrs == null ? null : mExtraAttrs.get(attr);
     }
 
     public void addOnChangeListener(OnIDPChangeListener listener) {
         mChangeListeners.add(listener);
     }
 
+    public void removeOnChangeListener(OnIDPChangeListener listener) {
+        mChangeListeners.remove(listener);
+    }
+
     private void killProcess(Context context) {
         Log.e("ConfigMonitor", "restarting launcher");
         android.os.Process.killProcess(android.os.Process.myPid());
     }
 
     public void verifyConfigChangedInBackground(final Context context) {
-
         String savedIconMaskPath = getDevicePrefs(context).getString(KEY_ICON_PATH_REF, "");
         // Good place to check if grid size changed in themepicker when launcher was dead.
         if (savedIconMaskPath.isEmpty()) {
@@ -233,6 +259,12 @@
         }
     }
 
+    public void setCurrentGrid(Context context, String gridName) {
+        Context appContext = context.getApplicationContext();
+        Utilities.getPrefs(appContext).edit().putString(KEY_IDP_GRID_NAME, gridName).apply();
+        new MainThreadExecutor().execute(() -> onConfigChanged(appContext));
+    }
+
     private void onConfigChanged(Context context) {
         // Config changes, what shall we do?
         InvariantDeviceProfile oldProfile = new InvariantDeviceProfile(this);
@@ -273,7 +305,8 @@
             int type;
             while (((type = parser.next()) != XmlPullParser.END_TAG ||
                     parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
-                if ((type == XmlPullParser.START_TAG) && "grid-option".equals(parser.getName())) {
+                if ((type == XmlPullParser.START_TAG)
+                        && GridOption.TAG_NAME.equals(parser.getName())) {
 
                     GridOption gridOption = new GridOption(context, Xml.asAttributeSet(parser));
                     final int displayDepth = parser.getDepth();
@@ -422,11 +455,13 @@
     }
 
 
-    private static final class GridOption {
+    public static final class GridOption {
 
-        private final String name;
-        private final int numRows;
-        private final int numColumns;
+        public static final String TAG_NAME = "grid-option";
+
+        public final String name;
+        public final int numRows;
+        public final int numColumns;
 
         private final int numFolderRows;
         private final int numFolderColumns;
@@ -436,7 +471,9 @@
         private final int defaultLayoutId;
         private final int demoModeLayoutId;
 
-        GridOption(Context context, AttributeSet attrs) {
+        private final SparseArray<TypedValue> extraAttrs;
+
+        public GridOption(Context context, AttributeSet attrs) {
             TypedArray a = context.obtainStyledAttributes(
                     attrs, R.styleable.GridDisplayOption);
             name = a.getString(R.styleable.GridDisplayOption_name);
@@ -454,6 +491,9 @@
             numFolderColumns = a.getInt(
                     R.styleable.GridDisplayOption_numFolderColumns, numColumns);
             a.recycle();
+
+            extraAttrs = Themes.createValueMap(context, attrs,
+                    IntArray.wrap(R.styleable.GridDisplayOption));
         }
     }
 
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index f09b6e8..b8b1181 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -189,6 +189,8 @@
     // Type: SparseArray<Parcelable>
     private static final String RUNTIME_STATE_WIDGET_PANEL = "launcher.widget_panel";
     public static final String ON_CREATE_EVT = "Launcher.onCreate";
+    private static final String ON_START_EVT = "Launcher.onStart";
+    private static final String ON_RESUME_EVT = "Launcher.onResume";
 
     private LauncherStateManager mStateManager;
 
@@ -762,12 +764,14 @@
 
     @Override
     protected void onStart() {
+        RaceConditionTracker.onEvent(ON_START_EVT, ENTER);
         super.onStart();
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onStart();
         }
         mAppWidgetHost.setListenIfResumed(true);
         NotificationListener.setNotificationsChangedListener(mPopupDataProvider);
+        RaceConditionTracker.onEvent(ON_START_EVT, EXIT);
     }
 
     private void logOnDelayedResume() {
@@ -780,6 +784,7 @@
 
     @Override
     protected void onResume() {
+        RaceConditionTracker.onEvent(ON_RESUME_EVT, ENTER);
         TraceHelper.beginSection("ON_RESUME");
         super.onResume();
         TraceHelper.partitionSection("ON_RESUME", "superCall");
@@ -802,6 +807,7 @@
         UiFactory.onLauncherStateOrResumeChanged(this);
 
         TraceHelper.endSection("ON_RESUME");
+        RaceConditionTracker.onEvent(ON_RESUME_EVT, EXIT);
     }
 
     @Override
@@ -1377,6 +1383,11 @@
     }
 
     private void setWorkspaceLoading(boolean value) {
+        if (com.android.launcher3.Utilities.IS_RUNNING_IN_TEST_HARNESS
+                && com.android.launcher3.Utilities.IS_DEBUG_DEVICE) {
+            android.util.Log.d("b/117332845", "setWorkspaceLoading " + value + " @ " +
+                    android.util.Log.getStackTraceString(new Throwable()));
+        }
         mWorkspaceLoading = value;
     }
 
diff --git a/src/com/android/launcher3/MainProcessInitializer.java b/src/com/android/launcher3/MainProcessInitializer.java
index 9692d73..93df025 100644
--- a/src/com/android/launcher3/MainProcessInitializer.java
+++ b/src/com/android/launcher3/MainProcessInitializer.java
@@ -38,6 +38,6 @@
         FileLog.setDir(context.getApplicationContext().getFilesDir());
         FeatureFlags.initialize(context);
         SessionCommitReceiver.applyDefaultUserPrefs(context);
-        FolderShape.init();
+        FolderShape.init(context);
     }
 }
diff --git a/src/com/android/launcher3/ProgressInterface.java b/src/com/android/launcher3/ProgressInterface.java
new file mode 100644
index 0000000..663d8ba
--- /dev/null
+++ b/src/com/android/launcher3/ProgressInterface.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2019 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;
+
+/**
+ * Progress is defined as a value with range [0, 1], and is specific to each implementor.
+ * It is used when there is a transition from one state of the UI to another.
+ */
+public interface ProgressInterface {
+    void setProgress(float progress);
+    float getProgress();
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index ffbf34c..e8e93fe 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -1,7 +1,6 @@
 package com.android.launcher3.allapps;
 
 import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT;
-import static com.android.launcher3.LauncherState.ALL_APPS_HEADER;
 import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherState.VERTICAL_SWIPE_INDICATOR;
@@ -15,9 +14,7 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
 import android.util.Property;
-import android.view.View;
 import android.view.animation.Interpolator;
 
 import com.android.launcher3.DeviceProfile;
@@ -26,13 +23,17 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.LauncherStateManager.StateHandler;
+import com.android.launcher3.ProgressInterface;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.anim.SpringObjectAnimator;
 import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ScrimView;
 
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+
 /**
  * Handles AllApps view transition.
  * 1) Slides all apps view using direct manipulation
@@ -43,7 +44,8 @@
  * If release velocity < THRES1, snap according to either top or bottom depending on whether it's
  * closer to top or closer to the page indicator.
  */
-public class AllAppsTransitionController implements StateHandler, OnDeviceProfileChangeListener {
+public class AllAppsTransitionController implements StateHandler, OnDeviceProfileChangeListener,
+        ProgressInterface {
 
     public static final Property<AllAppsTransitionController, Float> ALL_APPS_PROGRESS =
             new Property<AllAppsTransitionController, Float>(Float.class, "allAppsProgress") {
@@ -59,6 +61,19 @@
         }
     };
 
+    public static final FloatPropertyCompat<AllAppsTransitionController> ALL_APPS_PROGRESS_SPRING
+            = new FloatPropertyCompat<AllAppsTransitionController>("allAppsProgressSpring") {
+        @Override
+        public float getValue(AllAppsTransitionController controller) {
+            return controller.mProgress;
+        }
+
+        @Override
+        public void setValue(AllAppsTransitionController controller, float progress) {
+            controller.setProgress(progress);
+        }
+    };
+
     private AllAppsContainerView mAppsView;
     private ScrimView mScrimView;
 
@@ -112,6 +127,7 @@
      * @see #setState(LauncherState)
      * @see #setStateWithAnimation(LauncherState, AnimatorSetBuilder, AnimationConfig)
      */
+    @Override
     public void setProgress(float progress) {
         mProgress = progress;
         mScrimView.setProgress(progress);
@@ -136,6 +152,7 @@
         }
     }
 
+    @Override
     public float getProgress() {
         return mProgress;
     }
@@ -174,8 +191,8 @@
         Interpolator interpolator = config.userControlled ? LINEAR : toState == OVERVIEW
                 ? builder.getInterpolator(ANIM_OVERVIEW_SCALE, FAST_OUT_SLOW_IN)
                 : FAST_OUT_SLOW_IN;
-        ObjectAnimator anim =
-                ObjectAnimator.ofFloat(this, ALL_APPS_PROGRESS, mProgress, targetProgress);
+        Animator anim = new SpringObjectAnimator<>(this, ALL_APPS_PROGRESS_SPRING,
+                "allAppsSpringFromAATC", 1f / mShiftRange, mProgress, targetProgress);
         anim.setDuration(config.duration);
         anim.setInterpolator(builder.getInterpolator(ANIM_VERTICAL_PROGRESS, interpolator));
         anim.addListener(getProgressAnimatorListener());
diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
index 819c843..62f59e4 100644
--- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.anim;
 
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
 
 import android.animation.Animator;
 import android.animation.Animator.AnimatorListener;
@@ -23,10 +24,16 @@
 import android.animation.AnimatorSet;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
+import android.util.Log;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
+
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringAnimation;
 
 /**
  * Helper class to control the playback of an {@link AnimatorSet}, with custom interpolators
@@ -37,6 +44,9 @@
  */
 public abstract class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateListener {
 
+    private static final String TAG = "AnimatorPlaybackCtrler";
+    private static boolean DEBUG = false;
+
     public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration) {
         return wrap(anim, duration, null);
     }
@@ -60,6 +70,7 @@
     private final long mDuration;
 
     protected final AnimatorSet mAnim;
+    private Set<SpringAnimation> mSprings;
 
     protected float mCurrentFraction;
     private Runnable mEndAction;
@@ -67,6 +78,9 @@
     protected boolean mTargetCancelled = false;
     protected Runnable mOnCancelRunnable;
 
+    private OnAnimationEndDispatcher mEndListener;
+    private DynamicAnimation.OnAnimationEndListener mSpringEndListener;
+
     protected AnimatorPlaybackController(AnimatorSet anim, long duration,
             Runnable onCancelRunnable) {
         mAnim = anim;
@@ -75,7 +89,8 @@
 
         mAnimationPlayer = ValueAnimator.ofFloat(0, 1);
         mAnimationPlayer.setInterpolator(LINEAR);
-        mAnimationPlayer.addListener(new OnAnimationEndDispatcher());
+        mEndListener = new OnAnimationEndDispatcher();
+        mAnimationPlayer.addListener(mEndListener);
         mAnimationPlayer.addUpdateListener(this);
 
         mAnim.addListener(new AnimatorListenerAdapter() {
@@ -99,6 +114,15 @@
                 mTargetCancelled = false;
             }
         });
+
+        mSprings = new HashSet<>();
+        mSpringEndListener = (animation, canceled, value, velocity1) -> {
+            if (canceled) {
+                mEndListener.onAnimationCancel(mAnimationPlayer);
+            } else {
+                mEndListener.onAnimationEnd(mAnimationPlayer);
+            }
+        };
     }
 
     public AnimatorSet getTarget() {
@@ -180,6 +204,29 @@
         }
     }
 
+    /**
+     * Starts playback and sets the spring.
+     */
+    public void dispatchOnStartWithVelocity(float end, float velocity) {
+        if (!QUICKSTEP_SPRINGS.get()) {
+            dispatchOnStart();
+            return;
+        }
+
+        if (DEBUG) Log.d(TAG, "dispatchOnStartWithVelocity#end=" + end + ", velocity=" + velocity);
+
+        for (Animator a : mAnim.getChildAnimations()) {
+            if (a instanceof SpringObjectAnimator) {
+                if (DEBUG) Log.d(TAG, "Found springAnimator=" + a);
+                SpringObjectAnimator springAnimator = (SpringObjectAnimator) a;
+                mSprings.add(springAnimator.getSpring());
+                springAnimator.startSpring(end, velocity, mSpringEndListener);
+            }
+        }
+
+        dispatchOnStart();
+    }
+
     public void dispatchOnStart() {
         dispatchOnStartRecursively(mAnim);
     }
@@ -282,6 +329,18 @@
         }
     }
 
+    private boolean isAnySpringRunning() {
+        for (SpringAnimation spring : mSprings) {
+            if (spring.isRunning()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Only dispatches the on end actions once the animator and all springs have completed running.
+     */
     private class OnAnimationEndDispatcher extends AnimationSuccessListener {
 
         @Override
@@ -291,9 +350,12 @@
 
         @Override
         public void onAnimationSuccess(Animator animator) {
-            dispatchOnEndRecursively(mAnim);
-            if (mEndAction != null) {
-                mEndAction.run();
+            // We wait for the spring (if any) to finish running before completing the end callback.
+            if (mSprings.isEmpty() || !isAnySpringRunning()) {
+                dispatchOnEndRecursively(mAnim);
+                if (mEndAction != null) {
+                    mEndAction.run();
+                }
             }
         }
 
diff --git a/src/com/android/launcher3/anim/SpringObjectAnimator.java b/src/com/android/launcher3/anim/SpringObjectAnimator.java
new file mode 100644
index 0000000..4ece909
--- /dev/null
+++ b/src/com/android/launcher3/anim/SpringObjectAnimator.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 2019 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.anim;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.util.Log;
+import android.util.Property;
+
+import com.android.launcher3.ProgressInterface;
+
+import java.util.ArrayList;
+
+import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+
+/**
+ * This animator allows for an object's property to be be controlled by an {@link ObjectAnimator} or
+ * a {@link SpringAnimation}. It extends ValueAnimator so it can be used in an AnimatorSet.
+ */
+public class SpringObjectAnimator<T extends ProgressInterface> extends ValueAnimator {
+
+    private static final String TAG = "SpringObjectAnimator";
+    private static boolean DEBUG = false;
+
+    private T mObject;
+    private ObjectAnimator mObjectAnimator;
+    private float[] mValues;
+
+    private SpringAnimation mSpring;
+    private SpringProperty<T> mProperty;
+
+    private ArrayList<AnimatorListener> mListeners;
+    private boolean mSpringEnded = false;
+    private boolean mAnimatorEnded = false;
+    private boolean mEnded = false;
+
+    private static final float SPRING_DAMPING_RATIO = 0.9f;
+    private static final float SPRING_STIFFNESS = 600f;
+
+    public SpringObjectAnimator(T object, FloatPropertyCompat<T> floatProperty,
+            String name, float minimumVisibleChange, float... values) {
+        mObject = object;
+        mSpring = new SpringAnimation(object, floatProperty);
+        mSpring.setMinimumVisibleChange(minimumVisibleChange);
+        mSpring.setSpring(new SpringForce(0)
+                .setDampingRatio(SPRING_DAMPING_RATIO)
+                .setStiffness(SPRING_STIFFNESS));
+        mSpring.setStartVelocity(0.01f);
+        mProperty = new SpringProperty<T>(name, mSpring);
+        mObjectAnimator = ObjectAnimator.ofFloat(object, mProperty, values);
+        mValues = values;
+        mListeners = new ArrayList<>();
+        setFloatValues(values);
+
+        mObjectAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                mAnimatorEnded = false;
+                mEnded = false;
+                for (AnimatorListener l : mListeners) {
+                    l.onAnimationStart(animation);
+                }
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mAnimatorEnded = true;
+                tryEnding();
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                for (AnimatorListener l : mListeners) {
+                    l.onAnimationCancel(animation);
+                }
+                mSpring.animateToFinalPosition(mObject.getProgress());
+            }
+        });
+
+        mSpring.addUpdateListener((animation, value, velocity) -> mSpringEnded = false);
+        mSpring.addEndListener((animation, canceled, value, velocity) -> {
+            mSpringEnded = true;
+            tryEnding();
+        });
+    }
+
+    private void tryEnding() {
+        if (DEBUG) {
+            Log.d(TAG, "tryEnding#mAnimatorEnded=" + mAnimatorEnded + ", mSpringEnded="
+                    + mSpringEnded + ", mEnded=" + mEnded);
+        }
+
+        // If springs are disabled, ignore value of mSpringEnded
+        if (mAnimatorEnded && (mSpringEnded || !QUICKSTEP_SPRINGS.get()) && !mEnded) {
+            for (AnimatorListener l : mListeners) {
+                l.onAnimationEnd(this);
+            }
+            mEnded = true;
+        }
+    }
+
+    public SpringAnimation getSpring() {
+        return mSpring;
+    }
+
+    /**
+     * Initializes and sets up the spring to take over controlling the object.
+     */
+    void startSpring(float end, float velocity, OnAnimationEndListener endListener) {
+        // Cancel the spring so we can set new start velocity and final position. We need to remove
+        // the listener since the spring is not actually ending.
+        mSpring.removeEndListener(endListener);
+        mSpring.cancel();
+        mSpring.addEndListener(endListener);
+
+        mProperty.switchToSpring();
+
+        mSpring.setStartVelocity(velocity);
+        mSpring.animateToFinalPosition(end == 0 ? mValues[0] : mValues[1]);
+    }
+
+    @Override
+    public void addListener(AnimatorListener listener) {
+        mListeners.add(listener);
+    }
+
+    @Override
+    public ArrayList<AnimatorListener> getListeners() {
+        return mListeners;
+    }
+
+    @Override
+    public void removeAllListeners() {
+        mListeners.clear();
+    }
+
+    @Override
+    public void removeListener(AnimatorListener listener) {
+        mListeners.remove(listener);
+    }
+
+    @Override
+    public void addPauseListener(AnimatorPauseListener listener) {
+        mObjectAnimator.addPauseListener(listener);
+    }
+
+    @Override
+    public void cancel() {
+        mSpring.animateToFinalPosition(mObject.getProgress());
+        mObjectAnimator.cancel();
+    }
+
+    @Override
+    public void end() {
+        mObjectAnimator.end();
+    }
+
+    @Override
+    public long getDuration() {
+        return mObjectAnimator.getDuration();
+    }
+
+    @Override
+    public TimeInterpolator getInterpolator() {
+        return mObjectAnimator.getInterpolator();
+    }
+
+    @Override
+    public long getStartDelay() {
+        return mObjectAnimator.getStartDelay();
+    }
+
+    @Override
+    public long getTotalDuration() {
+        return mObjectAnimator.getTotalDuration();
+    }
+
+    @Override
+    public boolean isPaused() {
+        return mObjectAnimator.isPaused();
+    }
+
+    @Override
+    public boolean isRunning() {
+        return mObjectAnimator.isRunning();
+    }
+
+    @Override
+    public boolean isStarted() {
+        return mObjectAnimator.isStarted();
+    }
+
+    @Override
+    public void pause() {
+        mObjectAnimator.pause();
+    }
+
+    @Override
+    public void removePauseListener(AnimatorPauseListener listener) {
+        mObjectAnimator.removePauseListener(listener);
+    }
+
+    @Override
+    public void resume() {
+        mObjectAnimator.resume();
+    }
+
+    @Override
+    public ValueAnimator setDuration(long duration) {
+        return mObjectAnimator.setDuration(duration);
+    }
+
+    @Override
+    public void setInterpolator(TimeInterpolator value) {
+        mObjectAnimator.setInterpolator(value);
+    }
+
+    @Override
+    public void setStartDelay(long startDelay) {
+        mObjectAnimator.setStartDelay(startDelay);
+    }
+
+    @Override
+    public void setTarget(Object target) {
+        mObjectAnimator.setTarget(target);
+    }
+
+    @Override
+    public void start() {
+        mObjectAnimator.start();
+    }
+
+    @Override
+    public void setCurrentFraction(float fraction) {
+        mObjectAnimator.setCurrentFraction(fraction);
+    }
+
+    @Override
+    public void setCurrentPlayTime(long playTime) {
+        mObjectAnimator.setCurrentPlayTime(playTime);
+    }
+
+    public static class SpringProperty<T extends ProgressInterface> extends Property<T, Float> {
+
+        boolean useSpring = false;
+        final SpringAnimation mSpring;
+
+        public SpringProperty(String name, SpringAnimation spring) {
+            super(Float.class, name);
+            mSpring = spring;
+        }
+
+        public void switchToSpring() {
+            useSpring = true;
+        }
+
+        @Override
+        public Float get(T object) {
+            return object.getProgress();
+        }
+
+        @Override
+        public void set(T object, Float progress) {
+            if (useSpring) {
+                mSpring.animateToFinalPosition(progress);
+            } else {
+                object.setProgress(progress);
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
index 84e82e3..a7c0a47 100644
--- a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
+++ b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
@@ -12,6 +12,8 @@
 import java.lang.reflect.Method;
 import java.util.Locale;
 
+import androidx.annotation.NonNull;
+
 public class AlphabeticIndexCompat {
     private static final String TAG = "AlphabeticIndexCompat";
 
@@ -53,7 +55,7 @@
     /**
      * Computes the section name for an given string {@param s}.
      */
-    public String computeSectionName(CharSequence cs) {
+    public String computeSectionName(@NonNull CharSequence cs) {
         String s = Utilities.trim(cs);
         String sectionName = mBaseIndex.getBucketLabel(mBaseIndex.getBucketIndex(s));
         if (Utilities.trim(sectionName).isEmpty() && s.length() > 0) {
@@ -89,7 +91,7 @@
         /**
          * Returns the index of the bucket in which the given string should appear.
          */
-        protected int getBucketIndex(String s) {
+        protected int getBucketIndex(@NonNull String s) {
             if (s.isEmpty()) {
                 return UNKNOWN_BUCKET_INDEX;
             }
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index 03fdc64..fa93081 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -95,8 +95,19 @@
     public static final TogglableFlag APPLY_CONFIG_AT_RUNTIME = new TogglableFlag(
             "APPLY_CONFIG_AT_RUNTIME", true, "Apply display changes dynamically");
 
-    public static final TogglableFlag ENABLE_TASK_STABILIZER = new TogglableFlag(
-            "ENABLE_TASK_STABILIZER", false, "Stable task list across fast task switches");
+    public static final ToggleableGlobalSettingsFlag ENABLE_TASK_STABILIZER
+            = new ToggleableGlobalSettingsFlag("ENABLE_TASK_STABILIZER", false,
+            "Stable task list across fast task switches");
+
+    public static final TogglableFlag QUICKSTEP_SPRINGS = new TogglableFlag("QUICKSTEP_SPRINGS",
+            false, "Enable springs for quickstep animations");
+
+    public static final TogglableFlag ENABLE_QUICKSTEP_LIVE_TILE = new TogglableFlag(
+            "ENABLE_QUICKSTEP_LIVE_TILE", false, "Enable live tile in Quickstep overview");
+
+    public static final ToggleableGlobalSettingsFlag SWIPE_HOME
+            = new ToggleableGlobalSettingsFlag("SWIPE_HOME", false,
+            "[WIP] Swiping up on the nav bar goes home. Swipe and hold goes to recent apps.");
 
     public static void initialize(Context context) {
         // Avoid the disk read for user builds
diff --git a/src/com/android/launcher3/folder/FolderShape.java b/src/com/android/launcher3/folder/FolderShape.java
index ae279cb..4b06dda 100644
--- a/src/com/android/launcher3/folder/FolderShape.java
+++ b/src/com/android/launcher3/folder/FolderShape.java
@@ -23,6 +23,9 @@
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
@@ -34,13 +37,29 @@
 import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.os.Build;
+import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.util.TypedValue;
+import android.util.Xml;
 import android.view.ViewOutlineProvider;
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
+import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.Themes;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.annotation.Nullable;
 
 /**
  * Abstract representation of the shape of a folder icon
@@ -53,15 +72,7 @@
         return sInstance;
     }
 
-    private static FolderShape[] getAllShapes() {
-        return new FolderShape[] {
-                new Circle(),
-                new RoundedSquare(8f / 50),  // Ratios based on path defined in config_icon_mask
-                new RoundedSquare(30f / 50),
-                new Square(),
-                new TearDrop(),
-                new Squircle()};
-    }
+    private SparseArray<TypedValue> mAttrs;
 
     public abstract void drawShape(Canvas canvas, float offsetX, float offsetY, float radius,
             Paint paint);
@@ -71,6 +82,11 @@
     public abstract Animator createRevealAnimator(Folder target, Rect startRect, Rect endRect,
             float endRadius, boolean isReversed);
 
+    @Nullable
+    public TypedValue getAttrValue(int attr) {
+        return mAttrs == null ? null : mAttrs.get(attr);
+    }
+
     /**
      * Abstract shape where the reveal animation is a derivative of a round rect animation
      */
@@ -163,44 +179,22 @@
         }
     }
 
-    public static class Square extends SimpleRectShape {
-
-        @Override
-        public void drawShape(Canvas canvas, float offsetX, float offsetY, float radius,  Paint p) {
-            float cx = radius + offsetX;
-            float cy = radius + offsetY;
-            canvas.drawRect(cx - radius, cy - radius, cx + radius, cy + radius, p);
-        }
-
-        @Override
-        public void addShape(Path path, float offsetX, float offsetY, float radius) {
-            float cx = radius + offsetX;
-            float cy = radius + offsetY;
-            path.addRect(cx - radius, cy - radius, cx + radius, cy + radius, Path.Direction.CW);
-        }
-
-        @Override
-        protected float getStartRadius(Rect startRect) {
-            return 0;
-        }
-    }
-
     public static class RoundedSquare extends SimpleRectShape {
 
         /**
-         * Ratio of corner radius to half size. Based on the
+         * Ratio of corner radius to half size.
          */
-        private final float mRadiusFactor;
+        private final float mRadiusRatio;
 
-        public RoundedSquare(float radiusFactor) {
-            mRadiusFactor = radiusFactor;
+        public RoundedSquare(float radiusRatio) {
+            mRadiusRatio = radiusRatio;
         }
 
         @Override
         public void drawShape(Canvas canvas, float offsetX, float offsetY, float radius, Paint p) {
             float cx = radius + offsetX;
             float cy = radius + offsetY;
-            float cr = radius * mRadiusFactor;
+            float cr = radius * mRadiusRatio;
             canvas.drawRoundRect(cx - radius, cy - radius, cx + radius, cy + radius, cr, cr, p);
         }
 
@@ -208,14 +202,14 @@
         public void addShape(Path path, float offsetX, float offsetY, float radius) {
             float cx = radius + offsetX;
             float cy = radius + offsetY;
-            float cr = radius * mRadiusFactor;
+            float cr = radius * mRadiusRatio;
             path.addRoundRect(cx - radius, cy - radius, cx + radius, cy + radius, cr, cr,
                     Path.Direction.CW);
         }
 
         @Override
         protected float getStartRadius(Rect startRect) {
-            return (startRect.width() / 2f) * mRadiusFactor;
+            return (startRect.width() / 2f) * mRadiusRatio;
         }
     }
 
@@ -224,13 +218,16 @@
         /**
          * Radio of short radius to large radius, based on the shape options defined in the config.
          */
-        private static final float RADIUS_RATIO = 15f / 50;
-
+        private final float mRadiusRatio;
         private final float[] mTempRadii = new float[8];
 
+        public TearDrop(float radiusRatio) {
+            mRadiusRatio = radiusRatio;
+        }
+
         @Override
         public void addShape(Path p, float offsetX, float offsetY, float r1) {
-            float r2 = r1 * RADIUS_RATIO;
+            float r2 = r1 * mRadiusRatio;
             float cx = r1 + offsetX;
             float cy = r1 + offsetY;
 
@@ -249,7 +246,7 @@
         protected AnimatorUpdateListener newUpdateListener(Rect startRect, Rect endRect,
                 float endRadius, Path outPath) {
             float r1 = startRect.width() / 2f;
-            float r2 = r1 * RADIUS_RATIO;
+            float r2 = r1 * mRadiusRatio;
 
             float[] startValues = new float[] {
                     startRect.left, startRect.top, startRect.right, startRect.bottom, r1, r2};
@@ -273,13 +270,17 @@
         /**
          * Radio of radius to circle radius, based on the shape options defined in the config.
          */
-        private static final float RADIUS_RATIO = 10f / 50;
+        private final float mRadiusRatio;
+
+        public Squircle(float radiusRatio) {
+            mRadiusRatio = radiusRatio;
+        }
 
         @Override
         public void addShape(Path p, float offsetX, float offsetY, float r) {
             float cx = r + offsetX;
             float cy = r + offsetY;
-            float control = r - r * RADIUS_RATIO;
+            float control = r - r * mRadiusRatio;
 
             p.moveTo(cx, cy - r);
             addLeftCurve(cx, cy, r, control, p);
@@ -310,7 +311,7 @@
             float startCX = startRect.exactCenterX();
             float startCY = startRect.exactCenterY();
             float startR = startRect.width() / 2f;
-            float startControl = startR - startR * RADIUS_RATIO;
+            float startControl = startR - startR * mRadiusRatio;
             float startHShift = 0;
             float startVShift = 0;
 
@@ -351,17 +352,65 @@
     }
 
     /**
-     * Initializes the shape which is closest to closest to the {@link AdaptiveIconDrawable}
+     * Initializes the shape which is closest to the {@link AdaptiveIconDrawable}
      */
-    public static void init() {
+    public static void init(Context context) {
         if (!Utilities.ATLEAST_OREO) {
             return;
         }
-        new MainThreadExecutor().execute(FolderShape::pickShapeInBackground);
+        new MainThreadExecutor().execute(() -> pickShapeInBackground(context));
+    }
+
+    private static FolderShape getShapeDefinition(String type, float radius) {
+        switch (type) {
+            case "Circle":
+                return new Circle();
+            case "RoundedSquare":
+                return new RoundedSquare(radius);
+            case "TearDrop":
+                return new TearDrop(radius);
+            case "Squircle":
+                return new Squircle(radius);
+            default:
+                throw new IllegalArgumentException("Invalid shape type: " + type);
+        }
+    }
+
+    private static List<FolderShape> getAllShapes(Context context) {
+        ArrayList<FolderShape> result = new ArrayList<>();
+        try (XmlResourceParser parser = context.getResources().getXml(R.xml.folder_shapes)) {
+
+            // Find the root tag
+            int type;
+            while ((type = parser.next()) != XmlPullParser.END_TAG
+                    && type != XmlPullParser.END_DOCUMENT
+                    && !"shapes".equals(parser.getName()));
+
+            final int depth = parser.getDepth();
+            int[] radiusAttr = new int[] {R.attr.folderIconRadius};
+            IntArray keysToIgnore = new IntArray(0);
+
+            while (((type = parser.next()) != XmlPullParser.END_TAG ||
+                    parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
+
+                if (type == XmlPullParser.START_TAG) {
+                    AttributeSet attrs = Xml.asAttributeSet(parser);
+                    TypedArray a = context.obtainStyledAttributes(attrs, radiusAttr);
+                    FolderShape shape = getShapeDefinition(parser.getName(), a.getFloat(0, 1));
+                    a.recycle();
+
+                    shape.mAttrs = Themes.createValueMap(context, attrs, keysToIgnore);
+                    result.add(shape);
+                }
+            }
+        } catch (IOException | XmlPullParserException e) {
+            throw new RuntimeException(e);
+        }
+        return result;
     }
 
     @TargetApi(Build.VERSION_CODES.O)
-    protected static void pickShapeInBackground() {
+    protected static void pickShapeInBackground(Context context) {
         // Pick any large size
         int size = 200;
 
@@ -379,7 +428,7 @@
         // Find the shape with minimum area of divergent region.
         int minArea = Integer.MAX_VALUE;
         FolderShape closestShape = null;
-        for (FolderShape shape : getAllShapes()) {
+        for (FolderShape shape : getAllShapes(context)) {
             shapePath.reset();
             shape.addShape(shapePath, 0, 0, size / 2f);
             shapeR.setPath(shapePath, full);
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index 75d3425..7eb4015 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -36,6 +36,7 @@
 import com.android.launcher3.icons.BitmapRenderer;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
+import com.android.launcher3.widget.PendingAppWidgetHostView;
 
 import java.nio.ByteBuffer;
 
@@ -131,9 +132,15 @@
             width = (int) (mView.getWidth() * scale);
             height = (int) (mView.getHeight() * scale);
 
-            // Use software renderer for widgets as we know that they already work
-            return BitmapRenderer.createSoftwareBitmap(width + blurSizeOutline,
-                    height + blurSizeOutline, (c) -> drawDragView(c, scale));
+            if (mView instanceof PendingAppWidgetHostView) {
+                // Use hardware renderer as the icon for the pending app widget may be a hw bitmap
+                return BitmapRenderer.createHardwareBitmap(width + blurSizeOutline,
+                        height + blurSizeOutline, (c) -> drawDragView(c, scale));
+            } else {
+                // Use software renderer for widgets as we know that they already work
+                return BitmapRenderer.createSoftwareBitmap(width + blurSizeOutline,
+                        height + blurSizeOutline, (c) -> drawDragView(c, scale));
+            }
         }
 
         return BitmapRenderer.createHardwareBitmap(width + blurSizeOutline,
diff --git a/src/com/android/launcher3/graphics/GridOptionsProvider.java b/src/com/android/launcher3/graphics/GridOptionsProvider.java
new file mode 100644
index 0000000..efd39ee
--- /dev/null
+++ b/src/com/android/launcher3/graphics/GridOptionsProvider.java
@@ -0,0 +1,191 @@
+package com.android.launcher3.graphics;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.res.XmlResourceParser;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile.GridOption;
+import com.android.launcher3.R;
+import com.android.launcher3.util.LooperExecutor;
+import com.android.launcher3.util.UiThreadHelper;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Future;
+
+/**
+ * Exposes various launcher grid options and allows the caller to change them.
+ * APIs:
+ *      /list_options: List the various available grip options, has following columns
+ *          name: name of the grid
+ *          rows: number of rows in the grid
+ *          cols: number of columns in the grid
+ *          preview_count: number of previews available for this grid option. The preview uri
+ *                         looks like /preview/<grid-name>/<preview index starting with 0>
+ *          is_default: true if this grid is currently active
+ *
+ *     /preview: Opens a file stream for the grid preview
+ *
+ *     /default_grid: Call update to set the current grid, with values
+ *          name: name of the grid to apply
+ */
+public class GridOptionsProvider extends ContentProvider {
+
+    private static final String TAG = "GridOptionsProvider";
+
+    private static final String KEY_NAME = "name";
+    private static final String KEY_ROWS = "rows";
+    private static final String KEY_COLS = "cols";
+    private static final String KEY_PREVIEW_COUNT = "preview_count";
+    private static final String KEY_IS_DEFAULT = "is_default";
+
+    private static final String KEY_LIST_OPTIONS = "/list_options";
+    private static final String KEY_DEFAULT_GRID = "/default_grid";
+
+    private static final String KEY_PREVIEW = "preview";
+    private static final String MIME_TYPE_PNG = "image/png";
+
+    public static final PipeDataWriter<Future<Bitmap>> BITMAP_WRITER =
+            new PipeDataWriter<Future<Bitmap>>() {
+                @Override
+                public void writeDataToPipe(ParcelFileDescriptor output, Uri uri, String s,
+                        Bundle bundle, Future<Bitmap> bitmap) {
+                    try (AutoCloseOutputStream os = new AutoCloseOutputStream(output)) {
+                        bitmap.get().compress(Bitmap.CompressFormat.PNG, 100, os);
+                    } catch (Exception e) {
+                        Log.w(TAG, "fail to write to pipe", e);
+                    }
+                }
+            };
+
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder) {
+        if (!KEY_LIST_OPTIONS.equals(uri.getPath())) {
+            return null;
+        }
+        MatrixCursor cursor = new MatrixCursor(new String[] {
+                KEY_NAME, KEY_ROWS, KEY_COLS, KEY_PREVIEW_COUNT, KEY_IS_DEFAULT});
+        InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(getContext());
+        for (GridOption gridOption : parseAllGridOptions()) {
+            cursor.newRow()
+                    .add(KEY_NAME, gridOption.name)
+                    .add(KEY_ROWS, gridOption.numRows)
+                    .add(KEY_COLS, gridOption.numColumns)
+                    .add(KEY_PREVIEW_COUNT, 1)
+                    .add(KEY_IS_DEFAULT, idp.numColumns == gridOption.numColumns
+                            && idp.numRows == gridOption.numRows);
+        }
+        return cursor;
+    }
+
+    private List<GridOption> parseAllGridOptions() {
+        List<GridOption> result = new ArrayList<>();
+        try (XmlResourceParser parser = getContext().getResources().getXml(R.xml.device_profiles)) {
+            final int depth = parser.getDepth();
+            int type;
+            while (((type = parser.next()) != XmlPullParser.END_TAG ||
+                    parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
+                if ((type == XmlPullParser.START_TAG)
+                        && GridOption.TAG_NAME.equals(parser.getName())) {
+                    result.add(new GridOption(getContext(), Xml.asAttributeSet(parser)));
+                }
+            }
+        } catch (IOException | XmlPullParserException e) {
+            Log.e(TAG, "Error parsing device profile", e);
+            return Collections.emptyList();
+        }
+        return result;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        List<String> segments = uri.getPathSegments();
+        if (segments.size() > 0 && KEY_PREVIEW.equals(segments.get(0))) {
+            return MIME_TYPE_PNG;
+        }
+        return "vnd.android.cursor.dir/launcher_grid";
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues initialValues) {
+        return null;
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        return 0;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        if (!KEY_DEFAULT_GRID.equals(uri.getPath())) {
+            return 0;
+        }
+
+        String gridName = values.getAsString(KEY_NAME);
+        // Verify that this is a valid grid option
+        GridOption match = null;
+        for (GridOption option : parseAllGridOptions()) {
+            if (option.name.equals(gridName)) {
+                match = option;
+                break;
+            }
+        }
+        if (match == null) {
+            return 0;
+        }
+
+        InvariantDeviceProfile.INSTANCE.get(getContext()).setCurrentGrid(getContext(), gridName);
+        return 1;
+    }
+
+    @Override
+    public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+        List<String> segments = uri.getPathSegments();
+        if (segments.size() < 2 || !KEY_PREVIEW.equals(segments.get(0))) {
+            throw new FileNotFoundException("Invalid preview url");
+        }
+        String profileName = segments.get(1);
+        if (TextUtils.isEmpty(profileName)) {
+            throw new FileNotFoundException("Invalid preview url");
+        }
+
+        InvariantDeviceProfile idp;
+        try {
+            idp = new InvariantDeviceProfile(getContext(), profileName);
+        } catch (Exception e) {
+            throw new FileNotFoundException(e.getMessage());
+        }
+
+        LooperExecutor executor = new LooperExecutor(UiThreadHelper.getBackgroundLooper());
+        try {
+            return openPipeHelper(uri, MIME_TYPE_PNG, null,
+                    executor.submit(new LauncherPreviewRenderer(getContext(), idp)), BITMAP_WRITER);
+        } catch (Exception e) {
+            throw new FileNotFoundException(e.getMessage());
+        }
+    }
+}
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index dc6f50f..837749d 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -21,6 +21,7 @@
 
 import android.annotation.TargetApi;
 import android.app.Fragment;
+import android.app.WallpaperManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.TypedArray;
@@ -29,6 +30,7 @@
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.os.Build;
 import android.os.Handler;
@@ -61,6 +63,7 @@
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.BaseDragLayer;
 
+import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
 
 /**
@@ -72,7 +75,7 @@
  *   4) Measure and draw the view on a canvas
  */
 @TargetApi(Build.VERSION_CODES.O)
-public class LauncherPreviewRenderer {
+public class LauncherPreviewRenderer implements Callable<Bitmap> {
 
     private static final String TAG = "LauncherPreviewRenderer";
 
@@ -110,7 +113,8 @@
                 context.getString(R.string.label_application);
     }
 
-    public Bitmap createScreenShot() {
+    @Override
+    public Bitmap call() {
         return BitmapRenderer.createHardwareBitmap(mDp.widthPx, mDp.heightPx, c -> {
 
             if (Looper.myLooper() == Looper.getMainLooper()) {
@@ -279,7 +283,6 @@
             // Additional measure for views which use auto text size API
             measureView(mRootView, mDp.widthPx, mDp.heightPx);
 
-            canvas.drawColor(Color.GRAY);
             mRootView.draw(canvas);
             dispatchVisibilityAggregated(mRootView, false);
         }
diff --git a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
index 66f9dbf..6fac31e2 100644
--- a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
+++ b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
@@ -16,16 +16,9 @@
 
 package com.android.launcher3.graphics;
 
-import static android.content.Intent.ACTION_SCREEN_OFF;
-import static android.content.Intent.ACTION_USER_PRESENT;
-
 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
 
 import android.animation.ObjectAnimator;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -96,20 +89,6 @@
                 }
             };
 
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (ACTION_SCREEN_OFF.equals(action)) {
-                mAnimateScrimOnNextDraw = true;
-            } else if (ACTION_USER_PRESENT.equals(action)) {
-                // ACTION_USER_PRESENT is sent after onStart/onResume. This covers the case where
-                // the user unlocked and the Launcher is not in the foreground.
-                mAnimateScrimOnNextDraw = false;
-            }
-        }
-    };
-
     private static final int DARK_SCRIM_COLOR = 0x55000000;
     private static final int MAX_HOTSEAT_SCRIM_ALPHA = 100;
     private static final int ALPHA_MASK_HEIGHT_DP = 500;
@@ -225,20 +204,11 @@
     public void onViewAttachedToWindow(View view) {
         mWallpaperColorInfo.addOnChangeListener(this);
         onExtractedColorsChanged(mWallpaperColorInfo);
-
-        if (mTopScrim != null) {
-            IntentFilter filter = new IntentFilter(ACTION_SCREEN_OFF);
-            filter.addAction(ACTION_USER_PRESENT); // When the device wakes up + keyguard is gone
-            mRoot.getContext().registerReceiver(mReceiver, filter);
-        }
     }
 
     @Override
     public void onViewDetachedFromWindow(View view) {
         mWallpaperColorInfo.removeOnChangeListener(this);
-        if (mTopScrim != null) {
-            mRoot.getContext().unregisterReceiver(mReceiver);
-        }
     }
 
     @Override
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index 5fc5551..0d55301 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -62,7 +62,7 @@
         final IntArray addedWorkspaceScreensFinal = new IntArray();
 
         synchronized(dataModel) {
-            IntArray workspaceScreens = dataModel.workspaceScreens.clone();
+            IntArray workspaceScreens = dataModel.collectWorkspaceScreens();
 
             List<ItemInfo> filteredItems = new ArrayList<>();
             for (Pair<ItemInfo, Object> entry : mItemList) {
diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java
index f6e220f..23c6faf 100644
--- a/src/com/android/launcher3/model/BaseLoaderResults.java
+++ b/src/com/android/launcher3/model/BaseLoaderResults.java
@@ -92,7 +92,7 @@
         synchronized (mBgDataModel) {
             workspaceItems.addAll(mBgDataModel.workspaceItems);
             appWidgets.addAll(mBgDataModel.appWidgets);
-            orderedScreenIds.addAll(mBgDataModel.workspaceScreens);
+            orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens());
             mBgDataModel.lastBindId++;
         }
 
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 151d6f4..b338fff 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -27,6 +27,7 @@
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.Workspace;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.DumpTargetWrapper;
 import com.android.launcher3.model.nano.LauncherDumpProto;
@@ -37,6 +38,7 @@
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.IntSparseArrayMap;
 import com.google.protobuf.nano.MessageNano;
 
@@ -81,11 +83,6 @@
     public final IntSparseArrayMap<FolderInfo> folders = new IntSparseArrayMap<>();
 
     /**
-     * Ordered list of workspace screens ids.
-     */
-    public final IntArray workspaceScreens = new IntArray();
-
-    /**
      * Map of ShortcutKey to the number of times it is pinned.
      */
     public final Map<ShortcutKey, MutableInt> pinnedShortcutCounts = new HashMap<>();
@@ -118,11 +115,26 @@
         appWidgets.clear();
         folders.clear();
         itemsIdMap.clear();
-        workspaceScreens.clear();
         pinnedShortcutCounts.clear();
         deepShortcutMap.clear();
     }
 
+    /**
+     * Creates an array of valid workspace screens based on current items in the model.
+     */
+    public synchronized IntArray collectWorkspaceScreens() {
+        IntSet screenSet = new IntSet();
+        for (ItemInfo item: itemsIdMap) {
+            if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+                screenSet.add(item.screenId);
+            }
+        }
+        if (FeatureFlags.QSB_ON_FIRST_SCREEN.get() || screenSet.isEmpty()) {
+            screenSet.add(Workspace.FIRST_SCREEN_ID);
+        }
+        return screenSet.getArray();
+    }
+
     public synchronized void dump(String prefix, FileDescriptor fd, PrintWriter writer,
             String[] args) {
         if (Arrays.asList(args).contains("--proto")) {
@@ -130,11 +142,6 @@
             return;
         }
         writer.println(prefix + "Data Model:");
-        writer.print(prefix + " ---- workspace screens: ");
-        for (int i = 0; i < workspaceScreens.size(); i++) {
-            writer.print(" " + workspaceScreens.get(i));
-        }
-        writer.println();
         writer.println(prefix + " ---- workspace items ");
         for (int i = 0; i < workspaceItems.size(); i++) {
             writer.println(prefix + '\t' + workspaceItems.get(i).toString());
@@ -167,6 +174,7 @@
         // Add top parent nodes. (L1)
         DumpTargetWrapper hotseat = new DumpTargetWrapper(ContainerType.HOTSEAT, 0);
         IntSparseArrayMap<DumpTargetWrapper> workspaces = new IntSparseArrayMap<>();
+        IntArray workspaceScreens = collectWorkspaceScreens();
         for (int i = 0; i < workspaceScreens.size(); i++) {
             workspaces.put(workspaceScreens.get(i),
                     new DumpTargetWrapper(ContainerType.WORKSPACE, i));
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 8a6aa1af..cfabc10 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -31,7 +31,6 @@
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionInfo;
-import android.graphics.Bitmap;
 import android.os.Handler;
 import android.os.Process;
 import android.os.UserHandle;
@@ -43,19 +42,17 @@
 import com.android.launcher3.AllAppsList;
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.FolderInfo;
-import com.android.launcher3.ItemInfoWithIcon;
-import com.android.launcher3.icons.ComponentWithLabel;
-import com.android.launcher3.icons.ComponentWithLabel.ComponentCachingLogic;
-import com.android.launcher3.icons.cache.IconCacheUpdateHandler;
-import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.InstallShortcutReceiver;
 import com.android.launcher3.ItemInfo;
+import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.Workspace;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.PackageInstallerCompat;
@@ -63,16 +60,18 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIconPreviewVerifier;
+import com.android.launcher3.icons.ComponentWithLabel;
+import com.android.launcher3.icons.ComponentWithLabel.ComponentCachingLogic;
+import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.LauncherActivtiyCachingLogic;
 import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.icons.cache.IconCacheUpdateHandler;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.provider.ImportDataTask;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.LooperIdleLock;
 import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageManagerHelper;
@@ -158,9 +157,9 @@
             allItems.addAll(mBgDataModel.workspaceItems);
             allItems.addAll(mBgDataModel.appWidgets);
         }
-        int firstScreen = mBgDataModel.workspaceScreens.isEmpty()
-                ? -1 // In this case, we can still look at the items in the hotseat.
-                : mBgDataModel.workspaceScreens.get(0);
+        // Screen set is never empty
+        final int firstScreen = mBgDataModel.collectWorkspaceScreens().get(0);
+
         filterCurrentWorkspaceItems(firstScreen, allItems, firstScreenItems,
                 new ArrayList<>() /* otherScreenItems are ignored */);
         mFirstScreenBroadcast.sendBroadcasts(mApp.getContext(), firstScreenItems);
@@ -784,16 +783,6 @@
                         null,
                         new Handler(LauncherModel.getWorkerLooper()));
             }
-
-            // Initialize the screens array. Using an InstSet ensures that the screen ids
-            // are sorted.
-            IntSet screenSet = new IntSet();
-            for (ItemInfo item: mBgDataModel.itemsIdMap) {
-                if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
-                    screenSet.add(item.screenId);
-                }
-            }
-            mBgDataModel.workspaceScreens.addAll(screenSet.getArray());
         }
     }
 
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index 9c4a4ea..65103f6 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -29,6 +29,7 @@
 
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.util.UiThreadHelper;
 
 /**
  * Utility class to manage launcher rotation
@@ -154,7 +155,7 @@
         }
         if (activityFlags != mLastActivityFlags) {
             mLastActivityFlags = activityFlags;
-            mActivity.setRequestedOrientation(activityFlags);
+            UiThreadHelper.setOrientationAsync(mActivity, activityFlags);
         }
     }
 
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index a7bd243..bb14328 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -24,6 +24,7 @@
 import static com.android.launcher3.LauncherStateManager.NON_ATOMIC_COMPONENT;
 import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -45,6 +46,7 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
@@ -428,8 +430,8 @@
         maybeUpdateAtomicAnim(mFromState, targetState, targetState == mToState ? 1f : 0f);
         updateSwipeCompleteAnimation(anim, Math.max(duration, getRemainingAtomicDuration()),
                 targetState, velocity, fling);
-        mCurrentAnimation.dispatchOnStart();
-        if (fling && targetState == LauncherState.ALL_APPS) {
+        mCurrentAnimation.dispatchOnStartWithVelocity(endProgress, velocity);
+        if (fling && targetState == LauncherState.ALL_APPS && !QUICKSTEP_SPRINGS.get()) {
             mLauncher.getAppsView().addSpringFromFlingUpdateListener(anim, velocity);
         }
         anim.start();
diff --git a/src/com/android/launcher3/util/RaceConditionTracker.java b/src/com/android/launcher3/util/RaceConditionTracker.java
index 8b06787..6954d0e 100644
--- a/src/com/android/launcher3/util/RaceConditionTracker.java
+++ b/src/com/android/launcher3/util/RaceConditionTracker.java
@@ -24,6 +24,8 @@
 public class RaceConditionTracker {
     public final static boolean ENTER = true;
     public final static boolean EXIT = false;
+    static final String ENTER_POSTFIX = "enter";
+    static final String EXIT_POSTFIX = "exit";
 
     public interface EventProcessor {
         void onEvent(String eventName);
@@ -46,7 +48,7 @@
     }
 
     public static String enterExitEvt(String eventName, boolean isEnter) {
-        return eventName + ":" + (isEnter ? "enter" : "exit");
+        return eventName + ":" + (isEnter ? ENTER_POSTFIX : EXIT_POSTFIX);
     }
 
     public static String enterEvt(String eventName) {
diff --git a/src/com/android/launcher3/util/Themes.java b/src/com/android/launcher3/util/Themes.java
index 5f965a3..675e2f4 100644
--- a/src/com/android/launcher3/util/Themes.java
+++ b/src/com/android/launcher3/util/Themes.java
@@ -21,6 +21,9 @@
 import android.graphics.Color;
 import android.graphics.ColorMatrix;
 import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.util.TypedValue;
 
 /**
  * Various utility methods associated with theming.
@@ -104,4 +107,29 @@
         target.getArray()[14] = Color.blue(dstColor) - Color.blue(srcColor);
         target.getArray()[19] = Color.alpha(dstColor) - Color.alpha(srcColor);
     }
+
+    /**
+     * Creates a map for attribute-name to value for all the values in {@param attrs} which can be
+     * held in memory for later use.
+     */
+    public static SparseArray<TypedValue> createValueMap(Context context, AttributeSet attrSet,
+            IntArray keysToIgnore) {
+        int count = attrSet.getAttributeCount();
+        IntArray attrNameArray = new IntArray(count);
+        for (int i = 0; i < count; i++) {
+            attrNameArray.add(attrSet.getAttributeNameResource(i));
+        }
+        attrNameArray.removeAllValues(keysToIgnore);
+
+        int[] attrNames = attrNameArray.toArray();
+        SparseArray<TypedValue> result = new SparseArray<>(attrNames.length);
+        TypedArray ta = context.obtainStyledAttributes(attrSet, attrNames);
+        for (int i = 0; i < attrNames.length; i++) {
+            TypedValue tv = new TypedValue();
+            ta.getValue(i, tv);
+            result.put(attrNames[i], tv);
+        }
+
+        return result;
+    }
 }
diff --git a/src/com/android/launcher3/util/UiThreadHelper.java b/src/com/android/launcher3/util/UiThreadHelper.java
index 27140a1..cc442f9 100644
--- a/src/com/android/launcher3/util/UiThreadHelper.java
+++ b/src/com/android/launcher3/util/UiThreadHelper.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.util;
 
+import android.app.Activity;
 import android.content.Context;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -33,6 +34,8 @@
     private static Handler sHandler;
 
     private static final int MSG_HIDE_KEYBOARD = 1;
+    private static final int MSG_SET_ORIENTATION = 2;
+    private static final int MSG_RUN_COMMAND = 3;
 
     public static Looper getBackgroundLooper() {
         if (sHandlerThread == null) {
@@ -55,6 +58,15 @@
         Message.obtain(getHandler(context), MSG_HIDE_KEYBOARD, token).sendToTarget();
     }
 
+    public static void setOrientationAsync(Activity activity, int orientation) {
+        Message.obtain(getHandler(activity), MSG_SET_ORIENTATION, orientation, 0, activity)
+                .sendToTarget();
+    }
+
+    public static void runAsyncCommand(Context context, AsyncCommand command, int arg1, int arg2) {
+        Message.obtain(getHandler(context), MSG_RUN_COMMAND, arg1, arg2, command).sendToTarget();
+    }
+
     private static class UiCallbacks implements Handler.Callback {
 
         private final InputMethodManager mIMM;
@@ -69,8 +81,19 @@
                 case MSG_HIDE_KEYBOARD:
                     mIMM.hideSoftInputFromWindow((IBinder) message.obj, 0);
                     return true;
+                case MSG_SET_ORIENTATION:
+                    ((Activity) message.obj).setRequestedOrientation(message.arg1);
+                    return true;
+                case MSG_RUN_COMMAND:
+                    ((AsyncCommand) message.obj).execute(message.arg1, message.arg2);
+                    return true;
             }
             return false;
         }
     }
+
+    public interface AsyncCommand {
+
+        void execute(int arg1, int arg2);
+    }
 }
diff --git a/src/com/android/launcher3/util/ViewPool.java b/src/com/android/launcher3/util/ViewPool.java
new file mode 100644
index 0000000..8af048d
--- /dev/null
+++ b/src/com/android/launcher3/util/ViewPool.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util;
+
+import android.content.Context;
+import android.os.Handler;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.launcher3.util.ViewPool.Reusable;
+
+import androidx.annotation.AnyThread;
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+
+/**
+ * Utility class to maintain a pool of reusable views.
+ * During initialization, views are inflated on the background thread.
+ */
+public class ViewPool<T extends View & Reusable> {
+
+    private final Object[] mPool;
+
+    private final LayoutInflater mInflater;
+    private final ViewGroup mParent;
+    private final int mLayoutId;
+
+    private int mCurrentSize = 0;
+
+    public ViewPool(Context context, @Nullable ViewGroup parent,
+            int layoutId, int maxSize, int initialSize) {
+        mLayoutId = layoutId;
+        mParent = parent;
+        mInflater = LayoutInflater.from(context);
+        mPool = new Object[maxSize];
+
+        if (initialSize > 0) {
+            initPool(initialSize);
+        }
+    }
+
+    @UiThread
+    private void initPool(int initialSize) {
+        Preconditions.assertUIThread();
+        Handler handler = new Handler();
+
+        // Inflate views on a non looper thread. This allows us to catch errors like calling
+        // "new Handler()" in constructor easily.
+        new Thread(() -> {
+            for (int i = 0; i < initialSize; i++) {
+                T view = inflateNewView();
+                handler.post(() -> addToPool(view));
+            }
+        }).start();
+    }
+
+    @UiThread
+    public void recycle(T view) {
+        Preconditions.assertUIThread();
+        view.onRecycle();
+        addToPool(view);
+    }
+
+    @UiThread
+    private void addToPool(T view) {
+        Preconditions.assertUIThread();
+        if (mCurrentSize >= mPool.length) {
+            // pool is full
+            return;
+        }
+
+        mPool[mCurrentSize] = view;
+        mCurrentSize++;
+    }
+
+    @UiThread
+    public T getView() {
+        Preconditions.assertUIThread();
+        if (mCurrentSize > 0) {
+            mCurrentSize--;
+            return (T) mPool[mCurrentSize];
+        }
+        return inflateNewView();
+    }
+
+    @AnyThread
+    private T inflateNewView() {
+        return (T) mInflater.inflate(mLayoutId, mParent, false);
+    }
+
+    /**
+     * Interface to indicate that a view is reusable
+     */
+    public interface Reusable {
+
+        /**
+         * Called when a view is recycled / added back to the pool
+         */
+        void onRecycle();
+    }
+}
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
index 9a17ec6..7a7f828 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
@@ -69,7 +69,8 @@
         WidgetItemComparator widgetComparator = new WidgetItemComparator();
         for (Map.Entry<PackageItemInfo, ArrayList<WidgetItem>> entry : mWidgetsList.entrySet()) {
             WidgetListRowEntry row = new WidgetListRowEntry(entry.getKey(), entry.getValue());
-            row.titleSectionName = indexer.computeSectionName(row.pkgItem.title);
+            row.titleSectionName = (row.pkgItem.title == null) ? "" :
+                    indexer.computeSectionName(row.pkgItem.title);
             Collections.sort(row.widgets, widgetComparator);
             result.add(row);
         }
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 8c03e4b..1b34598 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -123,7 +123,7 @@
                 public void evaluate() throws Throwable {
                     try {
                         // Create launcher activity if necessary and bring it to the front.
-                        mDevice.pressHome();
+                        mLauncher.pressHome();
                         waitForLauncherCondition("Launcher activity wasn't created",
                                 launcher -> launcher != null);
 
diff --git a/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java b/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java
index 9354862..fdf87be 100644
--- a/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java
+++ b/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java
@@ -21,14 +21,12 @@
         LauncherActivityInfo settingsApp = getSettingsApp();
 
         clearHomescreen();
-        mDevice.pressHome();
-        mDevice.waitForIdle();
 
         final String appName = settingsApp.getLabel().toString();
         // 1. Open all apps and wait for load complete.
         // 2. Drag icon to homescreen.
         // 3. Verify that the icon works on homescreen.
-        mLauncher.getWorkspace().
+        mLauncher.pressHome().
                 switchToAllApps().
                 getAppIcon(appName).
                 dragToWorkspace().
diff --git a/tests/src/com/android/launcher3/util/RaceConditionReproducer.java b/tests/src/com/android/launcher3/util/RaceConditionReproducer.java
index 316e40d..0235f95 100644
--- a/tests/src/com/android/launcher3/util/RaceConditionReproducer.java
+++ b/tests/src/com/android/launcher3/util/RaceConditionReproducer.java
@@ -16,6 +16,9 @@
 
 package com.android.launcher3.util;
 
+import static com.android.launcher3.util.RaceConditionTracker.ENTER_POSTFIX;
+import static com.android.launcher3.util.RaceConditionTracker.EXIT_POSTFIX;
+
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -46,7 +49,7 @@
  * If an event A occurs before event B in the sequence, this is how execution order looks like:
  * Events: ... A ... B ...
  * Events and instructions, guaranteed order:
- *   (instructions executed prior to A) A ... B (instructions executed after B)
+ * (instructions executed prior to A) A ... B (instructions executed after B)
  *
  * Each iteration has 3 parts (phases).
  * Phase 1. Picking a previously seen event subsequence that we believe can have previously unseen
@@ -58,6 +61,8 @@
  * Phase 3. Releasing all threads and letting the test iteration run till its end.
  *
  * The iterations end when all seen paths have been declared “uncontinuable”.
+ *
+ * When we register event XXX:enter, we hold all other events until we register XXX:exit.
  */
 public class RaceConditionReproducer implements RaceConditionTracker.EventProcessor {
     private static final String TAG = "RaceConditionReproducer";
@@ -81,7 +86,7 @@
         private final Map<String, EventNode> mNextEvents = new HashMap<>();
         // Whether we believe that further iterations will not be able to add more events to
         // mNextEvents.
-        private boolean mStoppedAddingChildren = false;
+        private boolean mStoppedAddingChildren = true;
 
         private void debugDump(StringBuilder sb, int indent, String name) {
             for (int i = 0; i < indent; ++i) sb.append('.');
@@ -134,6 +139,8 @@
                 }
             }
             if (!mStoppedAddingChildren) {
+                // Mark that we have finished adding children. It will remain true if no new
+                // children are added, or will be set to false upon adding a new child.
                 mStoppedAddingChildren = true;
                 return true;
             }
@@ -216,6 +223,7 @@
         RaceConditionTracker.setEventProcessor(null);
         runResumeAllEventsCallbackLocked();
         assertTrue("Non-empty postponed events", mPostponedEvents.isEmpty());
+        assertTrue("Last registered event is :enter", lastEventAsEnter() == null);
 
         // No events came after mLastRegisteredEvent. It doesn't make sense to come to it again
         // because we won't see new continuations.
@@ -246,12 +254,36 @@
     }
 
     /**
+     * Returns whether the last event was not an XXX:enter, or this event is a matching XXX:exit.
+     */
+    private boolean canRegisterEventNowLocked(String event) {
+        final String lastEventAsEnter = lastEventAsEnter();
+        final String thisEventAsExit = eventAsExit(event);
+
+        if (lastEventAsEnter != null) {
+            if (!lastEventAsEnter.equals(thisEventAsExit)) {
+                assertTrue("YYY:exit after XXX:enter", thisEventAsExit == null);
+                // Last event was :enter, but this event is not :exit.
+                return false;
+            }
+        } else {
+            // Previous event was not :enter.
+            assertTrue(":exit after a non-enter event", thisEventAsExit == null);
+        }
+        return true;
+    }
+
+    /**
      * Registers an event issued by the app and returns null or decides that the event must be
      * postponed, and returns an object to wait on.
      */
     private synchronized Semaphore tryRegisterEvent(String event) {
         Log.d(TAG, "Event issued by the app: " + event);
 
+        if (!canRegisterEventNowLocked(event)) {
+            return createWaitObjectForPostponedEventLocked(event);
+        }
+
         if (mRegisteredEventCount < mSequenceToFollow.size()) {
             // We are in the first part of the iteration. We only register events that follow the
             // mSequenceToFollow and postponing all other events.
@@ -288,9 +320,14 @@
                 return createWaitObjectForPostponedEventLocked(event);
             }
         } else {
-            // The second phase of the iteration. We are past the growth point and register
+            // The third phase of the iteration. We are past the growth point and register
             // everything that comes.
             registerEventLocked(event);
+            // Register events that may have been postponed while waiting for an :exit event
+            // during the third phase. We don't do this if just registered event is :enter.
+            if (eventAsEnter(event) == null && mRegisteredEventCount > mSequenceToFollow.size()) {
+                registerPostponedEventsLocked(new HashSet<>(mPostponedEvents.keySet()));
+            }
         }
         return null;
     }
@@ -347,6 +384,11 @@
     private void registerPostponedEventsLocked(Collection<String> events) {
         for (String event : events) {
             registerPostponedEventLocked(event);
+            if (eventAsEnter(event) != null) {
+                // Once :enter is registered, switch to waiting for :exit to come. Won't register
+                // other postponed events.
+                break;
+            }
         }
     }
 
@@ -355,14 +397,51 @@
         registerEventLocked(event);
     }
 
+    /**
+     * If the last registered event was XXX:enter, returns XXX, otherwise, null.
+     */
+    private String lastEventAsEnter() {
+        return eventAsEnter(mCurrentSequence.substring(mCurrentSequence.lastIndexOf("|") + 1));
+    }
+
+    /**
+     * If the event is XXX:postfix, returns XXX, otherwise, null.
+     */
+    private static String prefixFromPostfixedEvent(String event, String postfix) {
+        final int columnPos = event.indexOf(':');
+        if (columnPos != -1 && postfix.equals(event.substring(columnPos + 1))) {
+            return event.substring(0, columnPos);
+        }
+        return null;
+    }
+
+    /**
+     * If the event is XXX:enter, returns XXX, otherwise, null.
+     */
+    private static String eventAsEnter(String event) {
+        return prefixFromPostfixedEvent(event, ENTER_POSTFIX);
+    }
+
+    /**
+     * If the event is XXX:exit, returns XXX, otherwise, null.
+     */
+    private static String eventAsExit(String event) {
+        return prefixFromPostfixedEvent(event, EXIT_POSTFIX);
+    }
+
     private void registerEventLocked(String event) {
+        assertTrue(canRegisterEventNowLocked(event));
+
         Log.d(TAG, "Actually registering event: " + event);
         EventNode next = mLastRegisteredEvent.mNextEvents.get(event);
         if (next == null) {
             // This event wasn't seen after mLastRegisteredEvent.
             next = new EventNode();
             mLastRegisteredEvent.mNextEvents.put(event, next);
-            mLastRegisteredEvent.mStoppedAddingChildren = false;
+            // The fact that we've added a new event after the previous one means that the
+            // previous event is still a growth point, unless this event is :exit, which means
+            // that the previous event is :enter.
+            mLastRegisteredEvent.mStoppedAddingChildren = eventAsExit(event) != null;
         }
 
         mLastRegisteredEvent = next;
@@ -371,12 +450,6 @@
         if (mCurrentSequence.length() > 0) mCurrentSequence.append("|");
         mCurrentSequence.append(event);
         Log.d(TAG, "Repro sequence: " + mCurrentSequence);
-
-        if (mRegisteredEventCount == mSequenceToFollow.size() + 1) {
-            // We just entered the third phase of the iteration, i.e. registered an event after
-            // the growth point. Now we can let go of all postponed events.
-            runResumeAllEventsCallbackLocked();
-        }
     }
 
     private void runResumeAllEventsCallbackLocked() {
diff --git a/tests/src/com/android/launcher3/util/RaceConditionReproducerTest.java b/tests/src/com/android/launcher3/util/RaceConditionReproducerTest.java
index 7dde5cc..3fc268e 100644
--- a/tests/src/com/android/launcher3/util/RaceConditionReproducerTest.java
+++ b/tests/src/com/android/launcher3/util/RaceConditionReproducerTest.java
@@ -77,6 +77,46 @@
     }
 
     @Test
+    @Ignore // The test is too long for continuous testing.
+    // 2 threads, 3 events, including enter-exit pairs each.
+    public void test3_3_enter_exit() throws Exception {
+        final RaceConditionReproducer eventProcessor = new RaceConditionReproducer();
+        boolean sawTheValidSequence = false;
+
+        for (; ; ) {
+            eventProcessor.startIteration();
+            Thread tb = new Thread(() -> {
+                RaceConditionTracker.onEvent("B1:enter");
+                RaceConditionTracker.onEvent("B1:exit");
+                RaceConditionTracker.onEvent("B2");
+                RaceConditionTracker.onEvent("B3:enter");
+                RaceConditionTracker.onEvent("B3:exit");
+            });
+            tb.start();
+
+            RaceConditionTracker.onEvent("A1");
+            RaceConditionTracker.onEvent("A2:enter");
+            RaceConditionTracker.onEvent("A2:exit");
+            RaceConditionTracker.onEvent("A3:enter");
+            RaceConditionTracker.onEvent("A3:exit");
+
+            tb.join();
+            final boolean needMoreIterations = eventProcessor.finishIteration();
+
+            sawTheValidSequence = sawTheValidSequence ||
+                    "B1:enter|B1:exit|A1|A2:enter|A2:exit|B2|A3:enter|A3:exit|B3:enter|B3:exit".
+                            equals(eventProcessor.getCurrentSequenceString());
+
+            if (!needMoreIterations) break;
+        }
+
+        assertEquals("Wrong number of leaf nodes",
+                factorial(3 + 3) / (factorial(3) * factorial(3)),
+                eventProcessor.numberOfLeafNodes());
+        assertTrue(sawTheValidSequence);
+    }
+
+    @Test
     // 2 threads, 3 events each; reproducing a particular event sequence.
     public void test3_3_ReproMode() throws Exception {
         final RaceConditionReproducer eventProcessor = new RaceConditionReproducer(
@@ -122,4 +162,42 @@
                 factorial(2 + 2 + 1) / (factorial(2) * factorial(2) * factorial(1)),
                 eventProcessor.numberOfLeafNodes());
     }
+
+    @Test
+    @Ignore // The test is too long for continuous testing.
+    // 2 threads with 2 events; 1 thread with 1 event. Includes enter-exit pairs.
+    public void test2_1_2_enter_exit() throws Exception {
+        final RaceConditionReproducer eventProcessor = new RaceConditionReproducer();
+
+        for (; ; ) {
+            eventProcessor.startIteration();
+            Thread tb = new Thread(() -> {
+                RaceConditionTracker.onEvent("B1:enter");
+                RaceConditionTracker.onEvent("B1:exit");
+                RaceConditionTracker.onEvent("B2:enter");
+                RaceConditionTracker.onEvent("B2:exit");
+            });
+            tb.start();
+
+            Thread tc = new Thread(() -> {
+                RaceConditionTracker.onEvent("C1:enter");
+                RaceConditionTracker.onEvent("C1:exit");
+            });
+            tc.start();
+
+            RaceConditionTracker.onEvent("A1:enter");
+            RaceConditionTracker.onEvent("A1:exit");
+            RaceConditionTracker.onEvent("A2:enter");
+            RaceConditionTracker.onEvent("A2:exit");
+
+            tb.join();
+            tc.join();
+
+            if (!eventProcessor.finishIteration()) break;
+        }
+
+        assertEquals("Wrong number of leaf nodes",
+                factorial(2 + 2 + 1) / (factorial(2) * factorial(2) * factorial(1)),
+                eventProcessor.numberOfLeafNodes());
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index 84fd908..82ea8be 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -54,7 +54,7 @@
     @NonNull
     public AppIcon getAppIcon(String appName) {
         final UiObject2 allAppsContainer = verifyActiveContainer();
-        final BySelector appIconSelector = AppIcon.getAppIconSelector(appName);
+        final BySelector appIconSelector = AppIcon.getAppIconSelector(appName, mLauncher);
         if (!allAppsContainer.hasObject(appIconSelector)) {
             scrollBackToBeginning();
             int attempts = 0;
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index efefc0d..3ffd30c 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -31,8 +31,8 @@
         super(launcher, icon);
     }
 
-    static BySelector getAppIconSelector(String appName) {
-        return By.clazz(TextView.class).text(appName).pkg(LauncherInstrumentation.LAUNCHER_PKG);
+    static BySelector getAppIconSelector(String appName, LauncherInstrumentation launcher) {
+        return By.clazz(TextView.class).text(appName).pkg(launcher.getLauncherPackageName());
     }
 
     /**
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java b/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java
index 2a03f9a..7f28151 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java
@@ -20,6 +20,8 @@
 
 import androidx.test.uiautomator.UiObject2;
 
+import java.util.List;
+
 /**
  * Context menu of an app icon.
  */
@@ -37,10 +39,9 @@
      * Returns a menu item with a given number. Fails if it doesn't exist.
      */
     public AppIconMenuItem getMenuItem(int itemNumber) {
-        assertTrue(mDeepShortcutsContainer.getChildCount() > itemNumber);
-
-        final UiObject2 shortcut = mLauncher.waitForObjectInContainer(
-                mDeepShortcutsContainer.getChildren().get(itemNumber), "bubble_text");
-        return new AppIconMenuItem(mLauncher, shortcut);
+        final List<UiObject2> menuItems = mLauncher.getObjectsInContainer(mDeepShortcutsContainer,
+                "bubble_text");
+        assertTrue(menuItems.size() > itemNumber);
+        return new AppIconMenuItem(mLauncher, menuItems.get(itemNumber));
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index 27e0954..08d2889 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -64,7 +64,7 @@
 
             mLauncher.swipe(
                     navBar.getVisibleBounds().centerX(), navBar.getVisibleBounds().centerY(),
-                    navBar.getVisibleBounds().centerX(), height - 300);
+                    navBar.getVisibleBounds().centerX(), height - 400);
         } else {
             mLauncher.getSystemUiObject("recent_apps").click();
         }
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index 4fce211..5f60113 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -16,13 +16,13 @@
 
 package com.android.launcher3.tapl;
 
-import java.util.Collections;
-import java.util.List;
-
 import androidx.annotation.NonNull;
 import androidx.test.uiautomator.Direction;
 import androidx.test.uiautomator.UiObject2;
 
+import java.util.Collections;
+import java.util.List;
+
 /**
  * Common overview pane for both Launcher and fallback recents
  */
@@ -69,7 +69,7 @@
     public OverviewTask getCurrentTask() {
         verifyActiveContainer();
         final List<UiObject2> taskViews = mLauncher.getDevice().findObjects(
-                LauncherInstrumentation.getLauncherObjectSelector("snapshot"));
+                mLauncher.getLauncherObjectSelector("snapshot"));
         LauncherInstrumentation.assertNotEquals("Unable to find a task", 0, taskViews.size());
 
         // taskViews contains up to 3 task views: the 'main' (having the widest visible
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index bd1c657..49bd73a 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -41,6 +41,7 @@
 import org.junit.Assert;
 
 import java.lang.ref.WeakReference;
+import java.util.List;
 import java.util.concurrent.TimeoutException;
 
 /**
@@ -83,7 +84,6 @@
     private static final String APPS_RES_ID = "apps_view";
     private static final String OVERVIEW_RES_ID = "overview_panel";
     private static final String WIDGETS_RES_ID = "widgets_list_view";
-    static final String LAUNCHER_PKG = "com.google.android.apps.nexuslauncher";
     public static final int WAIT_TIME_MS = 60000;
     private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
 
@@ -368,6 +368,11 @@
     }
 
     @NonNull
+    List<UiObject2> getObjectsInContainer(UiObject2 container, String resName) {
+        return container.findObjects(getLauncherObjectSelector(resName));
+    }
+
+    @NonNull
     UiObject2 waitForObjectInContainer(UiObject2 container, String resName) {
         final UiObject2 object = container.wait(
                 Until.findObject(getLauncherObjectSelector(resName)),
@@ -379,14 +384,18 @@
 
     @NonNull
     UiObject2 waitForLauncherObject(String resName) {
-        final UiObject2 object = mDevice.wait(Until.findObject(getLauncherObjectSelector(resName)),
-                WAIT_TIME_MS);
-        assertNotNull("Can't find a launcher object; id: " + resName, object);
+        final BySelector selector = getLauncherObjectSelector(resName);
+        final UiObject2 object = mDevice.wait(Until.findObject(selector), WAIT_TIME_MS);
+        assertNotNull("Can't find a launcher object; selector: " + selector, object);
         return object;
     }
 
-    static BySelector getLauncherObjectSelector(String resName) {
-        return By.res(LAUNCHER_PKG, resName);
+    BySelector getLauncherObjectSelector(String resName) {
+        return By.res(getLauncherPackageName(), resName);
+    }
+
+    String getLauncherPackageName() {
+        return mDevice.getLauncherPackageName();
     }
 
     @NonNull
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index c45f0f0..5e6ad4d 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -69,7 +69,7 @@
     @Nullable
     public AppIcon tryGetWorkspaceAppIcon(String appName) {
         final UiObject2 workspace = verifyActiveContainer();
-        final UiObject2 icon = workspace.findObject(AppIcon.getAppIconSelector(appName));
+        final UiObject2 icon = workspace.findObject(AppIcon.getAppIconSelector(appName, mLauncher));
         return icon != null ? new AppIcon(mLauncher, icon) : null;
     }
 
@@ -85,7 +85,7 @@
         return new AppIcon(mLauncher,
                 mLauncher.getObjectInContainer(
                         verifyActiveContainer(),
-                        AppIcon.getAppIconSelector(appName)));
+                        AppIcon.getAppIconSelector(appName, mLauncher)));
     }
 
     /**
@@ -108,7 +108,7 @@
     @NonNull
     private AppIcon getHotseatAppIcon(String appName) {
         return new AppIcon(mLauncher, mLauncher.getObjectInContainer(
-                mHotseat, AppIcon.getAppIconSelector(appName)));
+                mHotseat, AppIcon.getAppIconSelector(appName, mLauncher)));
     }
 
     private void dragIconToNextScreen(AppIcon app, UiObject2 workspace) {