Merge "Removes recovery" into 24D1-dev
diff --git a/go/quickstep/res/values-ne/strings.xml b/go/quickstep/res/values-ne/strings.xml
index 11a70dd..cee6603 100644
--- a/go/quickstep/res/values-ne/strings.xml
+++ b/go/quickstep/res/values-ne/strings.xml
@@ -8,7 +8,7 @@
     <string name="dialog_acknowledge" msgid="2804025517675853172">"बुझेँ"</string>
     <string name="dialog_cancel" msgid="6464336969134856366">"रद्द गर्नुहोस्"</string>
     <string name="dialog_settings" msgid="6564397136021186148">"सेटिङ"</string>
-    <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"स्क्रिनमा देखिने पाठ अनुवाद गरियोस् वा पढेर सुनाइयोस्"</string>
+    <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"स्क्रिनमा देखिने पाठ अनुवाद गर्नुहोस् वा पढेर सुनाइयोस्"</string>
     <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"तपाईंको स्क्रिनमा देखिने पाठ, वेब ठेगाना र स्क्रिनसटलगायतका जानकारी Google सँग सेयर गर्न सकिन्छ।\n\nकुन कुन जानकारी सेयर गर्न दिने भन्ने सेटिङ बदल्न "<b>"सेटिङ &gt; एप &gt; डिफल्ट एप &gt; डिजिटल सहायक एप"</b>" मा जानुहोस्।"</string>
     <string name="assistant_not_selected_title" msgid="5017072974603345228">"तपाईं यो सुविधा चलाउन चाहनुहुन्छ भने कुनै सहायक छनौट गर्नुहोस्"</string>
     <string name="assistant_not_selected_text" msgid="3244613673884359276">"तपाईं आफ्नो स्क्रिनमा देखिने पाठ सुन्न वा अनुवाद गर्न चाहनुहुन्छ भने सेटिङमा गई कुनै डिजिटल सहायक एप छनौट गर्नुहोस्"</string>
diff --git a/quickstep/res/values-af/strings.xml b/quickstep/res/values-af/strings.xml
index 48aec13..c0ffdbb 100644
--- a/quickstep/res/values-af/strings.xml
+++ b/quickstep/res/values-af/strings.xml
@@ -93,7 +93,7 @@
     <string name="default_device_name" msgid="6660656727127422487">"toestel"</string>
     <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Stelselnavigasie-instellings"</annotation></string>
     <string name="action_share" msgid="2648470652637092375">"Deel"</string>
-    <string name="action_screenshot" msgid="8171125848358142917">"Skermkiekie"</string>
+    <string name="action_screenshot" msgid="8171125848358142917">"Skermskoot"</string>
     <string name="action_split" msgid="2098009717623550676">"Verdeel"</string>
     <string name="action_save_app_pair" msgid="5974823919237645229">"Stoor app-paar"</string>
     <string name="toast_split_select_app" msgid="8464310533320556058">"Tik op ’n ander app om verdeelde skerm te gebruik"</string>
diff --git a/quickstep/res/values-ar/strings.xml b/quickstep/res/values-ar/strings.xml
index 048ff8b..fa41370 100644
--- a/quickstep/res/values-ar/strings.xml
+++ b/quickstep/res/values-ar/strings.xml
@@ -21,7 +21,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="recent_task_option_pin" msgid="7929860679018978258">"تثبيت"</string>
     <string name="recent_task_option_freeform" msgid="48863056265284071">"شكل مجاني"</string>
-    <string name="recents_empty_message" msgid="7040467240571714191">"ليست هناك عناصر تم استخدامها مؤخرًا"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"ما مِن عناصر تم استخدامها مؤخرًا"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"إعدادات استخدام التطبيق"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"محو الكل"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"التطبيقات المستخدمة مؤخرًا"</string>
diff --git a/quickstep/res/values-b+sr+Latn/strings.xml b/quickstep/res/values-b+sr+Latn/strings.xml
index 1afde8f..89e27c8 100644
--- a/quickstep/res/values-b+sr+Latn/strings.xml
+++ b/quickstep/res/values-b+sr+Latn/strings.xml
@@ -112,7 +112,7 @@
     <string name="taskbar_edu_splitscreen" msgid="5605512479258053350">"Prevucite na stranu da biste koristili 2 aplikacije odjednom"</string>
     <string name="taskbar_edu_stashing" msgid="5645461372669217294">"Sporo prevucite nagore da biste videli traku zadataka"</string>
     <string name="taskbar_edu_suggestions" msgid="8215044496435527982">"Dobijajte predloge aplikacija na osnovu rutine"</string>
-    <string name="taskbar_edu_pinning" msgid="6708550858580071558">"Dugo pritiskajte razdelnik da biste zakačili traku zadataka"</string>
+    <string name="taskbar_edu_pinning" msgid="6708550858580071558">"Dugo pritisnite razdelnik da biste zakačili traku zadataka"</string>
     <string name="taskbar_edu_features" msgid="3320337287472848162">"Uradite više pomoću trake zadataka"</string>
     <string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Uvek prikazuj traku zadataka"</string>
     <string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Da bi traka zadataka uvek bila prikazana u dnu ekrana, dodirnite i zadržite razdelnik"</string>
diff --git a/quickstep/res/values-cs/strings.xml b/quickstep/res/values-cs/strings.xml
index 5ebf7eb..9c1dedf 100644
--- a/quickstep/res/values-cs/strings.xml
+++ b/quickstep/res/values-cs/strings.xml
@@ -21,7 +21,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="recent_task_option_pin" msgid="7929860679018978258">"Připnout"</string>
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Neomezený režim"</string>
-    <string name="recents_empty_message" msgid="7040467240571714191">"Žádné nedávné položky"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"Žádné položky z nedávné doby"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Nastavení využití aplikací"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Vymazat vše"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Poslední aplikace"</string>
diff --git a/quickstep/res/values-eu/strings.xml b/quickstep/res/values-eu/strings.xml
index 3247785..6859498 100644
--- a/quickstep/res/values-eu/strings.xml
+++ b/quickstep/res/values-eu/strings.xml
@@ -110,7 +110,7 @@
     <string name="accessibility_rotate_button" msgid="4771825231336502943">"Biratu pantaila"</string>
     <string name="taskbar_edu_a11y_title" msgid="5417986057866415355">"Zereginen barra erabiltzeko argibideak"</string>
     <string name="taskbar_edu_splitscreen" msgid="5605512479258053350">"Bi aplikazio batera erabiltzeko, arrastatu bat albo batera"</string>
-    <string name="taskbar_edu_stashing" msgid="5645461372669217294">"Zereginen barra ikusteko, pasatu hatza gora poliki"</string>
+    <string name="taskbar_edu_stashing" msgid="5645461372669217294">"Zereginen barra ikusteko, pasatu hatza gora mantso"</string>
     <string name="taskbar_edu_suggestions" msgid="8215044496435527982">"Jaso aplikazioen iradokizunak erabileran oinarrituta"</string>
     <string name="taskbar_edu_pinning" msgid="6708550858580071558">"Zereginen barra ainguratzeko, sakatu zatitzailea luze"</string>
     <string name="taskbar_edu_features" msgid="3320337287472848162">"Egin gauza gehiago zereginen barrarekin"</string>
diff --git a/quickstep/res/values-fr/strings.xml b/quickstep/res/values-fr/strings.xml
index a74e7b6..03714c4 100644
--- a/quickstep/res/values-fr/strings.xml
+++ b/quickstep/res/values-fr/strings.xml
@@ -110,7 +110,7 @@
     <string name="accessibility_rotate_button" msgid="4771825231336502943">"Faire pivoter l\'écran"</string>
     <string name="taskbar_edu_a11y_title" msgid="5417986057866415355">"Fonctionnement de la barre des tâches"</string>
     <string name="taskbar_edu_splitscreen" msgid="5605512479258053350">"Faites glisser une appli sur le côté pour en utiliser 2 à la fois"</string>
-    <string name="taskbar_edu_stashing" msgid="5645461372669217294">"Balayez lentement vers haut pour afficher barre des tâches"</string>
+    <string name="taskbar_edu_stashing" msgid="5645461372669217294">"Balayez lentement vers le haut pour voir la barre des tâches"</string>
     <string name="taskbar_edu_suggestions" msgid="8215044496435527982">"Obtenez des suggestions d\'applis basées sur vos habitudes"</string>
     <string name="taskbar_edu_pinning" msgid="6708550858580071558">"Appui prolongé sur le séparateur pour épingler la barre des tâches"</string>
     <string name="taskbar_edu_features" msgid="3320337287472848162">"Faites-en plus avec la barre des tâches"</string>
@@ -134,7 +134,7 @@
     <string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Séparateur de barre des tâches"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Déplacer en haut ou à gauche"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Déplacer en bas ou à droite"</string>
-    <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Afficher # autre appli.}one{Afficher # autre appli.}other{Afficher # autre applis.}}"</string>
+    <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Afficher # autre appli}one{Afficher # autre appli}other{Afficher # autre applis}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> et <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
     <string name="desktop_select_app_toast" msgid="2306057322833956910">"Ajout de l\'appli au bureau"</string>
     <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"Annuler"</string>
diff --git a/quickstep/res/values-gl/strings.xml b/quickstep/res/values-gl/strings.xml
index 9875823..a336205 100644
--- a/quickstep/res/values-gl/strings.xml
+++ b/quickstep/res/values-gl/strings.xml
@@ -112,7 +112,7 @@
     <string name="taskbar_edu_splitscreen" msgid="5605512479258053350">"Arrastra unha aplicación cara a un lado para usar dúas á vez"</string>
     <string name="taskbar_edu_stashing" msgid="5645461372669217294">"Pasa o dedo amodo cara arriba para ver a barra de tarefas"</string>
     <string name="taskbar_edu_suggestions" msgid="8215044496435527982">"Obtén suxestións de aplicacións en función da túa rutina"</string>
-    <string name="taskbar_edu_pinning" msgid="6708550858580071558">"Mantén premida a liña divisoria para fixar a Barra de tarefas"</string>
+    <string name="taskbar_edu_pinning" msgid="6708550858580071558">"Mantén premida a liña divisoria para fixar a barra de tarefas"</string>
     <string name="taskbar_edu_features" msgid="3320337287472848162">"Tira máis proveito da barra de tarefas"</string>
     <string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Mostrar sempre a barra de tarefas"</string>
     <string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Para fixar a barra de tarefas na parte inferior, mantén premida a liña divisoria"</string>
diff --git a/quickstep/res/values-hi/strings.xml b/quickstep/res/values-hi/strings.xml
index 368cc0a..e0a93f8 100644
--- a/quickstep/res/values-hi/strings.xml
+++ b/quickstep/res/values-hi/strings.xml
@@ -21,9 +21,9 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="recent_task_option_pin" msgid="7929860679018978258">"पिन करें"</string>
     <string name="recent_task_option_freeform" msgid="48863056265284071">"फ़्रीफ़ॉर्म"</string>
-    <string name="recents_empty_message" msgid="7040467240571714191">"हाल ही में इस्तेमाल किया गया कोई ऐप्लिकेशन नहीं है"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"हाल ही का कोई आइटम नहीं है"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ऐप्लिकेशन इस्तेमाल की सेटिंग"</string>
-    <string name="recents_clear_all" msgid="5328176793634888831">"सभी ऐप्लिकेशन बंद करें"</string>
+    <string name="recents_clear_all" msgid="5328176793634888831">"सभी हटाएं"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"हाल ही में इस्तेमाल किए गए ऐप्लिकेशन"</string>
     <string name="task_view_closed" msgid="9170038230110856166">"टास्क बंद किया गया"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
@@ -110,10 +110,10 @@
     <string name="accessibility_rotate_button" msgid="4771825231336502943">"स्क्रीन घुमाएं"</string>
     <string name="taskbar_edu_a11y_title" msgid="5417986057866415355">"टास्कबार का ट्यूटोरियल"</string>
     <string name="taskbar_edu_splitscreen" msgid="5605512479258053350">"किसी ऐप को किनारे की ओर ड्रैग करके 2 ऐप एक साथ इस्तेमाल करें"</string>
-    <string name="taskbar_edu_stashing" msgid="5645461372669217294">"टास्कबार दिखाने के लिए, ऊपर की ओर धीरे से स्वाइप करें"</string>
-    <string name="taskbar_edu_suggestions" msgid="8215044496435527982">"डिवाइस के इस्तेमाल के आधार पर ऐप्लिकेशन के सुझाव पाएं"</string>
+    <string name="taskbar_edu_stashing" msgid="5645461372669217294">"टास्कबार देखने के लिए, ऊपर की ओर धीरे से स्वाइप करें"</string>
+    <string name="taskbar_edu_suggestions" msgid="8215044496435527982">"इस्तेमाल के आधार पर ऐप्लिकेशन के सुझाव पाएं"</string>
     <string name="taskbar_edu_pinning" msgid="6708550858580071558">"टास्कबार को पिन करने के लिए डिवाइडर को दबाकर रखें"</string>
-    <string name="taskbar_edu_features" msgid="3320337287472848162">"टास्कबार की मदद से कई और काम करें"</string>
+    <string name="taskbar_edu_features" msgid="3320337287472848162">"टास्कबार के साथ कई और काम करें"</string>
     <string name="taskbar_edu_pinning_title" msgid="210102174154211712">"टास्कबार को हमेशा दिखाएं"</string>
     <string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"टास्कबार को हमेशा अपनी स्क्रीन के नीचे दिखाने के लिए, डिवाइडर दबाकर रखें"</string>
     <string name="taskbar_edu_close" msgid="887022990168191073">"बंद करें"</string>
diff --git a/quickstep/res/values-hy/strings.xml b/quickstep/res/values-hy/strings.xml
index c09ceba..e9af69c 100644
--- a/quickstep/res/values-hy/strings.xml
+++ b/quickstep/res/values-hy/strings.xml
@@ -97,10 +97,10 @@
     <string name="action_split" msgid="2098009717623550676">"Տրոհել"</string>
     <string name="action_save_app_pair" msgid="5974823919237645229">"Պահել հավելվ․ զույգը"</string>
     <string name="toast_split_select_app" msgid="8464310533320556058">"Հպեք այլ հավելվածի՝ տրոհված էկրանից օգտվելու համար"</string>
-    <string name="toast_contextual_split_select_app" msgid="433510957123687090">"Ընտրեք այլ հավելված՝ կիսված էկրանից օգտվելու համար"</string>
+    <string name="toast_contextual_split_select_app" msgid="433510957123687090">"Ընտրեք այլ հավելված՝ տրոհված էկրանից օգտվելու համար"</string>
     <string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Չեղարկել"</b></string>
     <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Դուրս գալ տրոհված էկրանի ռեժիմից"</string>
-    <string name="toast_split_app_unsupported" msgid="2360229567007828914">"Ընտրեք այլ հավելված՝ կիսված էկրանից օգտվելու համար"</string>
+    <string name="toast_split_app_unsupported" msgid="2360229567007828914">"Ընտրեք այլ հավելված՝ տրոհված էկրանից օգտվելու համար"</string>
     <string name="blocked_by_policy" msgid="2071401072261365546">"Այս գործողությունն արգելված է հավելվածի կամ ձեր կազմակերպության կողմից"</string>
     <string name="split_widgets_not_supported" msgid="1355743038053053866">"Վիջեթները ներկայումս չեն աջակցվում. ընտրեք այլ հավելված"</string>
     <string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Բաց թողնե՞լ նավիգացիայի ուղեցույցը"</string>
diff --git a/quickstep/res/values-iw/strings.xml b/quickstep/res/values-iw/strings.xml
index 1a8311f..0fbb4c7 100644
--- a/quickstep/res/values-iw/strings.xml
+++ b/quickstep/res/values-iw/strings.xml
@@ -111,7 +111,7 @@
     <string name="taskbar_edu_a11y_title" msgid="5417986057866415355">"הסבר על סרגל האפליקציות"</string>
     <string name="taskbar_edu_splitscreen" msgid="5605512479258053350">"כדי להשתמש בשתי אפליקציות בו-זמנית, צריך לגרור אפליקציה לצד"</string>
     <string name="taskbar_edu_stashing" msgid="5645461372669217294">"צריך להחליק לאט למעלה כדי להציג את סרגל האפליקציות"</string>
-    <string name="taskbar_edu_suggestions" msgid="8215044496435527982">"קבלת הצעות לאפליקציות על סמך השימוש השגרתי שלך"</string>
+    <string name="taskbar_edu_suggestions" msgid="8215044496435527982">"אפשר לקבל הצעות לאפליקציות על סמך השימוש השגרתי שלך"</string>
     <string name="taskbar_edu_pinning" msgid="6708550858580071558">"כדי להצמיד את סרגל האפליקציות, לוחצים לחיצה ארוכה על המחיצה"</string>
     <string name="taskbar_edu_features" msgid="3320337287472848162">"פעולות נוספות שאפשר לעשות עם סרגל האפליקציות"</string>
     <string name="taskbar_edu_pinning_title" msgid="210102174154211712">"תמיד להציג את סרגל האפליקציות"</string>
diff --git a/quickstep/res/values-ky/strings.xml b/quickstep/res/values-ky/strings.xml
index 5fa61fb..ee0aaa3 100644
--- a/quickstep/res/values-ky/strings.xml
+++ b/quickstep/res/values-ky/strings.xml
@@ -109,11 +109,11 @@
     <string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Өткрп жиберүү"</string>
     <string name="accessibility_rotate_button" msgid="4771825231336502943">"Экранды буруу"</string>
     <string name="taskbar_edu_a11y_title" msgid="5417986057866415355">"Тапшырмалар тактасы жөнүндө маалымат"</string>
-    <string name="taskbar_edu_splitscreen" msgid="5605512479258053350">"2 колдонмону бир убакта пайдалануу үчүн капталга сүйрөңүз"</string>
+    <string name="taskbar_edu_splitscreen" msgid="5605512479258053350">"2 колдонмону бир убакта пайдалануу үчүн капталга сүйрөйсүз"</string>
     <string name="taskbar_edu_stashing" msgid="5645461372669217294">"Тапшырмалар тактасын көрүү үчүн экранды жай өйдө сүрүңүз"</string>
-    <string name="taskbar_edu_suggestions" msgid="8215044496435527982">"Программаңыздын негизинде сунушталган колдонмолорду алуу"</string>
-    <string name="taskbar_edu_pinning" msgid="6708550858580071558">"Тапшырмалар панелин кадап коюу үчүн бөлгүчтү коё бербей басып туруңуз"</string>
-    <string name="taskbar_edu_features" msgid="3320337287472848162">"Тапшырмалар тактасы менен көбүрөөк нерселерди аткарыңыз"</string>
+    <string name="taskbar_edu_suggestions" msgid="8215044496435527982">"Аракеттериңизге негизделген сунуштарды алып турасыз"</string>
+    <string name="taskbar_edu_pinning" msgid="6708550858580071558">"Тапшырмалар тактасын кадап коюу үчүн бөлгүчтү коё бербей басып турасыз"</string>
+    <string name="taskbar_edu_features" msgid="3320337287472848162">"Тапшырмалар тактасы менен көбүрөөк иш бүтүрөсүз"</string>
     <string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Тапшырмалар панелин ар дайым көрсөтүү"</string>
     <string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Тапшырмалар панелин экрандын ылдый жагында ар дайым көрсөтүү үчүн бөлгүчтү коё бербей басыңыз"</string>
     <string name="taskbar_edu_close" msgid="887022990168191073">"Жабуу"</string>
@@ -129,7 +129,7 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Тапшырмалар панели көрсөтүлдү"</string>
     <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Тапшырмалар панели жашырылды"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Чабыттоо тилкеси"</string>
-    <string name="always_show_taskbar" msgid="3608801276107751229">"Тапшырмалар панелин ар дайым көрсөтүү"</string>
+    <string name="always_show_taskbar" msgid="3608801276107751229">"Такта ар дайым көрүнсүн"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Өтүү режимин өзгөртүү"</string>
     <string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Тапшырмалар панелин бөлгүч"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Жогорку/сол бурчка жылдыруу"</string>
diff --git a/quickstep/res/values-mk/strings.xml b/quickstep/res/values-mk/strings.xml
index 4a3e62b..bf2c798 100644
--- a/quickstep/res/values-mk/strings.xml
+++ b/quickstep/res/values-mk/strings.xml
@@ -109,8 +109,8 @@
     <string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Прескокни"</string>
     <string name="accessibility_rotate_button" msgid="4771825231336502943">"Ротирајте го екранот"</string>
     <string name="taskbar_edu_a11y_title" msgid="5417986057866415355">"Обука за лентата со задачи"</string>
-    <string name="taskbar_edu_splitscreen" msgid="5605512479258053350">"Повлечете апликација настрана за да користите 2 апликации"</string>
-    <string name="taskbar_edu_stashing" msgid="5645461372669217294">"Полека повлечете нагоре за да се прикаже лентата со задачи"</string>
+    <string name="taskbar_edu_splitscreen" msgid="5605512479258053350">"Повлечете апликација настрана за да користите 2 апликации одеднаш"</string>
+    <string name="taskbar_edu_stashing" msgid="5645461372669217294">"Полека повлечете нагоре за да се прикаже „Лентата со задачи“"</string>
     <string name="taskbar_edu_suggestions" msgid="8215044496435527982">"Добивајте предлози за апликации според вашата рутина"</string>
     <string name="taskbar_edu_pinning" msgid="6708550858580071558">"Притиснете долго на разделникот за да ја закачите „Лентата со задачи“"</string>
     <string name="taskbar_edu_features" msgid="3320337287472848162">"Правете сешто со „Лентата со задачи“"</string>
diff --git a/quickstep/res/values-ne/strings.xml b/quickstep/res/values-ne/strings.xml
index fb5ebb3..a911a30 100644
--- a/quickstep/res/values-ne/strings.xml
+++ b/quickstep/res/values-ne/strings.xml
@@ -114,7 +114,7 @@
     <string name="taskbar_edu_suggestions" msgid="8215044496435527982">"आफ्नो रुटिनका आधारमा एपसम्बन्धी सुझावहरू प्राप्त गर्नुहोस्"</string>
     <string name="taskbar_edu_pinning" msgid="6708550858580071558">"टास्कबार पिन गर्न डिभाइडरमा केही बेरसम्म थिच्नुहोस्"</string>
     <string name="taskbar_edu_features" msgid="3320337287472848162">"टास्कबार प्रयोग गरेर अझ धेरै कार्य गर्नुहोस्"</string>
-    <string name="taskbar_edu_pinning_title" msgid="210102174154211712">"टास्कबार सधैँ देखाइयोस्"</string>
+    <string name="taskbar_edu_pinning_title" msgid="210102174154211712">"टास्कबार सधैँ देखाउनुहोस्"</string>
     <string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"आफ्नो स्क्रिनको पुछारमा टास्कबार सधैँ देखाइराख्न डिभाइडर टच एन्ड होल्ड गर्नुहोस्"</string>
     <string name="taskbar_edu_close" msgid="887022990168191073">"बन्द गर्नुहोस्"</string>
     <string name="taskbar_edu_done" msgid="6880178093977704569">"सम्पन्न भयो"</string>
@@ -129,12 +129,12 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"टास्कबार देखाइएको छ"</string>
     <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"टास्कबार लुकाइएको छ"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"नेभिगेसन बार"</string>
-    <string name="always_show_taskbar" msgid="3608801276107751229">"टास्कबार सधैँ देखाइयोस्"</string>
+    <string name="always_show_taskbar" msgid="3608801276107751229">"टास्कबार सधैँ देखाउनुहोस्"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"नेभिगेसन मोड बदल्नुहोस्"</string>
     <string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"टास्कबार डिभाइडर"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"सिरान/बायाँतिर सार्नुहोस्"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"फेद/दायाँतिर सार्नुहोस्"</string>
-    <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{थप # एप देखाइयोस्।}other{थप # वटा एप देखाइयोस्।}}"</string>
+    <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{थप # एप देखाउनुहोस्।}other{थप # वटा एप देखाउनुहोस्।}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> र <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
     <string name="desktop_select_app_toast" msgid="2306057322833956910">"डेस्कटपमा एप हालिँदै छ"</string>
     <string name="desktop_button_close_app_toast" msgid="5283096349579408560">"रद्द गर्नुहोस्"</string>
diff --git a/quickstep/res/values-pa/strings.xml b/quickstep/res/values-pa/strings.xml
index 4b80a2e..b28ce1c 100644
--- a/quickstep/res/values-pa/strings.xml
+++ b/quickstep/res/values-pa/strings.xml
@@ -21,7 +21,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="recent_task_option_pin" msgid="7929860679018978258">"ਪਿੰਨ ਕਰੋ"</string>
     <string name="recent_task_option_freeform" msgid="48863056265284071">"ਫ੍ਰੀਫਾਰਮ"</string>
-    <string name="recents_empty_message" msgid="7040467240571714191">"ਕੋਈ ਹਾਲੀਆ ਆਈਟਮਾਂ ਨਹੀਂ"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"ਕੋਈ ਹਾਲੀਆ ਆਈਟਮ ਨਹੀਂ"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ਐਪ ਵਰਤੋਂ ਦੀਆਂ ਸੈਟਿੰਗਾਂ"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"ਸਭ ਕਲੀਅਰ ਕਰੋ"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"ਹਾਲੀਆ ਐਪਾਂ"</string>
diff --git a/quickstep/res/values-pt-rPT/strings.xml b/quickstep/res/values-pt-rPT/strings.xml
index ce13890..b22b98c 100644
--- a/quickstep/res/values-pt-rPT/strings.xml
+++ b/quickstep/res/values-pt-rPT/strings.xml
@@ -110,7 +110,7 @@
     <string name="accessibility_rotate_button" msgid="4771825231336502943">"Rodar ecrã"</string>
     <string name="taskbar_edu_a11y_title" msgid="5417986057866415355">"Educação da Barra de tarefas"</string>
     <string name="taskbar_edu_splitscreen" msgid="5605512479258053350">"Arraste uma app para o lado para usar 2 apps em simultâneo"</string>
-    <string name="taskbar_edu_stashing" msgid="5645461372669217294">"Deslize lentamente para cima para mostrar a Barra de tarefas"</string>
+    <string name="taskbar_edu_stashing" msgid="5645461372669217294">"Deslize lentamente para cima para ver a Barra de tarefas"</string>
     <string name="taskbar_edu_suggestions" msgid="8215044496435527982">"Receba sugestões de apps baseadas na sua rotina"</string>
     <string name="taskbar_edu_pinning" msgid="6708550858580071558">"Mantenha o divisor premido para fixar a Barra de tarefas"</string>
     <string name="taskbar_edu_features" msgid="3320337287472848162">"Faça mais com a Barra de tarefas"</string>
diff --git a/quickstep/res/values-sk/strings.xml b/quickstep/res/values-sk/strings.xml
index 1865043..0b9a0e3 100644
--- a/quickstep/res/values-sk/strings.xml
+++ b/quickstep/res/values-sk/strings.xml
@@ -97,7 +97,7 @@
     <string name="action_split" msgid="2098009717623550676">"Rozdeliť"</string>
     <string name="action_save_app_pair" msgid="5974823919237645229">"Uložiť pár aplikácií"</string>
     <string name="toast_split_select_app" msgid="8464310533320556058">"Obrazovku rozdelíte klepnutím na inú aplikáciu"</string>
-    <string name="toast_contextual_split_select_app" msgid="433510957123687090">"Na použitie rozdelenej obrazovky vyberte inú aplikáciu"</string>
+    <string name="toast_contextual_split_select_app" msgid="433510957123687090">"Na použitie rozdelenej obrazovky vyberte ďalšiu aplikáciu"</string>
     <string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Zrušiť"</b></string>
     <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Ukončite výber rozdelenej obrazovky"</string>
     <string name="toast_split_app_unsupported" msgid="2360229567007828914">"Na použitie rozd. obrazovky vyberte inú aplikáciu"</string>
diff --git a/quickstep/res/values-sl/strings.xml b/quickstep/res/values-sl/strings.xml
index 4fa37df..1a34502 100644
--- a/quickstep/res/values-sl/strings.xml
+++ b/quickstep/res/values-sl/strings.xml
@@ -129,7 +129,7 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Opravilna vrstica je prikazana"</string>
     <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Opravilna vrstica je skrita"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Vrstica za krmarjenje"</string>
-    <string name="always_show_taskbar" msgid="3608801276107751229">"Stalen prikaz opravilne vrstice"</string>
+    <string name="always_show_taskbar" msgid="3608801276107751229">"Stalen prikaz oprav. vrstice"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Spreminjanje načina navigacije"</string>
     <string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Razdelilnik opravilne vrstice"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Premakni na vrh/levo"</string>
diff --git a/quickstep/res/values-sr/strings.xml b/quickstep/res/values-sr/strings.xml
index 822d25c..269614d 100644
--- a/quickstep/res/values-sr/strings.xml
+++ b/quickstep/res/values-sr/strings.xml
@@ -112,7 +112,7 @@
     <string name="taskbar_edu_splitscreen" msgid="5605512479258053350">"Превуците на страну да бисте користили 2 апликације одједном"</string>
     <string name="taskbar_edu_stashing" msgid="5645461372669217294">"Споро превуците нагоре да бисте видели траку задатака"</string>
     <string name="taskbar_edu_suggestions" msgid="8215044496435527982">"Добијајте предлоге апликација на основу рутине"</string>
-    <string name="taskbar_edu_pinning" msgid="6708550858580071558">"Дуго притискајте разделник да бисте закачили траку задатака"</string>
+    <string name="taskbar_edu_pinning" msgid="6708550858580071558">"Дуго притисните разделник да бисте закачили траку задатака"</string>
     <string name="taskbar_edu_features" msgid="3320337287472848162">"Урадите више помоћу траке задатака"</string>
     <string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Увек приказуј траку задатака"</string>
     <string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Да би трака задатака увек била приказана у дну екрана, додирните и задржите разделник"</string>
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index 0ce1cb8..c0e0587 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -18,8 +18,8 @@
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
 import static android.text.format.DateUtils.formatElapsedTime;
 
-import static com.android.launcher3.LauncherPrefs.nonRestorableItem;
 import static com.android.launcher3.EncryptionType.ENCRYPTED;
+import static com.android.launcher3.LauncherPrefs.nonRestorableItem;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
@@ -65,7 +65,7 @@
 import com.android.launcher3.logging.InstanceIdSequence;
 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
 import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.CollectionInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.pm.UserCache;
@@ -233,7 +233,7 @@
             }
             InstanceId instanceId = new InstanceIdSequence().newInstanceId();
             for (ItemInfo info : itemsIdMap) {
-                FolderInfo parent = getContainer(info, itemsIdMap);
+                CollectionInfo parent = getContainer(info, itemsIdMap);
                 StatsLogCompatManager.writeSnapshot(info.buildProto(parent), instanceId);
             }
             additionalSnapshotEvents(instanceId);
@@ -270,7 +270,7 @@
                         }
 
                         for (ItemInfo info : itemsIdMap) {
-                            FolderInfo parent = getContainer(info, itemsIdMap);
+                            CollectionInfo parent = getContainer(info, itemsIdMap);
                             LauncherAtom.ItemInfo itemInfo = info.buildProto(parent);
                             Log.d(TAG, itemInfo.toString());
                             StatsEvent statsEvent = StatsLogCompatManager.buildStatsEvent(itemInfo,
@@ -293,18 +293,19 @@
         }
     }
 
-    private static FolderInfo getContainer(ItemInfo info, IntSparseArrayMap<ItemInfo> itemsIdMap) {
+    private static CollectionInfo getContainer(
+            ItemInfo info, IntSparseArrayMap<ItemInfo> itemsIdMap) {
         if (info.container > 0) {
             ItemInfo containerInfo = itemsIdMap.get(info.container);
 
-            if (!(containerInfo instanceof FolderInfo)) {
+            if (!(containerInfo instanceof CollectionInfo)) {
                 Log.e(TAG, String.format(
                         "Item info: %s found with invalid container: %s",
                         info,
                         containerInfo));
             }
             // Allow crash to help debug b/173838775
-            return (FolderInfo) containerInfo;
+            return (CollectionInfo) containerInfo;
         }
         return null;
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index c543307..1047374 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -91,6 +91,7 @@
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.AppPairInfo;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -1084,11 +1085,11 @@
             ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key,
                     ActivityOptions.makeBasic());
             mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
-        } else if (tag instanceof FolderInfo fi && fi.itemType == Favorites.ITEM_TYPE_FOLDER) {
+        } else if (tag instanceof FolderInfo) {
             // Tapping an expandable folder icon on Taskbar
             shouldCloseAllOpenViews = false;
             expandFolder((FolderIcon) view);
-        } else if (tag instanceof FolderInfo fi && fi.itemType == Favorites.ITEM_TYPE_APP_PAIR) {
+        } else if (tag instanceof AppPairInfo api) {
             // Tapping an app pair icon on Taskbar
             if (recents != null && recents.isSplitSelectionActive()) {
                 // TODO (b/274835596): Implement "can't split with this" bounce animation
@@ -1096,8 +1097,8 @@
                         Toast.LENGTH_SHORT).show();
             } else {
                 // Else launch the selected app pair
-                launchFromTaskbar(recents, view, fi.contents);
-                mControllers.uiController.onTaskbarIconLaunched(fi);
+                launchFromTaskbar(recents, view, api.getContents());
+                mControllers.uiController.onTaskbarIconLaunched(api);
                 mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
             }
         } else if (tag instanceof WorkspaceItemInfo) {
@@ -1254,7 +1255,7 @@
                     if (findExactPairMatch) {
                         // We did not find the app pair we were looking for, so launch one.
                         recents.getSplitSelectController().getAppPairsController().launchAppPair(
-                                (AppPairIcon) launchingIconView);
+                                (AppPairIcon) launchingIconView, -1 /*cuj*/);
                     } else {
                         startItemInfoActivity(itemInfos.get(0));
                     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index 7cad57b..8dc81cf 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -163,6 +163,10 @@
                 setProviderInsets(provider, layoutParams.gravity, rotation)
             }
         }
+        // Also set the parent providers (i.e. not in paramsForRotation).
+        for (provider in windowLayoutParams.providedInsets) {
+            setProviderInsets(provider, windowLayoutParams.gravity, context.display.rotation)
+        }
         context.notifyUpdateLayoutParams()
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
index ca192c8..4462f20 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
@@ -116,9 +116,9 @@
                 }
             } else if (info instanceof FolderInfo && v instanceof FolderIcon) {
                 FolderInfo fi = (FolderInfo) info;
-                if (fi.contents.stream().anyMatch(matcher)) {
+                if (fi.anyMatch(matcher)) {
                     FolderDotInfo folderDotInfo = new FolderDotInfo();
-                    for (WorkspaceItemInfo si : fi.contents) {
+                    for (WorkspaceItemInfo si : fi.getContents()) {
                         folderDotInfo.addDotInfo(mPopupDataProvider.getDotInfoForItem(si));
                     }
                     ((FolderIcon) v).setDotInfo(folderDotInfo);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 3d58464..689a1ec 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -890,17 +890,11 @@
     }
 
     /**
-     * Should be called when a system gesture starts and settles, so we can defer updating
-     * FLAG_STASHED_IN_APP_IME until after the gesture transition completes.
+     * Should be called when a system gesture starts and settles, so we can remove
+     * FLAG_STASHED_IN_APP_IME while the gesture is in progress.
      */
     public void setSystemGestureInProgress(boolean inProgress) {
         mIsSystemGestureInProgress = inProgress;
-        if (mIsSystemGestureInProgress) {
-            return;
-        }
-
-        // Only update the following flags when system gesture is not in progress.
-        updateStateForFlag(FLAG_STASHED_IN_TASKBAR_ALL_APPS, false);
         setStashedImeState();
     }
 
@@ -952,12 +946,9 @@
                 && !hasAnyFlag(systemUiStateFlags, SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY);
         updateStateForFlag(FLAG_STASHED_DEVICE_LOCKED, isLocked);
 
-        // Only update FLAG_STASHED_IN_APP_IME when system gesture is not in progress.
         mIsImeShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SHOWING);
         mIsImeSwitcherShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SWITCHER_SHOWING);
-
-        if (!mIsSystemGestureInProgress) {
-            updateStateForFlag(FLAG_STASHED_IN_APP_IME, shouldStashForIme());
+        if (updateStateForFlag(FLAG_STASHED_IN_APP_IME, shouldStashForIme())) {
             animDuration = TASKBAR_STASH_DURATION_FOR_IME;
             startDelay = getTaskbarStashStartDelayForIme();
         }
@@ -970,7 +961,8 @@
      *
      * <p>Do not stash if in small screen, with 3 button nav, and in landscape (or seascape).
      * <p>Do not stash if taskbar is transient.
-     * <p>Do not stash if hardware keyboard is attached and taskbar is pinned and IME is docked
+     * <p>Do not stash if hardware keyboard is attached and taskbar is pinned and IME is docked.
+     * <p>Do not stash if a system gesture is started.
      */
     private boolean shouldStashForIme() {
         if (DisplayController.isTransientTaskbar(mActivity)) {
@@ -996,6 +988,11 @@
             return false;
         }
 
+        // Do not stash if a gesture started.
+        if (mIsSystemGestureInProgress) {
+            return false;
+        }
+
         return mIsImeShowing || mIsImeSwitcherShowing;
     }
 
@@ -1007,13 +1004,16 @@
      * @param flag    The flag to update.
      * @param enabled Whether to enable the flag: True will cause the task bar to be stashed /
      *                unstashed.
+     * @return Whether the flag state changed.
      */
-    public void updateStateForFlag(int flag, boolean enabled) {
+    public boolean updateStateForFlag(int flag, boolean enabled) {
+        int oldState = mState;
         if (enabled) {
             mState |= flag;
         } else {
             mState &= ~flag;
         }
+        return mState != oldState;
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index c81bf7a..67b41c6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -18,6 +18,7 @@
 import static android.content.pm.PackageManager.FEATURE_PC;
 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
 
+import static com.android.launcher3.BubbleTextView.DISPLAY_TASKBAR;
 import static com.android.launcher3.Flags.enableCursorHoverStates;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
@@ -52,6 +53,8 @@
 import com.android.launcher3.apppairs.AppPairIcon;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.folder.PreviewBackground;
+import com.android.launcher3.model.data.AppPairInfo;
+import com.android.launcher3.model.data.CollectionInfo;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -282,7 +285,7 @@
         removeView(view);
         view.setOnClickListener(null);
         view.setOnLongClickListener(null);
-        if (!(view.getTag() instanceof FolderInfo)) {
+        if (!(view.getTag() instanceof CollectionInfo)) {
             mActivityContext.getViewCache().recycleView(view.getSourceLayoutResId(), view);
         }
         view.setTag(null);
@@ -316,8 +319,8 @@
             boolean isCollection = false;
             if (hotseatItemInfo.isPredictedItem()) {
                 expectedLayoutResId = R.layout.taskbar_predicted_app_icon;
-            } else if (hotseatItemInfo instanceof FolderInfo fi) {
-                expectedLayoutResId = fi.itemType == ITEM_TYPE_APP_PAIR
+            } else if (hotseatItemInfo instanceof CollectionInfo ci) {
+                expectedLayoutResId = ci.itemType == ITEM_TYPE_APP_PAIR
                         ? R.layout.app_pair_icon
                         : R.layout.folder_icon;
                 isCollection = true;
@@ -345,17 +348,18 @@
 
             if (hotseatView == null) {
                 if (isCollection) {
-                    FolderInfo folderInfo = (FolderInfo) hotseatItemInfo;
+                    CollectionInfo collectionInfo = (CollectionInfo) hotseatItemInfo;
                     switch (hotseatItemInfo.itemType) {
                         case ITEM_TYPE_FOLDER:
                             hotseatView = FolderIcon.inflateFolderAndIcon(
-                                    expectedLayoutResId, mActivityContext, this, folderInfo);
+                                    expectedLayoutResId, mActivityContext, this,
+                                    (FolderInfo) collectionInfo);
                             ((FolderIcon) hotseatView).setTextVisible(false);
                             break;
                         case ITEM_TYPE_APP_PAIR:
                             hotseatView = AppPairIcon.inflateIcon(
-                                    expectedLayoutResId, mActivityContext, this, folderInfo,
-                                    BubbleTextView.DISPLAY_TASKBAR);
+                                    expectedLayoutResId, mActivityContext, this,
+                                    (AppPairInfo) collectionInfo, DISPLAY_TASKBAR);
                             ((AppPairIcon) hotseatView).setTextVisible(false);
                             break;
                         default:
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 4b1963b..1f7f0a7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -813,8 +813,8 @@
      * 3) All Apps button
      */
     public View getFirstIconMatch(Predicate<ItemInfo> matcher) {
-        Predicate<ItemInfo> folderMatcher = ItemInfoMatcher.forFolderMatch(matcher);
-        return mTaskbarView.getFirstMatch(matcher, folderMatcher);
+        Predicate<ItemInfo> collectionMatcher = ItemInfoMatcher.forFolderMatch(matcher);
+        return mTaskbarView.getFirstMatch(matcher, collectionMatcher);
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 75dfe30..10a7ff6 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -21,6 +21,7 @@
 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
 
 import static com.android.app.animation.Interpolators.EMPHASIZED;
+import static com.android.internal.jank.Cuj.CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE;
 import static com.android.launcher3.Flags.enablePredictiveBackGesture;
 import static com.android.launcher3.Flags.enableUnfoldStateAnimation;
 import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.PENDING_SPLIT_SELECT_INFO;
@@ -1338,7 +1339,8 @@
      * Launches two apps as an app pair.
      */
     public void launchAppPair(AppPairIcon appPairIcon) {
-        mSplitSelectStateController.getAppPairsController().launchAppPair(appPairIcon);
+        mSplitSelectStateController.getAppPairsController().launchAppPair(appPairIcon,
+                CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE);
     }
 
     public boolean canStartHomeSafely() {
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index d265918..e078a49 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -61,7 +61,7 @@
 import com.android.launcher3.model.AllAppsList;
 import com.android.launcher3.model.BaseModelUpdateTask;
 import com.android.launcher3.model.BgDataModel;
-import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.CollectionInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.LogConfig;
@@ -375,7 +375,7 @@
                 Executors.MODEL_EXECUTOR.execute(
                         () -> write(event, applyOverwrites(mItemInfo.buildProto())));
             } else {
-                // Item is inside the folder, fetch folder info in a BG thread
+                // Item is inside a collection, fetch collection info in a BG thread
                 // and then write to StatsLog.
                 appState.getModel().enqueueModelUpdateTask(
                         new BaseModelUpdateTask() {
@@ -383,8 +383,9 @@
                             public void execute(@NonNull final LauncherAppState app,
                                     @NonNull final BgDataModel dataModel,
                                     @NonNull final AllAppsList apps) {
-                                FolderInfo folderInfo = dataModel.folders.get(mItemInfo.container);
-                                write(event, applyOverwrites(mItemInfo.buildProto(folderInfo)));
+                                CollectionInfo collectionInfo =
+                                        dataModel.collections.get(mItemInfo.container);
+                                write(event, applyOverwrites(mItemInfo.buildProto(collectionInfo)));
                             }
                         });
             }
diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java
index 757f1f8..f4da867 100644
--- a/quickstep/src/com/android/quickstep/util/AppPairsController.java
+++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java
@@ -19,6 +19,7 @@
 
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 
+import static com.android.internal.jank.Cuj.CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_PAIR_LAUNCH;
 import static com.android.launcher3.model.data.AppInfo.PACKAGE_KEY_COMPARATOR;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -40,6 +41,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.internal.jank.Cuj;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
@@ -51,7 +53,7 @@
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.AppPairInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.taskbar.TaskbarActivityContext;
@@ -62,6 +64,7 @@
 import com.android.quickstep.views.GroupedTaskView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
 
 import java.util.Arrays;
@@ -112,6 +115,7 @@
      * well on trampoline apps).
      */
     public void saveAppPair(GroupedTaskView gtv) {
+        InteractionJankMonitorWrapper.begin(gtv, Cuj.CUJ_LAUNCHER_SAVE_APP_PAIR);
         TaskView.TaskIdAttributeContainer[] attributes = gtv.getTaskIdAttributeContainers();
         WorkspaceItemInfo recentsInfo1 = attributes[0].getItemInfo();
         WorkspaceItemInfo recentsInfo2 = attributes[1].getItemInfo();
@@ -145,30 +149,28 @@
 
         app1.rank = encodeRank(SPLIT_POSITION_TOP_OR_LEFT, snapPosition);
         app2.rank = encodeRank(SPLIT_POSITION_BOTTOM_OR_RIGHT, snapPosition);
-        FolderInfo newAppPair = FolderInfo.createAppPair(app1, app2);
-
-        if (newAppPair.contents.size() != 2) {
-            // if app pair doesn't have exactly 2 members, log an error and do not create the app
-            // pair.
-            Log.wtf(TAG,
-                    "tried to save an app pair with " + newAppPair.contents.size() + " members");
-            return;
-        }
+        AppPairInfo newAppPair = new AppPairInfo(app1, app2);
 
         IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache();
         MODEL_EXECUTOR.execute(() -> {
-            newAppPair.contents.forEach(member -> {
+            newAppPair.getContents().forEach(member -> {
                 member.title = "";
                 member.bitmap = iconCache.getDefaultIcon(newAppPair.user);
                 iconCache.getTitleAndIcon(member, member.usingLowResIcon());
             });
-            newAppPair.title = getDefaultTitle(newAppPair.contents.get(0).title,
-                    newAppPair.contents.get(1).title);
+            newAppPair.title = getDefaultTitle(newAppPair.getFirstApp().title,
+                    newAppPair.getSecondApp().title);
             MAIN_EXECUTOR.execute(() -> {
                 LauncherAccessibilityDelegate delegate =
                         Launcher.getLauncher(mContext).getAccessibilityDelegate();
                 if (delegate != null) {
-                    delegate.addToWorkspace(newAppPair, true);
+                    delegate.addToWorkspace(newAppPair, true, (success) -> {
+                        if (success) {
+                            InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_SAVE_APP_PAIR);
+                        } else {
+                            InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_SAVE_APP_PAIR);
+                        }
+                    });
                     mStatsLogManager.logger().withItemInfo(newAppPair)
                             .log(StatsLogManager.LauncherEvent.LAUNCHER_APP_PAIR_SAVE);
                 }
@@ -179,12 +181,18 @@
     /**
      * Launches an app pair by searching the RecentsModel for running instances of each app, and
      * staging either those running instances or launching the apps as new Intents.
+     *
+     * @param cuj Should be an integer from {@link Cuj} or -1 if no CUJ needs to be logged for jank
+     *            monitoring
      */
-    public void launchAppPair(AppPairIcon appPairIcon) {
-        WorkspaceItemInfo app1 = appPairIcon.getInfo().contents.get(0);
-        WorkspaceItemInfo app2 = appPairIcon.getInfo().contents.get(1);
+    public void launchAppPair(AppPairIcon appPairIcon, int cuj) {
+        WorkspaceItemInfo app1 = appPairIcon.getInfo().getFirstApp();
+        WorkspaceItemInfo app2 = appPairIcon.getInfo().getSecondApp();
         ComponentKey app1Key = new ComponentKey(app1.getTargetComponent(), app1.user);
         ComponentKey app2Key = new ComponentKey(app2.getTargetComponent(), app2.user);
+        mSplitSelectStateController.setLaunchingCuj(cuj);
+        InteractionJankMonitorWrapper.begin(appPairIcon, cuj);
+
         mSplitSelectStateController.findLastActiveTasksAndRunCallback(
                 Arrays.asList(app1Key, app2Key),
                 false /* findExactPairMatch */,
@@ -343,7 +351,8 @@
                                 && !lastActiveTasksOfAppPair.contains(runningTaskId2)) {
                             // Neither A nor B are on screen, so just launch a new app pair
                             // normally.
-                            launchAppPair(launchingIconView);
+                            launchAppPair(launchingIconView,
+                                    CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR);
                         } else {
                             // Exactly one app (A or B) is on-screen, so we have to launch the other
                             // on the appropriate side.
@@ -388,7 +397,8 @@
 
                         if (!task1IsOnScreen && !task2IsOnScreen) {
                             // Neither App A nor App B are on-screen, launch the app pair normally.
-                            launchAppPair(launchingIconView);
+                            launchAppPair(launchingIconView,
+                                    CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR);
                         } else {
                             // Either A or B is on-screen, so launch the other on the appropriate
                             // side.
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index 8f5c9c1..6b27004 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -659,8 +659,8 @@
 
         // Create a new floating view in Launcher, positioned above the launching icon
         val drawableArea = launchingIconView.iconDrawableArea
-        val appIcon1 = launchingIconView.info.contents[0].newIcon(launchingIconView.context)
-        val appIcon2 = launchingIconView.info.contents[1].newIcon(launchingIconView.context)
+        val appIcon1 = launchingIconView.info.getFirstApp().newIcon(launchingIconView.context)
+        val appIcon2 = launchingIconView.info.getSecondApp().newIcon(launchingIconView.context)
         appIcon1.setBounds(0, 0, dp.iconSizePx, dp.iconSizePx)
         appIcon2.setBounds(0, 0, dp.iconSizePx, dp.iconSizePx)
         val floatingView =
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 8e2520e..44da8b1 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -104,6 +104,7 @@
 import com.android.systemui.animation.RemoteAnimationRunnerCompat;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
 import com.android.wm.shell.splitscreen.ISplitSelectListener;
 
@@ -151,6 +152,12 @@
     /** True when the first selected split app is being launched in fullscreen. */
     private boolean mLaunchingFirstAppFullscreen;
 
+    /**
+     * Should be a constant from {@link com.android.internal.jank.Cuj} or -1, does not need to be
+     * set for all launches.
+     */
+    private int mLaunchCuj = -1;
+
     private FloatingTaskView mFirstFloatingTaskView;
     private SplitInstructionsView mSplitInstructionsView;
 
@@ -707,6 +714,10 @@
         return mSplitAnimationController;
     }
 
+    public void setLaunchingCuj(int launchCuj) {
+        mLaunchCuj = launchCuj;
+    }
+
     /**
      * Requires Shell Transitions
      */
@@ -850,6 +861,11 @@
         mSplitInstructionsView = null;
         mLaunchingFirstAppFullscreen = false;
 
+        if (mLaunchCuj != -1) {
+            InteractionJankMonitorWrapper.end(mLaunchCuj);
+        }
+        mLaunchCuj = -1;
+
         if (mSessionInstanceIds != null) {
             mStatsLogManager.logger()
                     .withInstanceId(mSessionInstanceIds.second)
diff --git a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
index 445a540..e068301 100644
--- a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
@@ -16,7 +16,6 @@
 
 package com.android.quickstep.util;
 
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
 
@@ -44,7 +43,7 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.AppPairInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -126,7 +125,7 @@
             intent = appInfo.intent;
             user = appInfo.user;
             bitmapInfo = appInfo.bitmap;
-        } else if (tag instanceof FolderInfo fi && fi.itemType == ITEM_TYPE_APP_PAIR) {
+        } else if (tag instanceof AppPairInfo) {
             // Prompt the user to select something else by wiggling the instructions view
             mController.getSplitInstructionsView().goBoing();
             return true;
diff --git a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
index 6a59ab4..a3e5a35 100644
--- a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
+++ b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
@@ -16,10 +16,12 @@
 
 package com.android.quickstep.views;
 
+import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_CANCEL_BUTTON;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
@@ -33,12 +35,14 @@
 import androidx.dynamicanimation.animation.SpringForce;
 
 import com.android.app.animation.Interpolators;
-import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.statemanager.BaseState;
+import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.states.StateAnimationConfig;
 import com.android.quickstep.util.SplitSelectStateController;
 
 /**
@@ -51,6 +55,7 @@
 public class SplitInstructionsView extends LinearLayout {
     private static final int BOUNCE_DURATION = 250;
     private static final float BOUNCE_HEIGHT = 20;
+    private static final int DURATION_DEFAULT_SPLIT_DISMISS = 350;
 
     private final StatefulActivity mLauncher;
     public boolean mIsCurrentlyAnimating = false;
@@ -137,9 +142,24 @@
         SplitSelectStateController splitSelectController =
                 ((RecentsView) mLauncher.getOverviewPanel()).getSplitSelectController();
 
-        splitSelectController.getSplitAnimationController().playPlaceholderDismissAnim(mLauncher,
-                LAUNCHER_SPLIT_SELECTION_EXIT_CANCEL_BUTTON);
-        mLauncher.getStateManager().goToState(LauncherState.NORMAL);
+        StateManager stateManager = mLauncher.getStateManager();
+        BaseState startState = stateManager.getState();
+        long duration = startState.getTransitionDuration(mLauncher, false);
+        if (duration == 0) {
+            // Case where we're in contextual on workspace (NORMAL), which by default has 0
+            // transition duration
+            duration = DURATION_DEFAULT_SPLIT_DISMISS;
+        }
+        StateAnimationConfig config = new StateAnimationConfig();
+        config.duration = duration;
+        AnimatorSet stateAnim = stateManager.createAtomicAnimation(
+                startState, NORMAL, config);
+        AnimatorSet dismissAnim = splitSelectController.getSplitAnimationController()
+                .createPlaceholderDismissAnim(mLauncher,
+                        LAUNCHER_SPLIT_SELECTION_EXIT_CANCEL_BUTTON, duration);
+        stateAnim.play(dismissAnim);
+        stateManager.setCurrentAnimation(stateAnim, NORMAL);
+        stateAnim.start();
     }
 
     void ensureProperRotation() {
diff --git a/quickstep/tests/src/com/android/quickstep/util/AppPairsControllerTest.kt b/quickstep/tests/src/com/android/quickstep/util/AppPairsControllerTest.kt
index 510faf6..adaf7ff 100644
--- a/quickstep/tests/src/com/android/quickstep/util/AppPairsControllerTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/util/AppPairsControllerTest.kt
@@ -105,7 +105,7 @@
         whenever(mockTopTaskTracker.getCachedTopTask(any())).thenReturn(mockCachedTaskInfo)
         whenever(mockTask1.getKey()).thenReturn(mockTaskKey1)
         whenever(mockTask2.getKey()).thenReturn(mockTaskKey2)
-        doNothing().whenever(spyAppPairsController).launchAppPair(any())
+        doNothing().whenever(spyAppPairsController).launchAppPair(any(), any())
         doNothing()
             .whenever(spyAppPairsController)
             .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull())
@@ -210,7 +210,7 @@
         callback.accept(arrayOf(mockTask1, mockTask2))
 
         // Verify that launchAppPair and launchToSide were never called
-        verify(spyAppPairsController, never()).launchAppPair(any())
+        verify(spyAppPairsController, never()).launchAppPair(any(), any())
         verify(spyAppPairsController, never())
             .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull())
     }
@@ -234,7 +234,7 @@
         callback.accept(arrayOf(mockTask1, mockTask2))
 
         // Verify that launchToSide was called with the correct arguments
-        verify(spyAppPairsController, never()).launchAppPair(any())
+        verify(spyAppPairsController, never()).launchAppPair(any(), any())
         verify(spyAppPairsController, times(1))
             .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_BOTTOM_OR_RIGHT))
     }
@@ -258,7 +258,7 @@
         callback.accept(arrayOf(mockTask1, mockTask2))
 
         // Verify that launchToSide was called with the correct arguments
-        verify(spyAppPairsController, never()).launchAppPair(any())
+        verify(spyAppPairsController, never()).launchAppPair(any(), any())
         verify(spyAppPairsController, times(1))
             .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_TOP_OR_LEFT))
     }
@@ -282,7 +282,7 @@
         callback.accept(arrayOf(mockTask1, mockTask2))
 
         // Verify that launchToSide was called with the correct arguments
-        verify(spyAppPairsController, never()).launchAppPair(any())
+        verify(spyAppPairsController, never()).launchAppPair(any(), any())
         verify(spyAppPairsController, times(1))
             .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_BOTTOM_OR_RIGHT))
     }
@@ -306,7 +306,7 @@
         callback.accept(arrayOf(mockTask1, mockTask2))
 
         // Verify that launchToSide was called with the correct arguments
-        verify(spyAppPairsController, never()).launchAppPair(any())
+        verify(spyAppPairsController, never()).launchAppPair(any(), any())
         verify(spyAppPairsController, times(1))
             .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_TOP_OR_LEFT))
     }
@@ -330,7 +330,7 @@
         callback.accept(arrayOf(mockTask1, mockTask2))
 
         // Verify that launchAppPair was called
-        verify(spyAppPairsController, times(1)).launchAppPair(any())
+        verify(spyAppPairsController, times(1)).launchAppPair(any(), any())
         verify(spyAppPairsController, never())
             .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull())
     }
@@ -354,7 +354,7 @@
         callback.accept(arrayOf(mockTask1, mockTask2))
 
         // Verify that launchToSide was called with the correct arguments
-        verify(spyAppPairsController, never()).launchAppPair(any())
+        verify(spyAppPairsController, never()).launchAppPair(any(), any())
         verify(spyAppPairsController, times(1))
             .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_BOTTOM_OR_RIGHT))
     }
@@ -378,7 +378,7 @@
         callback.accept(arrayOf(mockTask1, mockTask2))
 
         // Verify that launchToSide was called with the correct arguments
-        verify(spyAppPairsController, never()).launchAppPair(any())
+        verify(spyAppPairsController, never()).launchAppPair(any(), any())
         verify(spyAppPairsController, times(1))
             .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_TOP_OR_LEFT))
     }
@@ -402,7 +402,7 @@
         callback.accept(arrayOf(mockTask1, mockTask2))
 
         // Verify that launchAppPair was called
-        verify(spyAppPairsController, times(1)).launchAppPair(any())
+        verify(spyAppPairsController, times(1)).launchAppPair(any(), any())
         verify(spyAppPairsController, never())
             .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull())
     }
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index b99ec65..a3b80a6 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -82,7 +82,7 @@
     <string name="all_apps_button_work_label" msgid="7270707118948892488">"Lista poslovnih aplikacija"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Ukloni"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Deinstaliraj"</string>
-    <string name="app_info_drop_target_label" msgid="692894985365717661">"Infor. o aplikaciji"</string>
+    <string name="app_info_drop_target_label" msgid="692894985365717661">"Podaci o aplikaciji"</string>
     <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Instaliraj na privatni"</string>
     <string name="install_drop_target_label" msgid="2539096853673231757">"Instaliraj"</string>
     <string name="dismiss_prediction_label" msgid="3357562989568808658">"Ne predlaži aplikaciju"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index eaa283c..6825237 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -189,7 +189,7 @@
     <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Bloqueja o desbloqueja Espai privat"</string>
     <string name="ps_container_lock_title" msgid="2640257399982364682">"Bloqueja"</string>
     <string name="ps_container_transition" msgid="8667331812048014412">"Canvia a Espai privat"</string>
-    <string name="ps_add_button_label" msgid="8611055839242385935">"Instal·la aplicacions"</string>
+    <string name="ps_add_button_label" msgid="8611055839242385935">"Instal·la apps"</string>
     <string name="ps_add_button_content_description" msgid="3254274107740952556">"Instal·la les aplicacions a Espai privat"</string>
     <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Menú addicional"</string>
 </resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index ebc7e20..27cd9e5 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -189,7 +189,7 @@
     <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Zamknout/odemknout soukromý prostor"</string>
     <string name="ps_container_lock_title" msgid="2640257399982364682">"Zamknout"</string>
     <string name="ps_container_transition" msgid="8667331812048014412">"Převádění soukromého prostoru"</string>
-    <string name="ps_add_button_label" msgid="8611055839242385935">"Instalace aplikací"</string>
+    <string name="ps_add_button_label" msgid="8611055839242385935">"Instalovat aplikace"</string>
     <string name="ps_add_button_content_description" msgid="3254274107740952556">"Instalovat aplikace do soukromého prostoru"</string>
     <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Rozbalovací nabídka"</string>
 </resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 176bfe3..a9be411 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -189,7 +189,7 @@
     <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Bloquear/Desbloquear espacio privado"</string>
     <string name="ps_container_lock_title" msgid="2640257399982364682">"Bloquear"</string>
     <string name="ps_container_transition" msgid="8667331812048014412">"Cambiar a espacio privado"</string>
-    <string name="ps_add_button_label" msgid="8611055839242385935">"Descargar aplicaciones"</string>
+    <string name="ps_add_button_label" msgid="8611055839242385935">"Descarg. apps"</string>
     <string name="ps_add_button_content_description" msgid="3254274107740952556">"Descargar aplicaciones en el espacio privado"</string>
     <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Desplegable"</string>
 </resources>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index f269c25..16ee5d7 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -113,7 +113,7 @@
     <string name="app_pair_name_format" msgid="8134106404716224054">"Aplikazio parea: <xliff:g id="APP1">%1$s</xliff:g> eta <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Horma-papera eta estiloa"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Editatu orri nagusia"</string>
-    <string name="settings_button_text" msgid="8873672322605444408">"Orri nagusiaren ezarpenak"</string>
+    <string name="settings_button_text" msgid="8873672322605444408">"Orri nagusiko ezarpenak"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Administratzaileak desgaitu du"</string>
     <string name="allow_rotation_title" msgid="7222049633713050106">"Eman orri nagusia biratzeko baimena"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Telefonoa biratzean"</string>
@@ -189,7 +189,7 @@
     <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Blokeatu/Desblokeatu eremu pribatua"</string>
     <string name="ps_container_lock_title" msgid="2640257399982364682">"Blokeatu"</string>
     <string name="ps_container_transition" msgid="8667331812048014412">"Eremu pribaturako trantsizioa"</string>
-    <string name="ps_add_button_label" msgid="8611055839242385935">"Aplikazioak instalatu"</string>
+    <string name="ps_add_button_label" msgid="8611055839242385935">"Instalatu aplikazioak"</string>
     <string name="ps_add_button_content_description" msgid="3254274107740952556">"Instalatu aplikazioak eremu pribatuan"</string>
     <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Luzapena"</string>
 </resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index bab8c3e..f680305 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -189,7 +189,7 @@
     <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Verrouiller/Déverrouiller Espace privé"</string>
     <string name="ps_container_lock_title" msgid="2640257399982364682">"Verrouiller"</string>
     <string name="ps_container_transition" msgid="8667331812048014412">"Transition vers Espace privé"</string>
-    <string name="ps_add_button_label" msgid="8611055839242385935">"Installer des applis"</string>
+    <string name="ps_add_button_label" msgid="8611055839242385935">"Installer applis"</string>
     <string name="ps_add_button_content_description" msgid="3254274107740952556">"Installer des applis dans l\'espace privé"</string>
     <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Dépassement"</string>
 </resources>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index 15c2a40..2339265 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -29,7 +29,7 @@
     <string name="home_screen" msgid="5629429142036709174">"Inicio"</string>
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantalla dividida"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Información da aplicación para %1$s"</string>
-    <string name="save_app_pair" msgid="5647523853662686243">"Gardar parella de aplicacións"</string>
+    <string name="save_app_pair" msgid="5647523853662686243">"Gardar parella de apps"</string>
     <string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"O dispositivo non admite este emparellamento de aplicacións"</string>
     <string name="app_pair_needs_unfold" msgid="4588897528143807002">"Desprega o dispositivo para usar este emparellamento de aplicacións"</string>
@@ -189,7 +189,7 @@
     <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Bloquear ou desbloquear o espazo privado"</string>
     <string name="ps_container_lock_title" msgid="2640257399982364682">"Bloquear"</string>
     <string name="ps_container_transition" msgid="8667331812048014412">"Transición ao espazo privado"</string>
-    <string name="ps_add_button_label" msgid="8611055839242385935">"Instalar as aplicacións"</string>
+    <string name="ps_add_button_label" msgid="8611055839242385935">"Instalar apps"</string>
     <string name="ps_add_button_content_description" msgid="3254274107740952556">"Instalar as aplicacións no espazo privado"</string>
     <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Menú adicional"</string>
 </resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index f40252b..9db31ac 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -189,7 +189,7 @@
     <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Zaključavanje/otključavanje privatnog prostora"</string>
     <string name="ps_container_lock_title" msgid="2640257399982364682">"Zaključavanje"</string>
     <string name="ps_container_transition" msgid="8667331812048014412">"Prelazak na privatni prostor"</string>
-    <string name="ps_add_button_label" msgid="8611055839242385935">"Instaliranje aplikacija"</string>
+    <string name="ps_add_button_label" msgid="8611055839242385935">"Instalirajte aplikacije"</string>
     <string name="ps_add_button_content_description" msgid="3254274107740952556">"Instaliranje aplikacija u privatni prostor"</string>
     <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Dodatni izbornik"</string>
 </resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 6fc5f28..db8f961 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -189,7 +189,7 @@
     <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Privát terület zárolása/zárolásának feloldása"</string>
     <string name="ps_container_lock_title" msgid="2640257399982364682">"Zárolás"</string>
     <string name="ps_container_transition" msgid="8667331812048014412">"Átállás privát területre…"</string>
-    <string name="ps_add_button_label" msgid="8611055839242385935">"Alkalmazástelepítés"</string>
+    <string name="ps_add_button_label" msgid="8611055839242385935">"App telepítése"</string>
     <string name="ps_add_button_content_description" msgid="3254274107740952556">"Alkalmazások telepítése magánterületre"</string>
     <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Túlcsordulás"</string>
 </resources>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index fdb1b1e..71b9ce5 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -184,9 +184,9 @@
     <string name="remote_action_failed" msgid="1383965239183576790">"Չհաջողվեց կատարել գործողությունը (<xliff:g id="WHAT">%1$s</xliff:g>)"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Անձնական տարածք"</string>
     <string name="private_space_secondary_label" msgid="611902414159280263">"Անձնական հավելվածները պահեք կողպված և թաքցված"</string>
-    <string name="ps_container_title" msgid="4391796149519594205">"Անձնական"</string>
+    <string name="ps_container_title" msgid="4391796149519594205">"Մասնավոր"</string>
     <string name="ps_container_settings" msgid="6059734123353320479">"Անձնական տարածքի կարգավորումներ"</string>
-    <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Կողպել/ապակողպել անձնական տարածքը"</string>
+    <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Կողպել/ապակողպել մասնավոր տարածքը"</string>
     <string name="ps_container_lock_title" msgid="2640257399982364682">"Կողպում"</string>
     <string name="ps_container_transition" msgid="8667331812048014412">"Անցում մասնավոր տարածք"</string>
     <string name="ps_add_button_label" msgid="8611055839242385935">"Հավելվածների տեղադրում"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 58139c6..0fdae96 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -32,7 +32,7 @@
     <string name="save_app_pair" msgid="5647523853662686243">"Simpan pasangan aplikasi"</string>
     <string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Pasangan aplikasi ini tidak didukung di perangkat ini"</string>
-    <string name="app_pair_needs_unfold" msgid="4588897528143807002">"Buka perangkat untuk menggunakan pasangan aplikasi ini"</string>
+    <string name="app_pair_needs_unfold" msgid="4588897528143807002">"Bentangkan perangkat untuk menggunakan pasangan aplikasi ini"</string>
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Sentuh lama untuk memindahkan widget."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Ketuk dua kali &amp; tahan untuk memindahkan widget atau gunakan tindakan khusus."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
@@ -189,7 +189,7 @@
     <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Kunci/Buka Kunci Ruang Pribadi"</string>
     <string name="ps_container_lock_title" msgid="2640257399982364682">"Kunci"</string>
     <string name="ps_container_transition" msgid="8667331812048014412">"Ruang Pribadi Bertransisi"</string>
-    <string name="ps_add_button_label" msgid="8611055839242385935">"Menginstal aplikasi"</string>
+    <string name="ps_add_button_label" msgid="8611055839242385935">"Instal aplikasi"</string>
     <string name="ps_add_button_content_description" msgid="3254274107740952556">"Instal aplikasi ke Ruang Pribadi"</string>
     <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Menu tambahan"</string>
 </resources>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index b75f61f..51c2e52 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -186,10 +186,10 @@
     <string name="private_space_secondary_label" msgid="611902414159280263">"Haltu einkaforritum læstum og földum"</string>
     <string name="ps_container_title" msgid="4391796149519594205">"Lokað"</string>
     <string name="ps_container_settings" msgid="6059734123353320479">"Stillingar einkarýmis"</string>
-    <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Læsaeinkarými/taka einkarými úr lás"</string>
+    <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Læsa leynirými/taka leynirými úr lás"</string>
     <string name="ps_container_lock_title" msgid="2640257399982364682">"Læsa"</string>
     <string name="ps_container_transition" msgid="8667331812048014412">"Einkarými að breytast"</string>
     <string name="ps_add_button_label" msgid="8611055839242385935">"Setja upp forrit"</string>
-    <string name="ps_add_button_content_description" msgid="3254274107740952556">"Setja upp forrit í einkarými"</string>
+    <string name="ps_add_button_content_description" msgid="3254274107740952556">"Setja upp forrit í leynirými"</string>
     <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Yfirflæði"</string>
 </resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index c12e58f..5b3e221 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -123,7 +123,7 @@
     <string name="title_missing_notification_access" msgid="7503287056163941064">"通知へのアクセス権限が必要"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"通知ドットを表示するには、「<xliff:g id="NAME">%1$s</xliff:g>」のアプリ通知を ON にしてください"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"設定を変更"</string>
-    <string name="notification_dots_service_title" msgid="4284221181793592871">"通知ドットの表示"</string>
+    <string name="notification_dots_service_title" msgid="4284221181793592871">"通知ドットを表示"</string>
     <string name="developer_options_title" msgid="700788437593726194">"開発者向けオプション"</string>
     <string name="auto_add_shortcuts_label" msgid="4926805029653694105">"ホーム画面にアプリのアイコンを追加"</string>
     <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"新しいアプリをダウンロードしたときに自動で追加します"</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 05736a5..00041fc 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -189,7 +189,7 @@
     <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"သီးသန့်ချတ်ခန်း လော့ခ်ချ/ဖွင့်ရန်"</string>
     <string name="ps_container_lock_title" msgid="2640257399982364682">"လော့ခ်ချခြင်း"</string>
     <string name="ps_container_transition" msgid="8667331812048014412">"သီးသန့်ချတ်ခန်း အပြောင်းအလဲ"</string>
-    <string name="ps_add_button_label" msgid="8611055839242385935">"အက်ပ်များ ထည့်သွင်းခြင်း"</string>
+    <string name="ps_add_button_label" msgid="8611055839242385935">"အက်ပ်ထည့်ခြင်း"</string>
     <string name="ps_add_button_content_description" msgid="3254274107740952556">"‘သီးသန့်နေရာ’ တွင် အက်ပ်များ ထည့်သွင်းနိုင်သည်"</string>
     <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"မီနူးအပို"</string>
 </resources>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index d9b4efd..9a8ade5 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -78,25 +78,25 @@
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"मन पर्ने ट्रे अब कुनै ठाँउ छैन"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"एपको सूची"</string>
     <string name="all_apps_search_results" msgid="5889367432531296759">"खोज परिणामहरू"</string>
-    <string name="all_apps_button_personal_label" msgid="1315764287305224468">"व्यक्तिगत अनुप्रयोगहरूको सूची"</string>
-    <string name="all_apps_button_work_label" msgid="7270707118948892488">"कार्यसम्बन्धी अनुप्रयोगहरूको सूची"</string>
+    <string name="all_apps_button_personal_label" msgid="1315764287305224468">"व्यक्तिगत एपहरूको सूची"</string>
+    <string name="all_apps_button_work_label" msgid="7270707118948892488">"कार्यसम्बन्धी एपहरूको सूची"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"हटाउनुहोस्"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"अनइन्स्टल गर्नुहोस्"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"एपसम्बन्धी जानकारी"</string>
     <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"निजी प्रोफाइलमा इन्स्टल गर्नुहोस्"</string>
     <string name="install_drop_target_label" msgid="2539096853673231757">"स्थापना गर्नुहोस्"</string>
-    <string name="dismiss_prediction_label" msgid="3357562989568808658">"एप सिफारिस नगरियोस्"</string>
+    <string name="dismiss_prediction_label" msgid="3357562989568808658">"एप सिफारिस नगर्नुहोस्"</string>
     <string name="pin_prediction" msgid="4196423321649756498">"सिफारिस गरिएको एप पिन गर्नुहोस्"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"सर्टकट स्थापना गर्नेहोस्"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"प्रयोगकर्ताको हस्तक्षेप बिना एउटा एपलाई सर्टकटमा थप्नको लागि अनुमति दिनुहोस्।"</string>
-    <string name="permlab_read_settings" msgid="5136500343007704955">"होम स्क्रिनका सेटिङ र सर्टकटहरू रिड गरियोस्"</string>
+    <string name="permlab_read_settings" msgid="5136500343007704955">"होम स्क्रिनका सेटिङ र सर्टकटहरू रिड गर्नुहोस्"</string>
     <string name="permdesc_read_settings" msgid="4208061150510996676">"एपलाई होम स्क्रिनबाट सेटिङ र सर्टकटहरू रिड गर्ने अनुमति दिन्छ।"</string>
-    <string name="permlab_write_settings" msgid="4820028712156303762">"होम स्क्रिनका सेटिङ र सर्टकटहरू राइट गरियोस्"</string>
+    <string name="permlab_write_settings" msgid="4820028712156303762">"होम स्क्रिनका सेटिङ र सर्टकटहरू राइट गर्नुहोस्"</string>
     <string name="permdesc_write_settings" msgid="726859348127868466">"एपलाई होम स्क्रिनबाट सेटिङ र सर्टकट बदल्ने अनुमति दिन्छ"</string>
     <string name="gadget_error_text" msgid="740356548025791839">"विजेट लोड गर्न सकिएन"</string>
     <string name="gadget_setup_text" msgid="8348374825537681407">"विजेटका सेटिङ"</string>
     <string name="gadget_complete_setup_text" msgid="309040266978007925">"सेटअप पूरा गर्न ट्याप गर्नुहोस्"</string>
-    <string name="uninstall_system_app_text" msgid="4172046090762920660">"यो प्रणाली एप हो र यसलाई स्थापना रद्द गर्न सकिँदैन।"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"यो सिस्टम एप हो र यसलाई स्थापना रद्द गर्न सकिँदैन।"</string>
     <string name="folder_hint_text" msgid="5174843001373488816">"नाम सम्पादन गर्नुहोस्"</string>
     <string name="disabled_app_label" msgid="6673129024321402780">"असक्षम पारिएको <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="dotted_app_label" msgid="1865617679843363410">"{count,plural, =1{{app_name} सँग सम्बन्धित # सूचना छ}other{{app_name} सँग सम्बन्धित # वटा सूचना छन्}}"</string>
@@ -125,7 +125,7 @@
     <string name="title_change_settings" msgid="1376365968844349552">"सेटिङहरू बदल्नुहोस्"</string>
     <string name="notification_dots_service_title" msgid="4284221181793592871">"नोटिफिकेसन डट देखाउनुहोस्"</string>
     <string name="developer_options_title" msgid="700788437593726194">"विकासकर्ताका लागि उपलब्ध विकल्पहरू"</string>
-    <string name="auto_add_shortcuts_label" msgid="4926805029653694105">"एपका आइकनहरू होम स्क्रिनमा राखियोस्"</string>
+    <string name="auto_add_shortcuts_label" msgid="4926805029653694105">"एपका आइकनहरू होम स्क्रिनमा राख्नुहोस्"</string>
     <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"नयाँ एपका लागि"</string>
     <string name="package_state_unknown" msgid="7592128424511031410">"अज्ञात"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"हटाउनुहोस्"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index e382ba9..d67df82 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -123,7 +123,7 @@
     <string name="title_missing_notification_access" msgid="7503287056163941064">"Wymagany jest dostęp do powiadomień"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"Aby pokazywać kropki powiadomień, włącz powiadomienia aplikacji <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"Zmień ustawienia"</string>
-    <string name="notification_dots_service_title" msgid="4284221181793592871">"Pokaż kropki powiadomień"</string>
+    <string name="notification_dots_service_title" msgid="4284221181793592871">"Pokazuj kropki powiadomień"</string>
     <string name="developer_options_title" msgid="700788437593726194">"Opcje programisty"</string>
     <string name="auto_add_shortcuts_label" msgid="4926805029653694105">"Dodawaj ikony aplikacji do ekranu głównego"</string>
     <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"W przypadku nowych aplikacji"</string>
@@ -189,7 +189,7 @@
     <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Zablokuj/odblokuj obszar prywatny"</string>
     <string name="ps_container_lock_title" msgid="2640257399982364682">"Zablokuj"</string>
     <string name="ps_container_transition" msgid="8667331812048014412">"Przenoszenie obszaru prywatnego"</string>
-    <string name="ps_add_button_label" msgid="8611055839242385935">"Instalowanie aplikacji"</string>
+    <string name="ps_add_button_label" msgid="8611055839242385935">"Instaluj aplikacje"</string>
     <string name="ps_add_button_content_description" msgid="3254274107740952556">"Zainstaluj aplikacje w przestrzeni prywatnej"</string>
     <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Rozwiń menu"</string>
 </resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index da08937..2eb3567 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -187,7 +187,7 @@
     <string name="ps_container_title" msgid="4391796149519594205">"Privado"</string>
     <string name="ps_container_settings" msgid="6059734123353320479">"Definições do espaço privado"</string>
     <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Bloquear/desbloquear espaço privado"</string>
-    <string name="ps_container_lock_title" msgid="2640257399982364682">"Fechadura"</string>
+    <string name="ps_container_lock_title" msgid="2640257399982364682">"Bloquear"</string>
     <string name="ps_container_transition" msgid="8667331812048014412">"Transição do espaço privado"</string>
     <string name="ps_add_button_label" msgid="8611055839242385935">"Instalar apps"</string>
     <string name="ps_add_button_content_description" msgid="3254274107740952556">"Instale apps no espaço privado"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index b4b67a1..f1bf7eb 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -187,7 +187,7 @@
     <string name="ps_container_title" msgid="4391796149519594205">"Súkromné"</string>
     <string name="ps_container_settings" msgid="6059734123353320479">"Nastavenia súkromného priestoru"</string>
     <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Súkromný priestor zamykania a odomykania"</string>
-    <string name="ps_container_lock_title" msgid="2640257399982364682">"Uzamknutie"</string>
+    <string name="ps_container_lock_title" msgid="2640257399982364682">"Uzamknúť"</string>
     <string name="ps_container_transition" msgid="8667331812048014412">"Prechod súkromného priestoru"</string>
     <string name="ps_add_button_label" msgid="8611055839242385935">"Inštalovať aplikácie"</string>
     <string name="ps_add_button_content_description" msgid="3254274107740952556">"Inštalácia aplikácií v súkromnom priestore"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 270654d..03eb603 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -82,7 +82,7 @@
     <string name="all_apps_button_work_label" msgid="7270707118948892488">"Листа пословних апликација"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"Уклони"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Деинсталирај"</string>
-    <string name="app_info_drop_target_label" msgid="692894985365717661">"Инфор. о апликацији"</string>
+    <string name="app_info_drop_target_label" msgid="692894985365717661">"Подаци о апликацији"</string>
     <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Инсталирај на приватни"</string>
     <string name="install_drop_target_label" msgid="2539096853673231757">"Инсталирај"</string>
     <string name="dismiss_prediction_label" msgid="3357562989568808658">"Не предлажи апликацију"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index c3c2c20..256c77a 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -111,7 +111,7 @@
     <string name="folder_name_format_exact" msgid="8626242716117004803">"โฟลเดอร์: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> รายการ"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"โฟลเดอร์: <xliff:g id="NAME">%1$s</xliff:g>, อย่างน้อย <xliff:g id="SIZE">%2$d</xliff:g> รายการ"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"คู่แอป: <xliff:g id="APP1">%1$s</xliff:g> และ <xliff:g id="APP2">%2$s</xliff:g>"</string>
-    <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"วอลเปเปอร์และรูปแบบ"</string>
+    <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"วอลเปเปอร์และสไตล์"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"แก้ไขหน้าจอหลัก"</string>
     <string name="settings_button_text" msgid="8873672322605444408">"การตั้งค่าหน้าจอหลัก"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"ปิดใช้โดยผู้ดูแลระบบ"</string>
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index b9daf28..4ca20d6 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -435,8 +435,7 @@
     }
 
     @UiThread
-    @VisibleForTesting
-    public void applyLabel(ItemInfoWithIcon info) {
+    public void applyLabel(ItemInfo info) {
         CharSequence label = info.title;
         if (label != null) {
             mLastOriginalText = label;
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 9a5627a..58789fd 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -27,7 +27,7 @@
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.logging.StatsLogManager;
-import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.CollectionInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -75,7 +75,7 @@
         }
 
         return (info instanceof LauncherAppWidgetInfo)
-                || (info instanceof FolderInfo);
+                || (info instanceof CollectionInfo);
     }
 
     @Override
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index c1ebbe5..24ef3d0 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -202,6 +202,8 @@
 import com.android.launcher3.model.ModelWriter;
 import com.android.launcher3.model.StringCache;
 import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.AppPairInfo;
+import com.android.launcher3.model.data.CollectionInfo;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -797,13 +799,19 @@
     @Override
     public void invalidateParent(ItemInfo info) {
         if (info.container >= 0) {
-            View folderIcon = getWorkspace().getHomescreenIconByItemId(info.container);
-            if (folderIcon instanceof FolderIcon && folderIcon.getTag() instanceof FolderInfo) {
+            View collectionIcon = getWorkspace().getHomescreenIconByItemId(info.container);
+            if (collectionIcon instanceof FolderIcon folderIcon
+                    && collectionIcon.getTag() instanceof FolderInfo) {
                 if (new FolderGridOrganizer(getDeviceProfile())
                         .setFolderInfo((FolderInfo) folderIcon.getTag())
                         .isItemInPreview(info.rank)) {
                     folderIcon.invalidate();
                 }
+            } else if (collectionIcon instanceof AppPairIcon appPairIcon
+                    && collectionIcon.getTag() instanceof AppPairInfo appPairInfo) {
+                if (appPairInfo.getContents().contains(info)) {
+                    appPairIcon.getIconDrawableArea().redraw();
+                }
             }
         }
     }
@@ -1987,24 +1995,26 @@
     public boolean removeItem(View v, final ItemInfo itemInfo, boolean deleteFromDb,
             @Nullable final String reason) {
         if (itemInfo instanceof WorkspaceItemInfo) {
-            // Remove the shortcut from the folder before removing it from launcher
-            View folderIcon = mWorkspace.getHomescreenIconByItemId(itemInfo.container);
-            if (folderIcon instanceof FolderIcon) {
-                ((FolderInfo) folderIcon.getTag()).remove((WorkspaceItemInfo) itemInfo, true);
+            View collectionIcon = mWorkspace.getHomescreenIconByItemId(itemInfo.container);
+            if (collectionIcon instanceof FolderIcon) {
+                // Remove the shortcut from the folder before removing it from launcher
+                ((FolderInfo) collectionIcon.getTag()).remove((WorkspaceItemInfo) itemInfo, true);
+            } else if (collectionIcon instanceof AppPairIcon appPairIcon) {
+                removeItem(appPairIcon, appPairIcon.getInfo(), deleteFromDb,
+                        "removing app pair because one of its member apps was removed");
             } else {
                 mWorkspace.removeWorkspaceItem(v);
             }
             if (deleteFromDb) {
                 getModelWriter().deleteItemFromDatabase(itemInfo, reason);
             }
-        } else if (itemInfo instanceof FolderInfo) {
-            final FolderInfo folderInfo = (FolderInfo) itemInfo;
+        } else if (itemInfo instanceof CollectionInfo ci) {
             if (v instanceof FolderIcon) {
                 ((FolderIcon) v).removeListeners();
             }
             mWorkspace.removeWorkspaceItem(v);
             if (deleteFromDb) {
-                getModelWriter().deleteFolderAndContentsFromDatabase(folderInfo);
+                getModelWriter().deleteCollectionAndContentsFromDatabase(ci);
             }
         } else if (itemInfo instanceof LauncherAppWidgetInfo) {
             final LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) itemInfo;
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 60a6be6..50a597d 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -110,7 +110,7 @@
         mOnTerminateCallback.add(() ->
                 mContext.getSystemService(LauncherApps.class).unregisterCallback(callbacks));
 
-        if (Utilities.enableSupportForArchiving()) {
+        if (Flags.enableSupportForArchiving()) {
             ArchiveCompatibilityParams params = new ArchiveCompatibilityParams();
             params.setEnableUnarchivalConfirmation(false);
             launcherApps.setArchiveCompatibility(params);
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index d44438f..2b886e4 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -830,10 +830,4 @@
                 // No-Op
         }
     }
-
-    /** Encapsulates two flag checks into a single one. */
-    public static boolean enableSupportForArchiving() {
-        return Flags.enableSupportForArchiving()
-                || getSystemProperty("pm.archiving.enabled", "false").equals("true");
-    }
 }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index ca34dd1..ce3c55a 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -95,6 +95,7 @@
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
+import com.android.launcher3.model.data.AppPairInfo;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -3313,7 +3314,7 @@
                     }
                 } else if (child instanceof FolderIcon) {
                     FolderInfo folderInfo = (FolderInfo) info;
-                    List<WorkspaceItemInfo> matches = folderInfo.contents.stream()
+                    List<WorkspaceItemInfo> matches = folderInfo.getContents().stream()
                             .filter(matcher)
                             .collect(Collectors.toList());
                     if (!matches.isEmpty()) {
@@ -3322,6 +3323,11 @@
                             ((FolderIcon) child).getFolder().close(false /* animate */);
                         }
                     }
+                } else if (info instanceof AppPairInfo api) {
+                    // If an app pair's member apps are being removed, delete the whole app pair.
+                    if (api.anyMatch(matcher)) {
+                        mLauncher.removeItem(child, info, true);
+                    }
                 }
             }
         }
@@ -3373,9 +3379,9 @@
                 }
             } else if (info instanceof FolderInfo && v instanceof FolderIcon) {
                 FolderInfo fi = (FolderInfo) info;
-                if (fi.contents.stream().anyMatch(matcher)) {
+                if (fi.anyMatch(matcher)) {
                     FolderDotInfo folderDotInfo = new FolderDotInfo();
-                    for (WorkspaceItemInfo si : fi.contents) {
+                    for (WorkspaceItemInfo si : fi.getContents()) {
                         folderDotInfo.addDotInfo(mLauncher.getDotInfoForItem(si));
                     }
                     ((FolderIcon) v).setDotInfo(folderDotInfo);
diff --git a/src/com/android/launcher3/accessibility/BaseAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/BaseAccessibilityDelegate.java
index 19d0421..29862ae 100644
--- a/src/com/android/launcher3/accessibility/BaseAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/BaseAccessibilityDelegate.java
@@ -28,7 +28,7 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.CollectionInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -45,6 +45,7 @@
     public enum DragType {
         ICON,
         FOLDER,
+        APP_PAIR,
         WIDGET
     }
 
@@ -103,7 +104,7 @@
                     && item.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
         }
         return (item instanceof LauncherAppWidgetInfo)
-                || (item instanceof FolderInfo);
+                || (item instanceof CollectionInfo);
     }
 
     @Override
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index e861d38..785074b 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -22,6 +22,8 @@
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.ButtonDropTarget;
 import com.android.launcher3.CellLayout;
@@ -37,6 +39,8 @@
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.keyboard.KeyboardDragAndDropView;
 import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.AppPairInfo;
+import com.android.launcher3.model.data.CollectionInfo;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -58,6 +62,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.function.Consumer;
 
 public class LauncherAccessibilityDelegate extends BaseAccessibilityDelegate<Launcher> {
 
@@ -173,7 +178,7 @@
         } else if (action == MOVE) {
             return beginAccessibleDrag(host, item, fromKeyboard);
         } else if (action == ADD_TO_WORKSPACE) {
-            return addToWorkspace(item, true);
+            return addToWorkspace(item, true /*accessibility*/, null /*finishCallback*/);
         } else if (action == MOVE_TO_WORKSPACE) {
             return moveToWorkspace(item);
         } else if (action == RESIZE) {
@@ -313,6 +318,8 @@
         mDragInfo.dragType = DragType.ICON;
         if (info instanceof FolderInfo) {
             mDragInfo.dragType = DragType.FOLDER;
+        } else if (info instanceof AppPairInfo) {
+            mDragInfo.dragType = DragType.APP_PAIR;
         } else if (info instanceof LauncherAppWidgetInfo) {
             mDragInfo.dragType = DragType.WIDGET;
         }
@@ -384,13 +391,19 @@
      * @param item item to be added
      * @param accessibility true if the first item to be added to the workspace
      *     should be focused for accessibility.
+     * @param finishCallback Callback which will be run after this item has been added
+     *                       and the view has been transitioned to the workspace, or on failure.
      *
      * @return true if the item could be successfully added
      */
-    public boolean addToWorkspace(ItemInfo item, boolean accessibility) {
+    public boolean addToWorkspace(ItemInfo item, boolean accessibility,
+            @Nullable Consumer<Boolean> finishCallback) {
         final int[] coordinates = new int[2];
         final int screenId = findSpaceOnWorkspace(item, coordinates);
         if (screenId == -1) {
+            if (finishCallback != null) {
+                finishCallback.accept(false /*success*/);
+            }
             return false;
         }
         mContext.getStateManager().goToState(NORMAL, true, forSuccessCallback(() -> {
@@ -400,7 +413,7 @@
                         LauncherSettings.Favorites.CONTAINER_DESKTOP,
                         screenId, coordinates[0], coordinates[1]);
 
-                bindItem(item, accessibility);
+                bindItem(item, accessibility, finishCallback);
                 announceConfirmation(R.string.item_added_to_workspace);
             } else if (item instanceof PendingAddItemInfo) {
                 PendingAddItemInfo info = (PendingAddItemInfo) item;
@@ -413,33 +426,40 @@
                 mContext.getModelWriter().addItemToDatabase(info,
                         LauncherSettings.Favorites.CONTAINER_DESKTOP,
                         screenId, coordinates[0], coordinates[1]);
-                bindItem(info, accessibility);
-            } else if (item instanceof FolderInfo fi) {
+                bindItem(info, accessibility, finishCallback);
+            } else if (item instanceof CollectionInfo ci) {
                 Workspace<?> workspace = mContext.getWorkspace();
                 workspace.snapToPage(workspace.getPageIndexForScreenId(screenId));
-                mContext.getModelWriter().addItemToDatabase(fi,
+                mContext.getModelWriter().addItemToDatabase(ci,
                         LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId, coordinates[0],
                         coordinates[1]);
-                fi.contents.forEach(member -> {
-                    mContext.getModelWriter().addItemToDatabase(member, fi.id, -1, -1, -1);
-                });
-                bindItem(fi, accessibility);
+                ci.getContents().forEach(member ->
+                        mContext.getModelWriter()
+                                .addItemToDatabase(member, ci.id, -1, -1, -1));
+                bindItem(ci, accessibility, finishCallback);
             }
         }));
         return true;
     }
 
-    private void bindItem(ItemInfo item, boolean focusForAccessibility) {
+    private void bindItem(ItemInfo item, boolean focusForAccessibility,
+            @Nullable Consumer<Boolean> finishCallback) {
         View view = mContext.getItemInflater().inflateItem(item, mContext.getModelWriter());
         if (view == null) {
+            if (finishCallback != null) {
+                finishCallback.accept(false /*success*/);
+            }
             return;
         }
-        AnimatorSet anim = null;
-        if (focusForAccessibility) {
-            anim = new AnimatorSet();
-            anim.addListener(forEndCallback(
-                    () -> view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)));
-        }
+        AnimatorSet anim = new AnimatorSet();
+        anim.addListener(forEndCallback((success) -> {
+            if (focusForAccessibility) {
+                view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+            }
+            if (finishCallback != null) {
+                finishCallback.accept(success);
+            }
+        }));
         mContext.bindInflatedItems(Collections.singletonList(Pair.create(item, view)), anim);
     }
 
diff --git a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
index a8624dd..52073cc 100644
--- a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
+++ b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
@@ -149,7 +149,7 @@
                 // Find the first item in the folder.
                 FolderInfo folder = (FolderInfo) info;
                 WorkspaceItemInfo firstItem = null;
-                for (WorkspaceItemInfo shortcut : folder.contents) {
+                for (WorkspaceItemInfo shortcut : folder.getContents()) {
                     if (firstItem == null || firstItem.rank > shortcut.rank) {
                         firstItem = shortcut;
                     }
diff --git a/src/com/android/launcher3/apppairs/AppPairIcon.java b/src/com/android/launcher3/apppairs/AppPairIcon.java
index 12fc298..9010f82 100644
--- a/src/com/android/launcher3/apppairs/AppPairIcon.java
+++ b/src/com/android/launcher3/apppairs/AppPairIcon.java
@@ -19,7 +19,6 @@
 import static com.android.launcher3.BubbleTextView.DISPLAY_FOLDER;
 
 import android.content.Context;
-import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.util.AttributeSet;
@@ -34,7 +33,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Reorderable;
 import com.android.launcher3.dragndrop.DraggableView;
-import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.AppPairInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.util.MultiTranslateDelegate;
 import com.android.launcher3.views.ActivityContext;
@@ -51,17 +50,12 @@
 public class AppPairIcon extends FrameLayout implements DraggableView, Reorderable {
     private static final String TAG = "AppPairIcon";
 
-    /**
-     * Indicates that the app pair is currently launchable on the current screen.
-     */
-    private boolean mIsLaunchableAtScreenSize = true;
-
     // A view that holds the app pair icon graphic.
     private AppPairIconGraphic mIconGraphic;
     // A view that holds the app pair's title.
     private BubbleTextView mAppPairName;
     // The underlying ItemInfo that stores info about the app pair members, etc.
-    private FolderInfo mInfo;
+    private AppPairInfo mInfo;
     // The containing element that holds this icon: workspace, taskbar, folder, etc. Affects certain
     // aspects of how the icon is drawn.
     private int mContainer;
@@ -82,7 +76,7 @@
      * Builds an AppPairIcon to be added to the Launcher.
      */
     public static AppPairIcon inflateIcon(int resId, ActivityContext activity,
-            @Nullable ViewGroup group, FolderInfo appPairInfo, int container) {
+            @Nullable ViewGroup group, AppPairInfo appPairInfo, int container) {
         DeviceProfile grid = activity.getDeviceProfile();
         LayoutInflater inflater = (group != null)
                 ? LayoutInflater.from(group.getContext())
@@ -90,15 +84,13 @@
         AppPairIcon icon = (AppPairIcon) inflater.inflate(resId, group, false);
 
         // Sort contents, so that left-hand app comes first
-        appPairInfo.contents.sort(Comparator.comparingInt(a -> a.rank));
+        appPairInfo.getContents().sort(Comparator.comparingInt(a -> a.rank));
 
         icon.setTag(appPairInfo);
         icon.setOnClickListener(activity.getItemOnClickListener());
         icon.mInfo = appPairInfo;
         icon.mContainer = container;
 
-        icon.checkDisabledState();
-
         // Set up icon drawable area
         icon.mIconGraphic = icon.findViewById(R.id.app_pair_icon_graphic);
         icon.mIconGraphic.init(icon, container);
@@ -116,7 +108,7 @@
         // For some reason, app icons have setIncludeFontPadding(false) inside folders, so we set it
         // here to match that.
         icon.mAppPairName.setIncludeFontPadding(container != DISPLAY_FOLDER);
-        icon.mAppPairName.setText(appPairInfo.title);
+        icon.mAppPairName.applyLabel(appPairInfo);
 
         // Set up accessibility
         icon.setContentDescription(icon.getAccessibilityTitle(appPairInfo));
@@ -128,9 +120,9 @@
     /**
      * Returns a formatted accessibility title for app pairs.
      */
-    public String getAccessibilityTitle(FolderInfo appPairInfo) {
-        CharSequence app1 = appPairInfo.contents.get(0).title;
-        CharSequence app2 = appPairInfo.contents.get(1).title;
+    public String getAccessibilityTitle(AppPairInfo appPairInfo) {
+        CharSequence app1 = appPairInfo.getFirstApp().title;
+        CharSequence app2 = appPairInfo.getSecondApp().title;
         return getContext().getString(R.string.app_pair_name_format, app1, app2);
     }
 
@@ -175,7 +167,7 @@
         return mScaleForReorderBounce;
     }
 
-    public FolderInfo getInfo() {
+    public AppPairInfo getInfo() {
         return mInfo;
     }
 
@@ -187,46 +179,20 @@
         return mIconGraphic;
     }
 
-    public boolean isLaunchableAtScreenSize() {
-        return mIsLaunchableAtScreenSize;
-    }
-
-    /**
-     * Checks if the app pair is launchable in the current device configuration.
-     *
-     * App pairs can be "disabled" in two ways:
-     * 1) One of the member WorkspaceItemInfos is disabled (i.e. the app software itself is paused
-     * by the user or can't be launched).
-     * 2) This specific instance of an app pair can't be launched due to screen size requirements.
-     *
-     * This method checks and updates #2. Both #1 and #2 are checked when app pairs are drawn
-     * {@link AppPairIconGraphic#dispatchDraw(Canvas)} or clicked on
-     * {@link com.android.launcher3.touch.ItemClickHandler#onClickAppPairIcon(View)}
-     */
-    public void checkDisabledState() {
-        DeviceProfile dp = ActivityContext.lookupContext(getContext()).getDeviceProfile();
-        // If user is on a small screen, we can't launch if either of the apps is non-resizeable
-        mIsLaunchableAtScreenSize =
-                dp.isTablet || getInfo().contents.stream().noneMatch(
-                        wii -> wii.hasStatusFlag(WorkspaceItemInfo.FLAG_NON_RESIZEABLE));
-        // Invalidate to update icons
-        mIconGraphic.redraw();
-    }
-
     /**
      * Called when WorkspaceItemInfos get updated, and the app pair icon may need to be redrawn.
      */
     public void maybeRedrawForWorkspaceUpdate(Predicate<WorkspaceItemInfo> itemCheck) {
         // If either of the app pair icons return true on the predicate (i.e. in the list of
         // updated apps), redraw the icon graphic (icon background and both icons).
-        if (getInfo().contents.stream().anyMatch(itemCheck)) {
-            checkDisabledState();
+        if (getInfo().anyMatch(itemCheck)) {
+            mIconGraphic.redraw();
         }
     }
 
     /**
      * Inside folders, icons are vertically centered in their rows. See
-     * {@link BubbleTextView#onMeasure(int, int)} for comparison.
+     * {@link BubbleTextView} for comparison.
      */
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
diff --git a/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt b/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
index 04050b0..a3a1cfc 100644
--- a/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
+++ b/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
@@ -23,12 +23,12 @@
 import android.util.AttributeSet
 import android.view.Gravity
 import android.widget.FrameLayout
+import androidx.annotation.OpenForTesting
 import com.android.launcher3.DeviceProfile
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener
 import com.android.launcher3.icons.BitmapInfo
 import com.android.launcher3.icons.FastBitmapDrawable.getDisabledColorFilter
-import com.android.launcher3.model.data.FolderInfo
-import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.model.data.AppPairInfo
 import com.android.launcher3.util.Themes
 import com.android.launcher3.views.ActivityContext
 
@@ -36,29 +36,32 @@
  * A FrameLayout marking the area on an [AppPairIcon] where the visual icon will be drawn. One of
  * two child UI elements on an [AppPairIcon], along with a BubbleTextView holding the text title.
  */
-class AppPairIconGraphic @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
+@OpenForTesting
+open class AppPairIconGraphic
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null) :
     FrameLayout(context, attrs), OnDeviceProfileChangeListener {
     private val TAG = "AppPairIconGraphic"
 
     companion object {
-        /** Composes a drawable for this icon, consisting of a background and 2 app icons. */
+        /**
+         * Composes a drawable for this icon, consisting of a background and 2 app icons. The app
+         * pair will draw as "disabled" if either of the following is true:
+         * 1) One of the member WorkspaceItemInfos is disabled (i.e. the app software itself is
+         *    paused or can't be launched for some other reason).
+         * 2) One of the member apps can't be launched due to screen size requirements.
+         */
         @JvmStatic
-        fun composeDrawable(appPairInfo: FolderInfo, p: AppPairIconDrawingParams): Drawable {
+        fun composeDrawable(appPairInfo: AppPairInfo, p: AppPairIconDrawingParams): Drawable {
             // Generate new icons, using themed flag if needed.
             val flags = if (Themes.isThemedIconEnabled(p.context)) BitmapInfo.FLAG_THEMED else 0
-            val appIcon1 = appPairInfo.contents[0].newIcon(p.context, flags)
-            val appIcon2 = appPairInfo.contents[1].newIcon(p.context, flags)
+            val appIcon1 = appPairInfo.getFirstApp().newIcon(p.context, flags)
+            val appIcon2 = appPairInfo.getSecondApp().newIcon(p.context, flags)
             appIcon1.setBounds(0, 0, p.memberIconSize.toInt(), p.memberIconSize.toInt())
             appIcon2.setBounds(0, 0, p.memberIconSize.toInt(), p.memberIconSize.toInt())
 
-            // Check disabled status.
-            val activity: ActivityContext = ActivityContext.lookupContext(p.context)
-            val isLaunchableAtScreenSize =
-                activity.deviceProfile.isTablet ||
-                    appPairInfo.contents.stream().noneMatch { wii: WorkspaceItemInfo ->
-                        wii.hasStatusFlag(WorkspaceItemInfo.FLAG_NON_RESIZEABLE)
-                    }
-            val shouldDrawAsDisabled = appPairInfo.isDisabled || !isLaunchableAtScreenSize
+            val shouldDrawAsDisabled =
+                appPairInfo.isDisabled || !appPairInfo.isLaunchable(p.context)
 
             // Set disabled status on icons.
             appIcon1.setIsDisabled(shouldDrawAsDisabled)
@@ -124,7 +127,6 @@
      */
     fun getIconBounds(outBounds: Rect) {
         outBounds.set(0, 0, drawParams.backgroundSize.toInt(), drawParams.backgroundSize.toInt())
-
         outBounds.offset(
             // x-coordinate in parent's coordinate system
             ((parentIcon.width - drawParams.backgroundSize) / 2).toInt(),
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index b6e5977..bc5a164 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -33,6 +33,7 @@
 import com.android.launcher3.DropTarget;
 import com.android.launcher3.Flags;
 import com.android.launcher3.logging.InstanceId;
+import com.android.launcher3.model.data.AppPairInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -289,7 +290,8 @@
         // Cancel the current drag if we are removing an app that we are dragging
         if (mDragObject != null) {
             ItemInfo dragInfo = mDragObject.dragInfo;
-            if (dragInfo instanceof WorkspaceItemInfo && matcher.test(dragInfo)) {
+            if ((dragInfo instanceof WorkspaceItemInfo && matcher.test(dragInfo))
+                    || (dragInfo instanceof AppPairInfo api && api.anyMatch(matcher))) {
                 cancelDrag();
             }
         }
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index ecb5c8f..474108e 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -490,7 +490,7 @@
         mInfo = info;
         mFromTitle = info.title;
         mFromLabelState = info.getFromLabelState();
-        ArrayList<WorkspaceItemInfo> children = info.contents;
+        ArrayList<WorkspaceItemInfo> children = info.getContents();
         Collections.sort(children, ITEM_POS_COMPARATOR);
         updateItemLocationsInDatabaseBatch(true);
 
@@ -623,7 +623,7 @@
         // onDropComplete. Perform cleanup once drag-n-drop ends.
         mDragController.addDragListener(this);
 
-        ArrayList<WorkspaceItemInfo> items = new ArrayList<>(mInfo.contents);
+        ArrayList<WorkspaceItemInfo> items = new ArrayList<>(mInfo.getContents());
         mEmptyCellRank = items.size();
         items.add(null);    // Add an empty spot at the end
 
@@ -636,7 +636,7 @@
      * is played.
      */
     public void animateOpen() {
-        animateOpen(mInfo.contents, 0);
+        animateOpen(mInfo.getContents(), 0);
     }
 
     /**
@@ -1094,9 +1094,9 @@
                 mActivityContext.getDeviceProfile()).setFolderInfo(mInfo);
 
         ArrayList<ItemInfo> items = new ArrayList<>();
-        int total = mInfo.contents.size();
+        int total = mInfo.getContents().size();
         for (int i = 0; i < total; i++) {
-            WorkspaceItemInfo itemInfo = mInfo.contents.get(i);
+            WorkspaceItemInfo itemInfo = mInfo.getContents().get(i);
             if (verifier.updateRankAndPos(itemInfo, i)) {
                 items.add(itemInfo);
             }
@@ -1110,7 +1110,7 @@
                 FolderNameInfos nameInfos = new FolderNameInfos();
                 FolderNameProvider fnp = FolderNameProvider.newInstance(getContext());
                 fnp.getSuggestedFolderName(
-                        getContext(), mInfo.contents, nameInfos);
+                        getContext(), mInfo.getContents(), nameInfos);
                 mInfo.suggestedFolderNames = nameInfos;
             });
         }
@@ -1214,7 +1214,7 @@
     }
 
     public int getItemCount() {
-        return mInfo.contents.size();
+        return mInfo.getContents().size();
     }
 
     void replaceFolderWithFinalItem() {
diff --git a/src/com/android/launcher3/folder/FolderGridOrganizer.java b/src/com/android/launcher3/folder/FolderGridOrganizer.java
index cc24761..593673d 100644
--- a/src/com/android/launcher3/folder/FolderGridOrganizer.java
+++ b/src/com/android/launcher3/folder/FolderGridOrganizer.java
@@ -57,7 +57,7 @@
      * Updates the organizer with the provided folder info
      */
     public FolderGridOrganizer setFolderInfo(FolderInfo info) {
-        return setContentSize(info.contents.size());
+        return setContentSize(info.getContents().size());
     }
 
     /**
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index ee0d5fc..62ce311 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -215,7 +215,7 @@
 
         // Keep the notification dot up to date with the sum of all the content's dots.
         FolderDotInfo folderDotInfo = new FolderDotInfo();
-        for (WorkspaceItemInfo si : folderInfo.contents) {
+        for (WorkspaceItemInfo si : folderInfo.getContents()) {
             folderDotInfo.addDotInfo(activity.getDotInfoForItem(si));
         }
         icon.setDotInfo(folderDotInfo);
@@ -422,7 +422,7 @@
             FolderNameInfos nameInfos = new FolderNameInfos();
             Executors.MODEL_EXECUTOR.post(() -> {
                 d.folderNameProvider.getSuggestedFolderName(
-                        getContext(), mInfo.contents, nameInfos);
+                        getContext(), mInfo.getContents(), nameInfos);
                 postDelayed(() -> {
                     setLabelSuggestion(nameInfos, d.logInstanceId);
                     invalidate();
@@ -487,7 +487,7 @@
         }
         mFolder.notifyDrop();
         onDrop(item, d, null, 1.0f,
-                itemReturnedOnFailedDrop ? item.rank : mInfo.contents.size(),
+                itemReturnedOnFailedDrop ? item.rank : mInfo.getContents().size(),
                 itemReturnedOnFailedDrop
         );
     }
@@ -666,7 +666,7 @@
      * Returns the list of items which should be visible in the preview
      */
     public List<WorkspaceItemInfo> getPreviewItemsOnPage(int page) {
-        return mPreviewVerifier.setFolderInfo(mInfo).previewItemsForPage(page, mInfo.contents);
+        return mPreviewVerifier.setFolderInfo(mInfo).previewItemsForPage(page, mInfo.getContents());
     }
 
     @Override
@@ -809,7 +809,7 @@
      * Returns a formatted accessibility title for folder
      */
     public String getAccessiblityTitle(CharSequence title) {
-        int size = mInfo.contents.size();
+        int size = mInfo.getContents().size();
         if (size < MAX_NUM_ITEMS_IN_PREVIEW) {
             return getContext().getString(R.string.folder_name_format_exact, title, size);
         } else {
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
index bf59594..5d2bb3a 100644
--- a/src/com/android/launcher3/folder/FolderNameProvider.java
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -35,7 +35,7 @@
 import com.android.launcher3.model.BgDataModel;
 import com.android.launcher3.model.StringCache;
 import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.CollectionInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.util.IntSparseArrayMap;
 import com.android.launcher3.util.Preconditions;
@@ -62,7 +62,7 @@
      * name edit box can also be used to provide suggestion.
      */
     public static final int SUGGEST_MAX = 4;
-    protected IntSparseArrayMap<FolderInfo> mFolderInfos;
+    protected IntSparseArrayMap<CollectionInfo> mCollectionInfos;
     protected List<AppInfo> mAppInfos;
 
     /**
@@ -79,7 +79,7 @@
     }
 
     public static FolderNameProvider newInstance(Context context, List<AppInfo> appInfos,
-            IntSparseArrayMap<FolderInfo> folderInfos) {
+            IntSparseArrayMap<CollectionInfo> folderInfos) {
         Preconditions.assertWorkerThread();
         FolderNameProvider fnp = Overrides.getObject(FolderNameProvider.class,
                 context.getApplicationContext(), R.string.folder_name_provider_class);
@@ -93,9 +93,9 @@
                 new FolderNameWorker());
     }
 
-    private void load(List<AppInfo> appInfos, IntSparseArrayMap<FolderInfo> folderInfos) {
+    private void load(List<AppInfo> appInfos, IntSparseArrayMap<CollectionInfo> folderInfos) {
         mAppInfos = appInfos;
-        mFolderInfos = folderInfos;
+        mCollectionInfos = folderInfos;
     }
 
     /**
@@ -195,7 +195,7 @@
         @Override
         public void execute(@NonNull final LauncherAppState app,
                 @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
-            mFolderInfos = dataModel.folders.clone();
+            mCollectionInfos = dataModel.collections.clone();
             mAppInfos = Arrays.asList(apps.copyData());
         }
     }
diff --git a/src/com/android/launcher3/folder/LauncherDelegate.java b/src/com/android/launcher3/folder/LauncherDelegate.java
index 78298b3..33bcf21 100644
--- a/src/com/android/launcher3/folder/LauncherDelegate.java
+++ b/src/com/android/launcher3/folder/LauncherDelegate.java
@@ -93,7 +93,7 @@
                         // folder
                         CellLayout cellLayout = mLauncher.getCellLayout(info.container,
                                 mLauncher.getCellPosMapper().mapModelToPresenter(info).screenId);
-                        finalItem =  info.contents.remove(0);
+                        finalItem =  info.getContents().remove(0);
                         newIcon = mLauncher.getItemInflater().inflateItem(
                                 finalItem, mLauncher.getModelWriter(), cellLayout);
                         mLauncher.getModelWriter().addOrMoveItemInDatabase(finalItem,
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 1a57d91..14d1700 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -82,6 +82,8 @@
 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.WidgetsModel;
+import com.android.launcher3.model.data.AppPairInfo;
+import com.android.launcher3.model.data.CollectionInfo;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -389,16 +391,16 @@
         addInScreenFromBind(icon, info);
     }
 
-    private void inflateAndAddCollectionIcon(FolderInfo info) {
+    private void inflateAndAddCollectionIcon(CollectionInfo info) {
         boolean isOnDesktop = info.container == Favorites.CONTAINER_DESKTOP;
         CellLayout screen = isOnDesktop
                 ? mWorkspaceScreens.get(info.screenId)
                 : mHotseat;
-        FrameLayout folderIcon = info.itemType == Favorites.ITEM_TYPE_FOLDER
-                ? FolderIcon.inflateIcon(R.layout.folder_icon, this, screen, info)
-                : AppPairIcon.inflateIcon(R.layout.app_pair_icon, this, screen, info,
+        FrameLayout collectionIcon = info.itemType == Favorites.ITEM_TYPE_FOLDER
+                ? FolderIcon.inflateIcon(R.layout.folder_icon, this, screen, (FolderInfo) info)
+                : AppPairIcon.inflateIcon(R.layout.app_pair_icon, this, screen, (AppPairInfo) info,
                         isOnDesktop ? DISPLAY_WORKSPACE : DISPLAY_TASKBAR);
-        addInScreenFromBind(folderIcon, info);
+        addInScreenFromBind(collectionIcon, info);
     }
 
     private void inflateAndAddWidgets(
@@ -502,7 +504,7 @@
                     break;
                 case Favorites.ITEM_TYPE_FOLDER:
                 case Favorites.ITEM_TYPE_APP_PAIR:
-                    inflateAndAddCollectionIcon((FolderInfo) itemInfo);
+                    inflateAndAddCollectionIcon((CollectionInfo) itemInfo);
                     break;
                 default:
                     break;
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 1633eba..af704a8 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -51,6 +51,7 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.core.util.Pair;
 
+import com.android.launcher3.Flags;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.icons.ComponentWithLabel.ComponentCachingLogic;
@@ -248,7 +249,7 @@
     @SuppressWarnings("NewApi")
     public synchronized void getTitleAndIcon(ItemInfoWithIcon info,
             LauncherActivityInfo activityInfo, boolean useLowResIcon) {
-        boolean isAppArchived = Utilities.enableSupportForArchiving() && activityInfo != null
+        boolean isAppArchived = Flags.enableSupportForArchiving() && activityInfo != null
                 && activityInfo.getActivityInfo().isArchived;
         // If we already have activity info, no need to use package icon
         getTitleAndIcon(info, () -> activityInfo, isAppArchived, useLowResIcon,
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index 96a8da9..ce563b7 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -31,7 +31,7 @@
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.CollectionInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -131,8 +131,8 @@
                 int screenId = coords[0];
 
                 ItemInfo itemInfo;
-                if (item instanceof WorkspaceItemInfo || item instanceof FolderInfo ||
-                        item instanceof LauncherAppWidgetInfo) {
+                if (item instanceof WorkspaceItemInfo || item instanceof CollectionInfo
+                        || item instanceof LauncherAppWidgetInfo) {
                     itemInfo = item;
                 } else if (item instanceof WorkspaceItemFactory) {
                     itemInfo = ((WorkspaceItemFactory) item).makeWorkspaceItem(app.getContext());
diff --git a/src/com/android/launcher3/model/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java
index 8659471..8c5ea79 100644
--- a/src/com/android/launcher3/model/AllAppsList.java
+++ b/src/com/android/launcher3/model/AllAppsList.java
@@ -33,6 +33,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.AppFilter;
+import com.android.launcher3.Flags;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.AlphabeticIndexCompat;
 import com.android.launcher3.icons.IconCache;
@@ -330,7 +331,7 @@
                             PackageManagerHelper.getLoadingProgress(info),
                             PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING);
                     applicationInfo.intent = launchIntent;
-                    if (Utilities.enableSupportForArchiving()) {
+                    if (Flags.enableSupportForArchiving()) {
                         // In case an app is archived, the respective item flag corresponding to
                         // archiving should also be applied during package updates
                         if (info.getActivityInfo().isArchived) {
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 8579d1d..ee9ce7d 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -44,6 +44,7 @@
 import com.android.launcher3.Workspace;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.CollectionInfo;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -102,9 +103,9 @@
     public final ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
 
     /**
-     * Map of id to FolderInfos of all the folders created by LauncherModel
+     * Map of id to CollectionInfos of all the folders or app pairs created by LauncherModel
      */
-    public final IntSparseArrayMap<FolderInfo> folders = new IntSparseArrayMap<>();
+    public final IntSparseArrayMap<CollectionInfo> collections = new IntSparseArrayMap<>();
 
     /**
      * Extra container based items
@@ -144,7 +145,7 @@
     public synchronized void clear() {
         workspaceItems.clear();
         appWidgets.clear();
-        folders.clear();
+        collections.clear();
         itemsIdMap.clear();
         deepShortcutMap.clear();
         extraItems.clear();
@@ -179,9 +180,9 @@
         for (int i = 0; i < appWidgets.size(); i++) {
             writer.println(prefix + '\t' + appWidgets.get(i).toString());
         }
-        writer.println(prefix + " ---- folder items ");
-        for (int i = 0; i < folders.size(); i++) {
-            writer.println(prefix + '\t' + folders.valueAt(i).toString());
+        writer.println(prefix + " ---- collection items ");
+        for (int i = 0; i < collections.size(); i++) {
+            writer.println(prefix + '\t' + collections.valueAt(i).toString());
         }
         writer.println(prefix + " ---- extra items ");
         for (int i = 0; i < extraItems.size(); i++) {
@@ -211,12 +212,12 @@
             switch (item.itemType) {
                 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                 case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR:
-                    folders.remove(item.id);
+                    collections.remove(item.id);
                     if (FeatureFlags.IS_STUDIO_BUILD) {
                         for (ItemInfo info : itemsIdMap) {
                             if (info.container == item.id) {
-                                // We are deleting a folder which still contains items that
-                                // think they are contained by that folder.
+                                // We are deleting a collection which still contains items that
+                                // think they are contained by that collection.
                                 String msg = "deleting a collection (" + item + ") which still "
                                         + "contains items (" + info + ")";
                                 Log.e(TAG, msg);
@@ -259,7 +260,7 @@
         switch (item.itemType) {
             case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
             case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR:
-                folders.put(item.id, (FolderInfo) item);
+                collections.put(item.id, (CollectionInfo) item);
                 workspaceItems.add(item);
                 break;
             case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
@@ -269,14 +270,14 @@
                     workspaceItems.add(item);
                 } else {
                     if (newItem) {
-                        if (!folders.containsKey(item.container)) {
+                        if (!collections.containsKey(item.container)) {
                             // Adding an item to a nonexistent collection.
                             String msg = "attempted to add item: " + item + " to a nonexistent app"
                                     + " collection";
                             Log.e(TAG, msg);
                         }
                     } else {
-                        findOrMakeFolder(item.container).add((WorkspaceItemInfo) item, false);
+                        findOrMakeFolder(item.container).add((WorkspaceItemInfo) item);
                     }
                 }
                 break;
@@ -371,15 +372,18 @@
      * Return an existing FolderInfo object if we have encountered this ID previously,
      * or make a new one.
      */
-    public synchronized FolderInfo findOrMakeFolder(int id) {
+    public synchronized CollectionInfo findOrMakeFolder(int id) {
         // See if a placeholder was created for us already
-        FolderInfo folderInfo = folders.get(id);
-        if (folderInfo == null) {
-            // No placeholder -- create a new instance
-            folderInfo = new FolderInfo();
-            folders.put(id, folderInfo);
+        CollectionInfo collectionInfo = collections.get(id);
+        if (collectionInfo == null) {
+            // No placeholder -- create a new blank folder instance. At this point, we don't know
+            // if the desired container is supposed to be a folder or an app pair. In the case that
+            // it is an app pair, the blank folder will be replaced by a blank app pair when the app
+            // pair is getting processed, in WorkspaceItemProcessor.processFolderOrAppPair().
+            collectionInfo = new FolderInfo();
+            collections.put(id, collectionInfo);
         }
-        return folderInfo;
+        return collectionInfo;
     }
 
     /**
diff --git a/src/com/android/launcher3/model/FirstScreenBroadcast.java b/src/com/android/launcher3/model/FirstScreenBroadcast.java
index 9e91b9d..1deb665 100644
--- a/src/com/android/launcher3/model/FirstScreenBroadcast.java
+++ b/src/com/android/launcher3/model/FirstScreenBroadcast.java
@@ -36,6 +36,7 @@
 import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.model.data.CollectionInfo;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -67,7 +68,8 @@
     private static final String ACTION_FIRST_SCREEN_ACTIVE_INSTALLS
             = "com.android.launcher3.action.FIRST_SCREEN_ACTIVE_INSTALLS";
 
-    private static final String FOLDER_ITEM_EXTRA = "folderItem";
+    // String retained as "folderItem" for back-compatibility reasons.
+    private static final String COLLECTION_ITEM_EXTRA = "folderItem";
     private static final String WORKSPACE_ITEM_EXTRA = "workspaceItem";
     private static final String HOTSEAT_ITEM_EXTRA = "hotseatItem";
     private static final String WIDGET_ITEM_EXTRA = "widgetItem";
@@ -105,20 +107,19 @@
     @WorkerThread
     private void sendBroadcastToInstaller(Context context, String installerPackageName,
             Set<String> packages, List<ItemInfo> firstScreenItems) {
-        Set<String> folderItems = new HashSet<>();
+        Set<String> collectionItems = new HashSet<>();
         Set<String> workspaceItems = new HashSet<>();
         Set<String> hotseatItems = new HashSet<>();
         Set<String> widgetItems = new HashSet<>();
 
         for (ItemInfo info : firstScreenItems) {
-            if (info instanceof FolderInfo) {
-                FolderInfo folderInfo = (FolderInfo) info;
-                String folderItemInfoPackage;
-                for (ItemInfo folderItemInfo : cloneOnMainThread(folderInfo.contents)) {
-                    folderItemInfoPackage = getPackageName(folderItemInfo);
-                    if (folderItemInfoPackage != null
-                            && packages.contains(folderItemInfoPackage)) {
-                        folderItems.add(folderItemInfoPackage);
+            if (info instanceof CollectionInfo ci) {
+                String collectionItemInfoPackage;
+                for (ItemInfo collectionItemInfo : cloneOnMainThread(ci.getContents())) {
+                    collectionItemInfoPackage = getPackageName(collectionItemInfo);
+                    if (collectionItemInfoPackage != null
+                            && packages.contains(collectionItemInfoPackage)) {
+                        collectionItems.add(collectionItemInfoPackage);
                     }
                 }
             }
@@ -137,13 +138,13 @@
         }
 
         if (DEBUG) {
-            printList(installerPackageName, "Folder item", folderItems);
+            printList(installerPackageName, "Collection item", collectionItems);
             printList(installerPackageName, "Workspace item", workspaceItems);
             printList(installerPackageName, "Hotseat item", hotseatItems);
             printList(installerPackageName, "Widget item", widgetItems);
         }
 
-        if (folderItems.isEmpty()
+        if (collectionItems.isEmpty()
                 && workspaceItems.isEmpty()
                 && hotseatItems.isEmpty()
                 && widgetItems.isEmpty()) {
@@ -152,7 +153,7 @@
         }
         context.sendBroadcast(new Intent(ACTION_FIRST_SCREEN_ACTIVE_INSTALLS)
                 .setPackage(installerPackageName)
-                .putStringArrayListExtra(FOLDER_ITEM_EXTRA, new ArrayList<>(folderItems))
+                .putStringArrayListExtra(COLLECTION_ITEM_EXTRA, new ArrayList<>(collectionItems))
                 .putStringArrayListExtra(WORKSPACE_ITEM_EXTRA, new ArrayList<>(workspaceItems))
                 .putStringArrayListExtra(HOTSEAT_ITEM_EXTRA, new ArrayList<>(hotseatItems))
                 .putStringArrayListExtra(WIDGET_ITEM_EXTRA, new ArrayList<>(widgetItems))
@@ -180,7 +181,7 @@
     }
 
     /**
-     * Clone the provided list on UI thread. This is used for {@link FolderInfo#contents} which
+     * Clone the provided list on UI thread. This is used for {@link FolderInfo#getContents()} which
      * is always modified on UI thread.
      */
     @AnyThread
diff --git a/src/com/android/launcher3/model/ItemInstallQueue.java b/src/com/android/launcher3/model/ItemInstallQueue.java
index d350879..90aba2a 100644
--- a/src/com/android/launcher3/model/ItemInstallQueue.java
+++ b/src/com/android/launcher3/model/ItemInstallQueue.java
@@ -40,6 +40,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 
+import com.android.launcher3.Flags;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
@@ -300,7 +301,7 @@
                     } else {
                         lai = laiList.get(0);
                         si.intent = makeLaunchIntent(lai);
-                        if (Utilities.enableSupportForArchiving()
+                        if (Flags.enableSupportForArchiving()
                                 && lai.getActivityInfo().isArchived) {
                             si.runtimeStatusFlags |= FLAG_ARCHIVED;
                         }
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 17cef90..f8c76a3 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -20,7 +20,6 @@
 import static com.android.launcher3.Flags.enableLauncherBrMetricsFixed;
 import static com.android.launcher3.LauncherPrefs.IS_FIRST_LOAD_AFTER_RESTORE;
 import static com.android.launcher3.LauncherPrefs.SHOULD_SHOW_SMARTSPACE;
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
 import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
 import static com.android.launcher3.config.FeatureFlags.SMARTSPACE_AS_A_WIDGET;
@@ -77,9 +76,12 @@
 import com.android.launcher3.icons.cache.IconCacheUpdateHandler;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.AppPairInfo;
+import com.android.launcher3.model.data.CollectionInfo;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.IconRequestInfo;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.pm.InstallSessionHelper;
@@ -99,7 +101,6 @@
 import com.android.launcher3.widget.WidgetInflater;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -234,6 +235,7 @@
             if (Objects.equals(mApp.getInvariantDeviceProfile().dbFile, mDbName)) {
                 verifyNotStopped();
                 sanitizeFolders(mItemsDeleted);
+                sanitizeAppPairs();
                 sanitizeWidgetsShortcutsAndPackages();
                 logASplit("sanitizeData");
             }
@@ -420,7 +422,7 @@
 
             final HashMap<PackageUserKey, SessionInfo> installingPkgs =
                     mSessionHelper.getActiveSessions();
-            if (Utilities.enableSupportForArchiving()) {
+            if (Flags.enableSupportForArchiving()) {
                 mInstallingPkgsCached = installingPkgs;
             }
             installingPkgs.forEach(mApp.getIconCache()::updateSessionCache);
@@ -482,14 +484,20 @@
     }
 
     /**
-     * After all items have been processed and added to the BgDataModel, this method requests
-     * high-res icons for the items that are part of an app pair
+     * After all items have been processed and added to the BgDataModel, this method sorts and
+     * requests high-res icons for the items that are part of an app pair.
      */
     private void processAppPairItems() {
-        mBgDataModel.workspaceItems.stream()
-                .filter((itemInfo -> itemInfo.itemType == ITEM_TYPE_APP_PAIR))
-                .forEach(fi -> ((FolderInfo) fi).contents.forEach(item ->
-                        mIconCache.getTitleAndIcon(item, false /*useLowResIcon*/)));
+        for (CollectionInfo collection : mBgDataModel.collections) {
+            if (!(collection instanceof AppPairInfo appPair)) {
+                continue;
+            }
+
+            appPair.getContents().sort(Folder.ITEM_POS_COMPARATOR);
+            // Fetch hi-res icons if needed.
+            appPair.getContents().stream().filter(ItemInfoWithIcon::usingLowResIcon)
+                    .forEach(member -> mIconCache.getTitleAndIcon(member, false));
+        }
     }
 
     /**
@@ -545,20 +553,21 @@
         // Sort the folder items, update ranks, and make sure all preview items are high res.
         List<FolderGridOrganizer> verifiers = mApp.getInvariantDeviceProfile().supportedProfiles
                 .stream().map(FolderGridOrganizer::new).toList();
-        for (FolderInfo folder : mBgDataModel.folders) {
-            Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
+        for (CollectionInfo collection : mBgDataModel.collections) {
+            if (!(collection instanceof FolderInfo folder)) {
+                continue;
+            }
+
+            folder.getContents().sort(Folder.ITEM_POS_COMPARATOR);
             verifiers.forEach(verifier -> verifier.setFolderInfo(folder));
-            int size = folder.contents.size();
+            int size = folder.getContents().size();
 
             // Update ranks here to ensure there are no gaps caused by removed folder items.
             // Ranks are the source of truth for folder items, so cellX and cellY can be
             // ignored for now. Database will be updated once user manually modifies folder.
             for (int rank = 0; rank < size; ++rank) {
-                WorkspaceItemInfo info = folder.contents.get(rank);
-                // rank is used differently in app pairs, so don't reset
-                if (folder.itemType != ITEM_TYPE_APP_PAIR) {
-                    info.rank = rank;
-                }
+                WorkspaceItemInfo info = folder.getContents().get(rank);
+                info.rank = rank;
 
                 if (info.usingLowResIcon() && info.itemType == Favorites.ITEM_TYPE_APPLICATION
                         && verifiers.stream().anyMatch(it -> it.isItemInPreview(info.rank))) {
@@ -611,14 +620,32 @@
             IntArray deletedFolderIds = mApp.getModel().getModelDbController().deleteEmptyFolders();
             synchronized (mBgDataModel) {
                 for (int folderId : deletedFolderIds) {
-                    mBgDataModel.workspaceItems.remove(mBgDataModel.folders.get(folderId));
-                    mBgDataModel.folders.remove(folderId);
+                    mBgDataModel.workspaceItems.remove(mBgDataModel.collections.get(folderId));
+                    mBgDataModel.collections.remove(folderId);
                     mBgDataModel.itemsIdMap.remove(folderId);
                 }
             }
         }
     }
 
+    /** Cleans up app pairs if they don't have the right number of member apps (2). */
+    private void sanitizeAppPairs() {
+        IntArray deletedAppPairIds = mApp.getModel().getModelDbController().deleteBadAppPairs();
+        IntArray deletedAppIds = mApp.getModel().getModelDbController().deleteUnparentedApps();
+
+        IntArray deleted = new IntArray();
+        deleted.addAll(deletedAppPairIds);
+        deleted.addAll(deletedAppIds);
+
+        synchronized (mBgDataModel) {
+            for (int id : deleted) {
+                mBgDataModel.workspaceItems.remove(mBgDataModel.collections.get(id));
+                mBgDataModel.collections.remove(id);
+                mBgDataModel.itemsIdMap.remove(id);
+            }
+        }
+    }
+
     private void sanitizeWidgetsShortcutsAndPackages() {
         Context context = mApp.getContext();
 
@@ -667,7 +694,7 @@
             for (int i = 0; i < apps.size(); i++) {
                 LauncherActivityInfo app = apps.get(i);
                 AppInfo appInfo = new AppInfo(app, mUserCache.getUserInfo(user), quietMode);
-                if (Utilities.enableSupportForArchiving() && app.getApplicationInfo().isArchived) {
+                if (Flags.enableSupportForArchiving() && app.getApplicationInfo().isArchived) {
                     // For archived apps, include progress info in case there is a pending
                     // install session post restart of device.
                     String appPackageName = app.getApplicationInfo().packageName;
@@ -754,16 +781,16 @@
 
     private void loadFolderNames() {
         FolderNameProvider provider = FolderNameProvider.newInstance(mApp.getContext(),
-                mBgAllAppsList.data, mBgDataModel.folders);
+                mBgAllAppsList.data, mBgDataModel.collections);
 
         synchronized (mBgDataModel) {
-            for (int i = 0; i < mBgDataModel.folders.size(); i++) {
+            for (int i = 0; i < mBgDataModel.collections.size(); i++) {
                 FolderNameInfos suggestionInfos = new FolderNameInfos();
-                FolderInfo info = mBgDataModel.folders.valueAt(i);
-                if (info.suggestedFolderNames == null) {
-                    provider.getSuggestedFolderName(mApp.getContext(), info.contents,
+                CollectionInfo info = mBgDataModel.collections.valueAt(i);
+                if (info instanceof FolderInfo fi && fi.suggestedFolderNames == null) {
+                    provider.getSuggestedFolderName(mApp.getContext(), fi.getContents(),
                             suggestionInfos);
-                    info.suggestedFolderNames = suggestionInfos;
+                    fi.suggestedFolderNames = suggestionInfos;
                 }
             }
         }
diff --git a/src/com/android/launcher3/model/ModelDbController.java b/src/com/android/launcher3/model/ModelDbController.java
index ba2b64d..64771bd 100644
--- a/src/com/android/launcher3/model/ModelDbController.java
+++ b/src/com/android/launcher3/model/ModelDbController.java
@@ -15,11 +15,15 @@
  */
 package com.android.launcher3.model;
 
+import static android.provider.BaseColumns._ID;
 import static android.util.Base64.NO_PADDING;
 import static android.util.Base64.NO_WRAP;
 
 import static com.android.launcher3.DefaultLayoutParser.RES_PARTNER_DEFAULT_LAYOUT;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
+import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
 import static com.android.launcher3.LauncherSettings.Favorites.addTableToDb;
 import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_KEY;
 import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_LABEL;
@@ -387,6 +391,68 @@
         }
     }
 
+    /**
+     * Deletes any app pair that doesn't contain 2 member apps from the DB.
+     * @return Ids of deleted app pairs.
+     */
+    @WorkerThread
+    public IntArray deleteBadAppPairs() {
+        createDbIfNotExists();
+
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        try (SQLiteTransaction t = new SQLiteTransaction(db)) {
+            // Select all entries with ITEM_TYPE = ITEM_TYPE_APP_PAIR whose id does not appear
+            // exactly twice in the CONTAINER column.
+            String selection =
+                    ITEM_TYPE + " = " + ITEM_TYPE_APP_PAIR
+                            + " AND " + _ID +  " NOT IN"
+                            + " (SELECT " + CONTAINER + " FROM " + TABLE_NAME
+                            + " GROUP BY " + CONTAINER + " HAVING COUNT(*) = 2)";
+
+            IntArray appPairIds = LauncherDbUtils.queryIntArray(false, db, TABLE_NAME,
+                    _ID, selection, null, null);
+            if (!appPairIds.isEmpty()) {
+                db.delete(TABLE_NAME, Utilities.createDbSelectionQuery(
+                        _ID, appPairIds), null);
+            }
+            t.commit();
+            return appPairIds;
+        } catch (SQLException ex) {
+            Log.e(TAG, ex.getMessage(), ex);
+            return new IntArray();
+        }
+    }
+
+    /**
+     * Deletes any app with a container id that doesn't exist.
+     * @return Ids of deleted apps.
+     */
+    @WorkerThread
+    public IntArray deleteUnparentedApps() {
+        createDbIfNotExists();
+
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        try (SQLiteTransaction t = new SQLiteTransaction(db)) {
+            // Select all entries whose container id does not appear in the database.
+            String selection =
+                    CONTAINER + " >= 0"
+                            + " AND " + CONTAINER + " NOT IN"
+                            + " (SELECT " + _ID + " FROM " + TABLE_NAME + ")";
+
+            IntArray appIds = LauncherDbUtils.queryIntArray(false, db, TABLE_NAME,
+                    _ID, selection, null, null);
+            if (!appIds.isEmpty()) {
+                db.delete(TABLE_NAME, Utilities.createDbSelectionQuery(
+                        _ID, appIds), null);
+            }
+            t.commit();
+            return appIds;
+        } catch (SQLException ex) {
+            Log.e(TAG, ex.getMessage(), ex);
+            return new IntArray();
+        }
+    }
+
     private static void addModifiedTime(ContentValues values) {
         values.put(LauncherSettings.Favorites.MODIFIED, System.currentTimeMillis());
     }
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index 55093a3..b477cb1 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -37,7 +37,7 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.BgDataModel.Callbacks;
-import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.CollectionInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -275,7 +275,7 @@
     public void deleteItemsFromDatabase(@NonNull final Predicate<ItemInfo> matcher,
             @Nullable final String reason) {
         deleteItemsFromDatabase(StreamSupport.stream(mBgDataModel.itemsIdMap.spliterator(), false)
-                        .filter(matcher).collect(Collectors.toList()), reason);
+                .filter(matcher).collect(Collectors.toList()), reason);
     }
 
     /**
@@ -302,15 +302,15 @@
     /**
      * Remove the specified folder and all its contents from the database.
      */
-    public void deleteFolderAndContentsFromDatabase(final FolderInfo info) {
+    public void deleteCollectionAndContentsFromDatabase(final CollectionInfo info) {
         ModelVerifier verifier = new ModelVerifier();
         notifyDelete(Collections.singleton(info));
 
         enqueueDeleteRunnable(newModelTask(() -> {
             mModel.getModelDbController().delete(Favorites.TABLE_NAME,
                     Favorites.CONTAINER + "=" + info.id, null);
-            mBgDataModel.removeItem(mContext, info.contents);
-            info.contents.clear();
+            mBgDataModel.removeItem(mContext, info.getContents());
+            info.getContents().clear();
 
             mModel.getModelDbController().delete(Favorites.TABLE_NAME,
                     Favorites._ID + "=" + info.id, null);
@@ -458,12 +458,12 @@
 
                 if (item.container != Favorites.CONTAINER_DESKTOP &&
                         item.container != Favorites.CONTAINER_HOTSEAT) {
-                    // Item is in a folder, make sure this folder exists
-                    if (!mBgDataModel.folders.containsKey(item.container)) {
+                    // Item is in a collection, make sure this collection exists
+                    if (!mBgDataModel.collections.containsKey(item.container)) {
                         // An items container is being set to a that of an item which is not in
                         // the list of Folders.
                         String msg = "item: " + item + " container being set to: " +
-                                item.container + ", not in the list of folders";
+                                item.container + ", not in the list of collections";
                         Log.e(TAG, msg);
                     }
                 }
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 0ba468d..e8767bf 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -276,7 +276,7 @@
                                     PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING);
                             // In case an app is archived, we need to make sure that archived state
                             // in WorkspaceItemInfo is refreshed.
-                            if (Utilities.enableSupportForArchiving() && !activities.isEmpty()) {
+                            if (Flags.enableSupportForArchiving() && !activities.isEmpty()) {
                                 boolean newArchivalState = activities.get(
                                         0).getActivityInfo().isArchived;
                                 if (newArchivalState != si.isArchived()) {
@@ -361,17 +361,10 @@
         }
 
         if (!removedPackages.isEmpty() || !removedComponents.isEmpty()) {
-            // This predicate is used to mark an ItemInfo for removal if its package or component
-            // is marked for removal.
-            Predicate<ItemInfo> removeAppMatch =
+            Predicate<ItemInfo> removeMatch =
                     ItemInfoMatcher.ofPackages(removedPackages, mUser)
                             .or(ItemInfoMatcher.ofComponents(removedComponents, mUser))
                             .and(ItemInfoMatcher.ofItemIds(forceKeepShortcuts).negate());
-            // This predicate is used to mark an app pair for removal if it contains an app marked
-            // for removal.
-            Predicate<ItemInfo> removeAppPairMatch =
-                    ItemInfoMatcher.forAppPairMatch(removeAppMatch);
-            Predicate<ItemInfo> removeMatch = removeAppMatch.or(removeAppPairMatch);
             deleteAndBindComponentsRemoved(removeMatch,
                     "removed because the corresponding package or component is removed. "
                             + "mOp=" + mOp + " removedPackages=" + removedPackages.stream().collect(
diff --git a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
index 59f56df..ba612cc 100644
--- a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
+++ b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
@@ -26,12 +26,15 @@
 import android.text.TextUtils
 import android.util.Log
 import android.util.LongSparseArray
+import com.android.launcher3.Flags
 import com.android.launcher3.InvariantDeviceProfile
 import com.android.launcher3.LauncherAppState
 import com.android.launcher3.LauncherSettings.Favorites
 import com.android.launcher3.Utilities
 import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError
 import com.android.launcher3.logging.FileLog
+import com.android.launcher3.model.data.AppPairInfo
+import com.android.launcher3.model.data.FolderInfo
 import com.android.launcher3.model.data.IconRequestInfo
 import com.android.launcher3.model.data.ItemInfoWithIcon
 import com.android.launcher3.model.data.LauncherAppWidgetInfo
@@ -332,7 +335,7 @@
             }
             if (
                 (c.restoreFlag != 0 ||
-                    Utilities.enableSupportForArchiving() &&
+                    Flags.enableSupportForArchiving() &&
                         activityInfo != null &&
                         activityInfo.applicationInfo.isArchived) && !TextUtils.isEmpty(targetPkg)
             ) {
@@ -344,7 +347,7 @@
                             ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE.inv()
                 } else if (
                     activityInfo == null ||
-                        (Utilities.enableSupportForArchiving() &&
+                        (Flags.enableSupportForArchiving() &&
                             activityInfo.applicationInfo.isArchived)
                 ) {
                     // For archived apps, include progress info in case there is
@@ -360,25 +363,40 @@
     }
 
     /**
-     * Loads the folder information from the database and formats it into a FolderInfo. Some of the
-     * processing for folder content items is done in LoaderTask after all the items in the
-     * workspace have been loaded. The loaded FolderInfos are stored in the BgDataModel.
+     * Loads CollectionInfo information from the database and formats it. This function runs while
+     * LoaderTask is still active; some of the processing for folder content items is done after all
+     * the items in the workspace have been loaded. The loaded and formatted CollectionInfo is then
+     * stored in the BgDataModel.
      */
     private fun processFolderOrAppPair() {
-        val folderInfo =
-            bgDataModel.findOrMakeFolder(c.id).apply {
-                c.applyCommonProperties(this)
-                itemType = c.itemType
-                // Do not trim the folder label, as is was set by the user.
-                title = c.getString(c.mTitleIndex)
-                spanX = 1
-                spanY = 1
-                options = c.options
-            }
+        var collection = bgDataModel.findOrMakeFolder(c.id)
+        // If we generated a placeholder Folder before this point, it may need to be replaced with
+        // an app pair.
+        if (c.itemType == Favorites.ITEM_TYPE_APP_PAIR && collection is FolderInfo) {
+            val folderInfo: FolderInfo = collection
+            val newAppPair = AppPairInfo()
+            // Move the placeholder's contents over to the new app pair.
+            folderInfo.contents.forEach(newAppPair::add)
+            collection = newAppPair
+            // Remove the placeholder and add the app pair into the data model.
+            bgDataModel.collections.remove(c.id)
+            bgDataModel.collections.put(c.id, collection)
+        }
 
-        // no special handling required for restored folders
+        c.applyCommonProperties(collection)
+        // Do not trim the folder label, as is was set by the user.
+        collection.title = c.getString(c.mTitleIndex)
+        collection.spanX = 1
+        collection.spanY = 1
+        if (collection is FolderInfo) {
+            collection.options = c.options
+        } else {
+            // An app pair may be inside another folder, so it needs to preserve rank information.
+            collection.rank = c.rank
+        }
+
         c.markRestored()
-        c.checkAndAddItem(folderInfo, bgDataModel, memoryLogger)
+        c.checkAndAddItem(collection, bgDataModel, memoryLogger)
     }
 
     /**
@@ -443,7 +461,7 @@
                         !isSafeMode &&
                         (si == null) &&
                         (lapi == null) &&
-                        !(Utilities.enableSupportForArchiving() &&
+                        !(Flags.enableSupportForArchiving() &&
                             pmHelper.isAppArchived(component.packageName))
                 ) {
                     // Restore never started
diff --git a/src/com/android/launcher3/model/data/AppInfo.java b/src/com/android/launcher3/model/data/AppInfo.java
index 210d720..93ba619 100644
--- a/src/com/android/launcher3/model/data/AppInfo.java
+++ b/src/com/android/launcher3/model/data/AppInfo.java
@@ -181,7 +181,7 @@
         if (PackageManagerHelper.isAppSuspended(appInfo)) {
             info.runtimeStatusFlags |= FLAG_DISABLED_SUSPENDED;
         }
-        if (Utilities.enableSupportForArchiving() && lai.getActivityInfo().isArchived) {
+        if (Flags.enableSupportForArchiving() && lai.getActivityInfo().isArchived) {
             info.runtimeStatusFlags |= FLAG_ARCHIVED;
         }
         info.runtimeStatusFlags |= (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0
diff --git a/src/com/android/launcher3/model/data/AppPairInfo.kt b/src/com/android/launcher3/model/data/AppPairInfo.kt
new file mode 100644
index 0000000..4081316
--- /dev/null
+++ b/src/com/android/launcher3/model/data/AppPairInfo.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model.data
+
+import android.content.Context
+import com.android.launcher3.LauncherSettings
+import com.android.launcher3.logger.LauncherAtom
+import com.android.launcher3.views.ActivityContext
+
+/** A type of app collection that launches multiple apps into split screen. */
+class AppPairInfo() : CollectionInfo() {
+    init {
+        itemType = LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR
+    }
+
+    /** Convenience constructor, calls primary constructor and init block */
+    constructor(app1: WorkspaceItemInfo, app2: WorkspaceItemInfo) : this() {
+        add(app1)
+        add(app2)
+    }
+
+    /** Adds an element to the contents array. */
+    override fun add(item: WorkspaceItemInfo) {
+        contents.add(item)
+    }
+
+    /** Returns the first app in the pair. */
+    fun getFirstApp() = contents[0]
+
+    /** Returns the second app in the pair. */
+    fun getSecondApp() = contents[1]
+
+    /** Returns if either of the app pair members is currently disabled. */
+    override fun isDisabled() = anyMatch { it.isDisabled }
+
+    /** Checks if the app pair is launchable at the current screen size. */
+    fun isLaunchable(context: Context) =
+        (ActivityContext.lookupContext(context) as ActivityContext).getDeviceProfile().isTablet ||
+            noneMatch { it.hasStatusFlag(WorkspaceItemInfo.FLAG_NON_RESIZEABLE) }
+
+    /** Generates an ItemInfo for logging. */
+    override fun buildProto(cInfo: CollectionInfo?): LauncherAtom.ItemInfo {
+        val appPairIcon = LauncherAtom.FolderIcon.newBuilder().setCardinality(contents.size)
+        appPairIcon.setLabelInfo(title.toString())
+        return getDefaultItemInfoBuilder()
+            .setFolderIcon(appPairIcon)
+            .setRank(rank)
+            .setContainerInfo(getContainerInfo())
+            .build()
+    }
+}
diff --git a/src/com/android/launcher3/model/data/CollectionInfo.kt b/src/com/android/launcher3/model/data/CollectionInfo.kt
new file mode 100644
index 0000000..2b865a5
--- /dev/null
+++ b/src/com/android/launcher3/model/data/CollectionInfo.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model.data
+
+import com.android.launcher3.LauncherSettings
+import com.android.launcher3.logger.LauncherAtom
+import com.android.launcher3.util.ContentWriter
+import java.util.function.Predicate
+
+abstract class CollectionInfo : ItemInfo() {
+    var contents: ArrayList<WorkspaceItemInfo> = ArrayList()
+
+    abstract fun add(item: WorkspaceItemInfo)
+
+    /** Convenience function. Checks contents to see if any match a given predicate. */
+    fun anyMatch(matcher: Predicate<in WorkspaceItemInfo>): Boolean {
+        return contents.stream().anyMatch(matcher)
+    }
+
+    /** Convenience function. Returns true if none of the contents match a given predicate. */
+    fun noneMatch(matcher: Predicate<in WorkspaceItemInfo>): Boolean {
+        return contents.stream().noneMatch(matcher)
+    }
+
+    override fun onAddToDatabase(writer: ContentWriter) {
+        super.onAddToDatabase(writer)
+        writer.put(LauncherSettings.Favorites.TITLE, title)
+    }
+
+    /** Returns the collection wrapped as {@link LauncherAtom.ItemInfo} for logging. */
+    override fun buildProto(): LauncherAtom.ItemInfo {
+        return buildProto(null)
+    }
+}
diff --git a/src/com/android/launcher3/model/data/FolderInfo.java b/src/com/android/launcher3/model/data/FolderInfo.java
index 83ba2b3..1bbb2fe 100644
--- a/src/com/android/launcher3/model/data/FolderInfo.java
+++ b/src/com/android/launcher3/model/data/FolderInfo.java
@@ -24,8 +24,6 @@
 import static com.android.launcher3.logger.LauncherAtom.Attribute.MANUAL_LABEL;
 import static com.android.launcher3.logger.LauncherAtom.Attribute.SUGGESTED_LABEL;
 
-import android.os.Process;
-
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
@@ -49,7 +47,7 @@
 /**
  * Represents a folder containing shortcuts or apps.
  */
-public class FolderInfo extends ItemInfo {
+public class FolderInfo extends CollectionInfo {
 
     public static final int NO_FLAGS = 0x00000000;
 
@@ -100,27 +98,15 @@
 
     public FolderNameInfos suggestedFolderNames;
 
-    /**
-     * The apps and shortcuts
-     */
-    public ArrayList<WorkspaceItemInfo> contents = new ArrayList<>();
-
     private ArrayList<FolderListener> mListeners = new ArrayList<>();
 
     public FolderInfo() {
         itemType = LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
-        user = Process.myUserHandle();
     }
 
-    /**
-     * Create an app pair, a type of app collection that launches multiple apps into split screen
-     */
-    public static FolderInfo createAppPair(WorkspaceItemInfo app1, WorkspaceItemInfo app2) {
-        FolderInfo newAppPair = new FolderInfo();
-        newAppPair.itemType = LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
-        newAppPair.add(app1, /* animate */ false);
-        newAppPair.add(app2, /* animate */ false);
-        return newAppPair;
+    /** Adds a app or shortcut to the contents array without animation. */
+    public void add(@NonNull WorkspaceItemInfo item) {
+        add(item, false /* animate */);
     }
 
     /**
@@ -129,15 +115,15 @@
      * @param item
      */
     public void add(WorkspaceItemInfo item, boolean animate) {
-        add(item, contents.size(), animate);
+        add(item, getContents().size(), animate);
     }
 
     /**
      * Add an app or shortcut for a specified rank.
      */
     public void add(WorkspaceItemInfo item, int rank, boolean animate) {
-        rank = Utilities.boundToRange(rank, 0, contents.size());
-        contents.add(rank, item);
+        rank = Utilities.boundToRange(rank, 0, getContents().size());
+        getContents().add(rank, item);
         for (int i = 0; i < mListeners.size(); i++) {
             mListeners.get(i).onAdd(item, rank);
         }
@@ -157,7 +143,7 @@
      * Remove all matching app or shortcut. Does not change the DB.
      */
     public void removeAll(List<WorkspaceItemInfo> items, boolean animate) {
-        contents.removeAll(items);
+        getContents().removeAll(items);
         for (int i = 0; i < mListeners.size(); i++) {
             mListeners.get(i).onRemove(items);
         }
@@ -167,8 +153,7 @@
     @Override
     public void onAddToDatabase(@NonNull ContentWriter writer) {
         super.onAddToDatabase(writer);
-        writer.put(LauncherSettings.Favorites.TITLE, title)
-                .put(LauncherSettings.Favorites.OPTIONS, options);
+        writer.put(LauncherSettings.Favorites.OPTIONS, options);
     }
 
     public void addListener(FolderListener listener) {
@@ -219,9 +204,9 @@
 
     @NonNull
     @Override
-    public LauncherAtom.ItemInfo buildProto(@Nullable FolderInfo fInfo) {
+    public LauncherAtom.ItemInfo buildProto(@Nullable CollectionInfo cInfo) {
         FolderIcon.Builder folderIcon = FolderIcon.newBuilder()
-                .setCardinality(contents.size());
+                .setCardinality(getContents().size());
         if (LabelState.SUGGESTED.equals(getLabelState())) {
             folderIcon.setLabelInfo(title.toString());
         }
@@ -278,20 +263,11 @@
     public ItemInfo makeShallowCopy() {
         FolderInfo folderInfo = new FolderInfo();
         folderInfo.copyFrom(this);
-        folderInfo.contents = this.contents;
+        folderInfo.setContents(this.getContents());
         return folderInfo;
     }
 
     /**
-     * Returns {@link LauncherAtom.FolderIcon} wrapped as {@link LauncherAtom.ItemInfo} for logging.
-     */
-    @NonNull
-    @Override
-    public LauncherAtom.ItemInfo buildProto() {
-        return buildProto(null);
-    }
-
-    /**
      * Returns index of the accepted suggestion.
      */
     public OptionalInt getAcceptedSuggestionIndex() {
@@ -371,13 +347,4 @@
         }
         return LauncherAtom.ToState.TO_STATE_UNSPECIFIED;
     }
-
-    @Override
-    public boolean isDisabled() {
-        if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR) {
-            return contents.stream().anyMatch((WorkspaceItemInfo::isDisabled));
-        }
-
-        return super.isDisabled();
-    }
 }
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index 55849c2..348a953 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -345,10 +345,9 @@
 
     /**
      * Creates {@link LauncherAtom.ItemInfo} with important fields and parent container info.
-     * @param fInfo
      */
     @NonNull
-    public LauncherAtom.ItemInfo buildProto(@Nullable final FolderInfo fInfo) {
+    public LauncherAtom.ItemInfo buildProto(@Nullable final CollectionInfo cInfo) {
         LauncherAtom.ItemInfo.Builder itemBuilder = getDefaultItemInfoBuilder();
         Optional<ComponentName> nullableComponent = Optional.ofNullable(getTargetComponent());
         switch (itemType) {
@@ -394,21 +393,21 @@
             default:
                 break;
         }
-        if (fInfo != null) {
+        if (cInfo != null) {
             LauncherAtom.FolderContainer.Builder folderBuilder =
                     LauncherAtom.FolderContainer.newBuilder();
             folderBuilder.setGridX(cellX).setGridY(cellY).setPageIndex(screenId);
 
-            switch (fInfo.container) {
+            switch (cInfo.container) {
                 case CONTAINER_HOTSEAT:
                 case CONTAINER_HOTSEAT_PREDICTION:
                     folderBuilder.setHotseat(LauncherAtom.HotseatContainer.newBuilder()
-                            .setIndex(fInfo.screenId));
+                            .setIndex(cInfo.screenId));
                     break;
                 case CONTAINER_DESKTOP:
                     folderBuilder.setWorkspace(LauncherAtom.WorkspaceContainer.newBuilder()
-                            .setPageIndex(fInfo.screenId)
-                            .setGridX(fInfo.cellX).setGridY(fInfo.cellY));
+                            .setPageIndex(cInfo.screenId)
+                            .setGridX(cInfo.cellX).setGridY(cInfo.cellY));
                     break;
             }
             itemBuilder.setContainerInfo(ContainerInfo.newBuilder().setFolder(folderBuilder));
diff --git a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
index 352c363..f8878b6 100644
--- a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
@@ -22,6 +22,7 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.Flags;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.BitmapInfo.DrawableCreationFlags;
@@ -171,7 +172,7 @@
      * Returns true if the app corresponding to the item is archived.
      */
     public boolean isArchived() {
-        if (!Utilities.enableSupportForArchiving()) {
+        if (!Flags.enableSupportForArchiving()) {
             return false;
         }
         return (runtimeStatusFlags & FLAG_ARCHIVED) != 0;
diff --git a/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java b/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
index 6fa8c54..f4dda55 100644
--- a/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
@@ -271,8 +271,8 @@
 
     @NonNull
     @Override
-    public LauncherAtom.ItemInfo buildProto(@Nullable FolderInfo folderInfo) {
-        LauncherAtom.ItemInfo info = super.buildProto(folderInfo);
+    public LauncherAtom.ItemInfo buildProto(@Nullable CollectionInfo collectionInfo) {
+        LauncherAtom.ItemInfo info = super.buildProto(collectionInfo);
         return info.toBuilder()
                 .setWidget(info.getWidget().toBuilder().setWidgetFeatures(widgetFeatures))
                 .addItemAttributes(getAttribute(sourceContainer))
diff --git a/src/com/android/launcher3/pm/InstallSessionHelper.java b/src/com/android/launcher3/pm/InstallSessionHelper.java
index f3769d5..4a3318e 100644
--- a/src/com/android/launcher3/pm/InstallSessionHelper.java
+++ b/src/com/android/launcher3/pm/InstallSessionHelper.java
@@ -29,6 +29,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 
+import com.android.launcher3.Flags;
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.SessionCommitReceiver;
 import com.android.launcher3.Utilities;
@@ -217,7 +218,7 @@
                 && !promiseIconAddedForId(sessionInfo.getSessionId())) {
             // In case of unarchival, we do not want to add a workspace promise icon if one is
             // not already present. For general app installations however, we do support it.
-            if (!Utilities.enableSupportForArchiving() || !sessionInfo.isUnarchival()) {
+            if (!Flags.enableSupportForArchiving() || !sessionInfo.isUnarchival()) {
                 FileLog.d(LOG, "Adding package name to install queue: "
                         + sessionInfo.getAppPackageName());
 
@@ -232,7 +233,7 @@
 
     public boolean verifySessionInfo(@Nullable final PackageInstaller.SessionInfo sessionInfo) {
         // For archived apps we always want to show promise icons and the checks below don't apply.
-        if (Utilities.enableSupportForArchiving() && sessionInfo != null
+        if (Flags.enableSupportForArchiving() && sessionInfo != null
                 && sessionInfo.isUnarchival()) {
             return true;
         }
diff --git a/src/com/android/launcher3/pm/InstallSessionTracker.java b/src/com/android/launcher3/pm/InstallSessionTracker.java
index eacbc11..24d58f3 100644
--- a/src/com/android/launcher3/pm/InstallSessionTracker.java
+++ b/src/com/android/launcher3/pm/InstallSessionTracker.java
@@ -31,6 +31,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 
+import com.android.launcher3.Flags;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.PackageUserKey;
 
@@ -80,7 +81,7 @@
 
         helper.tryQueuePromiseAppIcon(sessionInfo);
 
-        if (Utilities.enableSupportForArchiving() && sessionInfo != null
+        if (Flags.enableSupportForArchiving() && sessionInfo != null
                 && sessionInfo.isUnarchival()) {
             // For archived apps, icon could already be present on the workspace. To make sure
             // the icon state is updated, we send a change event.
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 111931e..de24be7 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -41,6 +41,7 @@
 
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.BuildConfig;
+import com.android.launcher3.Flags;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherSettings;
@@ -53,6 +54,7 @@
 import com.android.launcher3.logging.InstanceIdSequence;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.AppPairInfo;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
@@ -101,11 +103,9 @@
         if (tag instanceof WorkspaceItemInfo) {
             onClickAppShortcut(v, (WorkspaceItemInfo) tag, launcher);
         } else if (tag instanceof FolderInfo) {
-            if (v instanceof FolderIcon) {
-                onClickFolderIcon(v);
-            } else if (v instanceof AppPairIcon) {
-                onClickAppPairIcon(v);
-            }
+            onClickFolderIcon(v);
+        } else if (tag instanceof AppPairInfo) {
+            onClickAppPairIcon(v);
         } else if (tag instanceof AppInfo) {
             startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher);
         } else if (tag instanceof LauncherAppWidgetInfo) {
@@ -150,7 +150,7 @@
     private static void onClickAppPairIcon(View v) {
         Launcher launcher = Launcher.getLauncher(v.getContext());
         AppPairIcon appPairIcon = (AppPairIcon) v;
-        if (!appPairIcon.isLaunchableAtScreenSize()) {
+        if (!appPairIcon.getInfo().isLaunchable(launcher)) {
             // Display a message for app pairs that are disabled due to screen size
             boolean isFoldable = InvariantDeviceProfile.INSTANCE.get(launcher)
                     .supportedProfiles.stream().anyMatch(dp -> dp.isTwoPanels);
@@ -159,8 +159,8 @@
                             : R.string.app_pair_unlaunchable_at_screen_size,
                     Toast.LENGTH_SHORT).show();
         } else if (appPairIcon.getInfo().isDisabled()) {
-            WorkspaceItemInfo app1 = appPairIcon.getInfo().contents.get(0);
-            WorkspaceItemInfo app2 = appPairIcon.getInfo().contents.get(1);
+            WorkspaceItemInfo app1 = appPairIcon.getInfo().getFirstApp();
+            WorkspaceItemInfo app2 = appPairIcon.getInfo().getSecondApp();
             // Show the user why the app pair is disabled.
             if (app1.isDisabled() && !handleDisabledItemClicked(app1, launcher)) {
                 // If handleDisabledItemClicked() did not handle the error message, we initiate an
@@ -340,7 +340,7 @@
 
         // Check for abandoned promise
         if ((v instanceof BubbleTextView) && shortcut.hasPromiseIconUi()
-                && (!Utilities.enableSupportForArchiving() || !shortcut.isArchived())) {
+                && (!Flags.enableSupportForArchiving() || !shortcut.isArchived())) {
             String packageName = shortcut.getIntent().getComponent() != null
                     ? shortcut.getIntent().getComponent().getPackageName()
                     : shortcut.getIntent().getPackage();
diff --git a/src/com/android/launcher3/util/ItemInflater.kt b/src/com/android/launcher3/util/ItemInflater.kt
index 0f8311d..ebf4656 100644
--- a/src/com/android/launcher3/util/ItemInflater.kt
+++ b/src/com/android/launcher3/util/ItemInflater.kt
@@ -29,6 +29,7 @@
 import com.android.launcher3.apppairs.AppPairIcon
 import com.android.launcher3.folder.FolderIcon
 import com.android.launcher3.model.ModelWriter
+import com.android.launcher3.model.data.AppPairInfo
 import com.android.launcher3.model.data.FolderInfo
 import com.android.launcher3.model.data.ItemInfo
 import com.android.launcher3.model.data.LauncherAppWidgetInfo
@@ -81,7 +82,7 @@
                     R.layout.app_pair_icon,
                     context,
                     parent,
-                    item as FolderInfo,
+                    item as AppPairInfo,
                     BubbleTextView.DISPLAY_WORKSPACE
                 )
             Favorites.ITEM_TYPE_APPWIDGET,
diff --git a/src/com/android/launcher3/util/ItemInfoMatcher.java b/src/com/android/launcher3/util/ItemInfoMatcher.java
index 3074111..063313a 100644
--- a/src/com/android/launcher3/util/ItemInfoMatcher.java
+++ b/src/com/android/launcher3/util/ItemInfoMatcher.java
@@ -65,20 +65,11 @@
      * Returns a matcher for items within folders.
      */
     public static Predicate<ItemInfo> forFolderMatch(Predicate<ItemInfo> childOperator) {
-        return info -> info instanceof FolderInfo && ((FolderInfo) info).contents.stream()
+        return info -> info instanceof FolderInfo && ((FolderInfo) info).getContents().stream()
                 .anyMatch(childOperator);
     }
 
     /**
-     * Returns a matcher for items within app pairs.
-     */
-    public static Predicate<ItemInfo> forAppPairMatch(Predicate<ItemInfo> childOperator) {
-        Predicate<ItemInfo> isAppPair = info ->
-                info instanceof FolderInfo fi && fi.itemType == Favorites.ITEM_TYPE_APP_PAIR;
-        return isAppPair.and(forFolderMatch(childOperator));
-    }
-
-    /**
      * Returns a matcher for items with provided ids
      */
     public static Predicate<ItemInfo> ofItemIds(IntSet ids) {
diff --git a/src/com/android/launcher3/util/LauncherBindableItemsContainer.java b/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
index 69786bb..02779ce 100644
--- a/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
+++ b/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
@@ -23,6 +23,7 @@
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.graphics.PreloadIconDrawable;
+import com.android.launcher3.model.data.AppPairInfo;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -59,7 +60,7 @@
                                 : null);
             } else if (info instanceof FolderInfo && v instanceof FolderIcon) {
                 ((FolderIcon) v).updatePreviewItems(updates::contains);
-            } else if (info instanceof FolderInfo && v instanceof AppPairIcon appPairIcon) {
+            } else if (info instanceof AppPairInfo && v instanceof AppPairIcon appPairIcon) {
                 appPairIcon.maybeRedrawForWorkspaceUpdate(updates::contains);
             }
 
@@ -89,7 +90,7 @@
                 ((PendingAppWidgetHostView) v).applyState();
             } else if (v instanceof FolderIcon && info instanceof FolderInfo) {
                 ((FolderIcon) v).updatePreviewItems(updates::contains);
-            } else if (info instanceof FolderInfo && v instanceof AppPairIcon appPairIcon) {
+            } else if (info instanceof AppPairInfo && v instanceof AppPairIcon appPairIcon) {
                 appPairIcon.maybeRedrawForWorkspaceUpdate(updates::contains);
             }
             // process all the shortcuts
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 606918e..851c795 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -38,6 +38,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.Flags;
 import com.android.launcher3.PendingAddItemInfo;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -109,7 +110,7 @@
      */
     public boolean isAppArchivedForUser(@NonNull final String packageName,
             @NonNull final UserHandle user) {
-        if (!Utilities.enableSupportForArchiving()) {
+        if (!Flags.enableSupportForArchiving()) {
             return false;
         }
         final ApplicationInfo info = getApplicationInfo(
@@ -290,6 +291,6 @@
     @SuppressWarnings("NewApi")
     private boolean isPackageInstalledOrArchived(ApplicationInfo info) {
         return (info.flags & ApplicationInfo.FLAG_INSTALLED) != 0 || (
-                Utilities.enableSupportForArchiving() && info.isArchived);
+                Flags.enableSupportForArchiving() && info.isArchived);
     }
 }
diff --git a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
index a501960..a916252 100644
--- a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
+++ b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
@@ -25,7 +25,7 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.PendingAddItemInfo;
 import com.android.launcher3.logger.LauncherAtom;
-import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.CollectionInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.widget.picker.WidgetRecommendationCategory;
 import com.android.launcher3.widget.util.WidgetSizes;
@@ -82,8 +82,8 @@
 
     @NonNull
     @Override
-    public LauncherAtom.ItemInfo buildProto(@Nullable FolderInfo folderInfo) {
-        LauncherAtom.ItemInfo info = super.buildProto(folderInfo);
+    public LauncherAtom.ItemInfo buildProto(@Nullable CollectionInfo collectionInfo) {
+        LauncherAtom.ItemInfo info = super.buildProto(collectionInfo);
         return info.toBuilder()
                 .addItemAttributes(LauncherAppWidgetInfo.getAttribute(sourceContainer))
                 .build();
diff --git a/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java b/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
index dbb2715..6d8f5f0 100644
--- a/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
+++ b/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
@@ -30,7 +30,7 @@
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.model.ModelDbController;
-import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.CollectionInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
 import com.android.launcher3.tapl.LauncherInstrumentation;
@@ -78,9 +78,8 @@
                     ContentWriter writer = new ContentWriter(mContext);
                     ItemInfo item = mItemsToSubmit.get(i).get();
 
-                    if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
-                        FolderInfo folderInfo = (FolderInfo) item;
-                        for (ItemInfo itemInfo : folderInfo.contents) {
+                    if (item instanceof CollectionInfo ci) {
+                        for (ItemInfo itemInfo : ci.getContents()) {
                             itemInfo.container = i;
                             containerItems.add(itemInfo);
                         }
diff --git a/tests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt b/tests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
index 4ec5b0e..3a1883c 100644
--- a/tests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
+++ b/tests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
@@ -28,6 +28,7 @@
 import com.android.launcher3.icons.BaseIconFactory
 import com.android.launcher3.icons.FastBitmapDrawable
 import com.android.launcher3.icons.UserBadgeDrawable
+import com.android.launcher3.model.data.FolderInfo
 import com.android.launcher3.model.data.WorkspaceItemInfo
 import com.android.launcher3.util.ActivityContextWrapper
 import com.android.launcher3.util.FlagOp
@@ -71,8 +72,8 @@
                     .build()
             )
             .loadModelSync()
-        folderItems = modelHelper.bgDataModel.folders.valueAt(0).contents
-        folderIcon.mInfo = modelHelper.bgDataModel.folders.valueAt(0)
+        folderItems = modelHelper.bgDataModel.collections.valueAt(0).contents
+        folderIcon.mInfo = modelHelper.bgDataModel.collections.valueAt(0) as FolderInfo
         folderIcon.mInfo.contents = folderItems
 
         // Set first icon to be themed.
diff --git a/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
index 7a0b60a..abb0c39 100644
--- a/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
+++ b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
@@ -160,6 +160,6 @@
     }
 
     private List<WorkspaceItemInfo> allItems() {
-        return ((FolderInfo) mModelHelper.getBgDataModel().itemsIdMap.get(1)).contents;
+        return ((FolderInfo) mModelHelper.getBgDataModel().itemsIdMap.get(1)).getContents();
     }
 }
diff --git a/tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java b/tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
index 2b89321..10785f7 100644
--- a/tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
+++ b/tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
@@ -87,7 +87,7 @@
         assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
         ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0);
         assertEquals(LauncherSettings.Favorites.ITEM_TYPE_FOLDER, info.itemType);
-        assertEquals(3, ((FolderInfo) info).contents.size());
+        assertEquals(3, ((FolderInfo) info).getContents().size());
     }
 
     @Test
@@ -102,7 +102,7 @@
         assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
         ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0);
         assertEquals(LauncherSettings.Favorites.ITEM_TYPE_FOLDER, info.itemType);
-        assertEquals(3, ((FolderInfo) info).contents.size());
+        assertEquals(3, ((FolderInfo) info).getContents().size());
         assertEquals("CustomFolder", info.title.toString());
     }
 
@@ -154,11 +154,11 @@
         // Verify folder
         assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
         FolderInfo info = (FolderInfo) mModelHelper.getBgDataModel().workspaceItems.get(0);
-        assertEquals(3, info.contents.size());
+        assertEquals(3, info.getContents().size());
 
         // Verify last icon
         assertEquals(LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT,
-                info.contents.get(info.contents.size() - 1).itemType);
+                info.getContents().get(info.getContents().size() - 1).itemType);
     }
 
     private void writeLayoutAndLoad(LauncherLayoutBuilder builder) throws Exception {
diff --git a/tests/src/com/android/launcher3/model/FolderIconLoadTest.kt b/tests/src/com/android/launcher3/model/FolderIconLoadTest.kt
index 2118ed6..ed587a1 100644
--- a/tests/src/com/android/launcher3/model/FolderIconLoadTest.kt
+++ b/tests/src/com/android/launcher3/model/FolderIconLoadTest.kt
@@ -165,11 +165,11 @@
         // Reload again with correct icon state
         app.model.forceReload()
         modelHelper.loadModelSync()
-        val folders = modelHelper.getBgDataModel().folders
+        val collections = modelHelper.getBgDataModel().collections
 
-        assertThat(folders.size()).isEqualTo(1)
-        assertThat(folders.valueAt(0).contents.size).isEqualTo(itemCount)
-        return folders.valueAt(0).contents
+        assertThat(collections.size()).isEqualTo(1)
+        assertThat(collections.valueAt(0).contents.size).isEqualTo(itemCount)
+        return collections.valueAt(0).contents
     }
 
     private fun verifyHighRes(items: ArrayList<WorkspaceItemInfo>, vararg indices: Int) {
diff --git a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
index e2ca31f..3d10a85 100644
--- a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
+++ b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
@@ -103,7 +103,7 @@
                 .runSyncOnBackgroundThread()
             Truth.assertThat(workspaceItems.size).isAtLeast(25)
             Truth.assertThat(appWidgets.size).isAtLeast(7)
-            Truth.assertThat(folders.size()).isAtLeast(8)
+            Truth.assertThat(collections.size()).isAtLeast(8)
             Truth.assertThat(itemsIdMap.size()).isAtLeast(40)
         }
 
diff --git a/tests/src/com/android/launcher3/util/ItemInflaterTest.kt b/tests/src/com/android/launcher3/util/ItemInflaterTest.kt
index efad899..0065527 100644
--- a/tests/src/com/android/launcher3/util/ItemInflaterTest.kt
+++ b/tests/src/com/android/launcher3/util/ItemInflaterTest.kt
@@ -36,6 +36,7 @@
 import com.android.launcher3.folder.FolderIcon
 import com.android.launcher3.model.ModelWriter
 import com.android.launcher3.model.data.AppInfo
+import com.android.launcher3.model.data.AppPairInfo
 import com.android.launcher3.model.data.FolderInfo
 import com.android.launcher3.model.data.LauncherAppWidgetInfo
 import com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_ID_NOT_VALID
@@ -170,7 +171,7 @@
 
     @Test
     fun test_app_pair_inflated_on_UI() {
-        val itemInfo = FolderInfo()
+        val itemInfo = AppPairInfo()
         itemInfo.itemType = ITEM_TYPE_APP_PAIR
         itemInfo.contents.add(workspaceItemInfo())
         itemInfo.contents.add(workspaceItemInfo())
@@ -186,7 +187,7 @@
     fun test_app_pair_inflated_on_BG() {
         setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WORKSPACE_INFLATION)
 
-        val itemInfo = FolderInfo()
+        val itemInfo = AppPairInfo()
         itemInfo.itemType = ITEM_TYPE_APP_PAIR
         itemInfo.contents.add(workspaceItemInfo())
         itemInfo.contents.add(workspaceItemInfo())