Merge "desktop-exploded-view: Initial implementation" into main
diff --git a/OWNERS b/OWNERS
index bed2acd..3f7a780 100644
--- a/OWNERS
+++ b/OWNERS
@@ -8,7 +8,6 @@
hyunyoungs@google.com
vadimt@google.com
winsonc@google.com
-awickham@google.com
agvard@google.com
# Launcher workspace eng team
@@ -21,6 +20,7 @@
pinyaoting@google.com
andonian@google.com
sihua@google.com
+abegovic@google.com
# Multitasking eng team
tracyzhou@google.com
@@ -55,11 +55,25 @@
twickham@google.com
victortulias@google.com
+## Note: some of the below overlap and also work on other integrations like Circle to Search.
+
+# All Apps / QSB team
+awickham@google.com
+brdayauon@google.com
+ganjam@google.com
+kylim@google.com
+
+# Smartspace team
+xilei@google.com
+davidct@google.com
+iamiam@google.com
+jiuyu@google.com
+
per-file FeatureFlags.java, globs = set noparent
-per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com, captaincole@google.com
+per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com, captaincole@google.com, abegovic@google.com
per-file DeviceConfigWrapper.java, globs = set noparent
-per-file DeviceConfigWrapper.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com
+per-file DeviceConfigWrapper.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com, abegovic@google.com
# Predictive Back
per-file LauncherBackAnimationController.java = shanh@google.com, gallmann@google.com
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 5186740..9fa2f50 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -65,13 +65,6 @@
}
flag {
- name: "enable_taskbar_connected_displays"
- namespace: "launcher"
- description: "Enables connected displays in taskbar."
- bug: "362720616"
-}
-
-flag {
name: "enable_taskbar_customization"
namespace: "launcher"
description: "Enables taskbar customization framework."
@@ -591,3 +584,20 @@
description: "Changes mouse interaction behavior"
bug: "388897603"
}
+
+flag {
+ name: "enable_alt_tab_kqs_on_connected_displays"
+ namespace: "lse_desktop_experience"
+ description: "Enable Alt + Tab KQS support on connected displays"
+ bug: "394007677"
+}
+
+flag {
+ name: "expressive_theme_in_taskbar_and_navigation"
+ namespace: "launcher"
+ description: "Enables the expressive theme and GSF font styles for Taskbar and Gesture Navigation"
+ bug: "394613212"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/aconfig/launcher_accessibility.aconfig b/aconfig/launcher_accessibility.aconfig
new file mode 100644
index 0000000..afee8fe
--- /dev/null
+++ b/aconfig/launcher_accessibility.aconfig
@@ -0,0 +1,12 @@
+package: "com.android.launcher3"
+container: "system"
+
+flag {
+ name: "remove_exclude_from_screen_magnification_flag_usage"
+ namespace: "accessibility"
+ description: "Remove the WindowManager PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION flag usage in Launcher"
+ bug: "369019568"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
\ No newline at end of file
diff --git a/quickstep/res/drawable/ic_close_option.xml b/quickstep/res/drawable/ic_close_option.xml
new file mode 100644
index 0000000..5681cb5
--- /dev/null
+++ b/quickstep/res/drawable/ic_close_option.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M256,760L200,704L424,480L200,256L256,200L480,424L704,200L760,256L536,480L760,704L704,760L480,536L256,760Z" />
+</vector>
diff --git a/quickstep/res/values-af/strings.xml b/quickstep/res/values-af/strings.xml
index 4c4a403..c71308f 100644
--- a/quickstep/res/values-af/strings.xml
+++ b/quickstep/res/values-af/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Vormvry"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Rekenaar"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Skuif na eksterne skerm"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Werkskerm"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Geen onlangse items nie"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Programgebruikinstellings"</string>
diff --git a/quickstep/res/values-am/strings.xml b/quickstep/res/values-am/strings.xml
index 2716fd5..a7b9957 100644
--- a/quickstep/res/values-am/strings.xml
+++ b/quickstep/res/values-am/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"ነፃ ቅጽ"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ዴስክቶፕ"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"ወደ ውጫዊ ማሳያ አንቀሳቅስ"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"ዴስክቶፕ"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"ምንም የቅርብ ጊዜ ንጥሎች የሉም"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"የመተግበሪያ አጠቃቀም ቅንብሮች"</string>
diff --git a/quickstep/res/values-ar/strings.xml b/quickstep/res/values-ar/strings.xml
index a866bb9..5b61fa7 100644
--- a/quickstep/res/values-ar/strings.xml
+++ b/quickstep/res/values-ar/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"شكل مجاني"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"الكمبيوتر المكتبي"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"نقل التطبيق إلى شاشة خارجية"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"إغلاق"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"كمبيوتر مكتبي"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"ما مِن عناصر تم استخدامها مؤخرًا"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"إعدادات استخدام التطبيق"</string>
@@ -46,8 +47,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"تم تفعيل ميزة \"التطبيقات المقترحة\"."</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ميزة \"التطبيقات المقترحة\" غير مفعّلة."</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"التطبيق المتوقع: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"دليل توجيهي للتنقُّل بالإيماءات"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"يُرجى تدوير الجهاز"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"يُرجى تدوير جهازك لإكمال الدليل التوجيهي للتنقُّل بالإيماءات."</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"تأكَّد من التمرير سريعًا من أقصى الحافة اليسرى أو اليمنى."</string>
diff --git a/quickstep/res/values-as/strings.xml b/quickstep/res/values-as/strings.xml
index 4292a76..d7cb836 100644
--- a/quickstep/res/values-as/strings.xml
+++ b/quickstep/res/values-as/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ডেস্কটপ"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"বাহ্যিক ডিছপ্লে’লৈ নিয়ক"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"বন্ধ কৰক"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"ডেস্কটপ"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"কোনো শেহতীয়া বস্তু নাই"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"এপে ব্যৱহাৰ কৰা ডেটাৰ ছেটিং"</string>
diff --git a/quickstep/res/values-az/strings.xml b/quickstep/res/values-az/strings.xml
index a7bd553..ab57177 100644
--- a/quickstep/res/values-az/strings.xml
+++ b/quickstep/res/values-az/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Sərbəst rejim"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Masaüstü"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Xarici displeyə köçürün"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Masaüstü"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Son elementlər yoxdur"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Tətbiq istifadə ayarları"</string>
diff --git a/quickstep/res/values-b+sr+Latn/strings.xml b/quickstep/res/values-b+sr+Latn/strings.xml
index ef46503..58a9c6b 100644
--- a/quickstep/res/values-b+sr+Latn/strings.xml
+++ b/quickstep/res/values-b+sr+Latn/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Slobodni oblik"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Računar"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Premestite na spoljni ekran"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Zatvori"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Računari"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nema nedavnih stavki"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Podešavanja korišćenja aplikacije"</string>
diff --git a/quickstep/res/values-be/strings.xml b/quickstep/res/values-be/strings.xml
index 205fe9d..08d855c 100644
--- a/quickstep/res/values-be/strings.xml
+++ b/quickstep/res/values-be/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Адвольная форма"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Працоўны стол"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Перамясціць на знешні дысплэй"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Працоўны стол"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Няма новых элементаў"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Налады выкарыстання праграмы"</string>
diff --git a/quickstep/res/values-bg/strings.xml b/quickstep/res/values-bg/strings.xml
index 048806f..f67e8e4 100644
--- a/quickstep/res/values-bg/strings.xml
+++ b/quickstep/res/values-bg/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Свободна форма"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"За компютър"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Преместване към външния екран"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Настолен компютър"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Няма скорошни елементи"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Настройки за използването на приложенията"</string>
diff --git a/quickstep/res/values-bn/strings.xml b/quickstep/res/values-bn/strings.xml
index dec7443..8ff5d76 100644
--- a/quickstep/res/values-bn/strings.xml
+++ b/quickstep/res/values-bn/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"ফ্রি-ফর্ম"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ডেস্কটপ"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"এক্সটার্নাল ডিসপ্লেতে সরিয়ে নিয়ে যান"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"ডেস্কটপ"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"কোনও সাম্প্রতিক আইটেম নেই"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"অ্যাপ ব্যবহারের সেটিংস"</string>
diff --git a/quickstep/res/values-bs/strings.xml b/quickstep/res/values-bs/strings.xml
index 626279f..9553e28 100644
--- a/quickstep/res/values-bs/strings.xml
+++ b/quickstep/res/values-bs/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Slobodan oblik"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Radna površina"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Premještanje na vanjski ekran"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Zatvori"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Radna površina"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nema nedavnih stavki"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Postavke korištenja aplikacije"</string>
diff --git a/quickstep/res/values-ca/strings.xml b/quickstep/res/values-ca/strings.xml
index 810e8f8..44ef6c3 100644
--- a/quickstep/res/values-ca/strings.xml
+++ b/quickstep/res/values-ca/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Format lliure"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Escriptori"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Mou a la pantalla externa"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Escriptori"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"No hi ha cap element recent"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Configuració d\'ús d\'aplicacions"</string>
diff --git a/quickstep/res/values-cs/strings.xml b/quickstep/res/values-cs/strings.xml
index 9fd2fa5..d809cbe 100644
--- a/quickstep/res/values-cs/strings.xml
+++ b/quickstep/res/values-cs/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Neomezený režim"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Počítač"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Přesunout na externí displej"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Zavřít"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Počítač"</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>
diff --git a/quickstep/res/values-da/strings.xml b/quickstep/res/values-da/strings.xml
index b2a5f33..224ac11 100644
--- a/quickstep/res/values-da/strings.xml
+++ b/quickstep/res/values-da/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Frit format"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Computertilstand"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Flyt til ekstern skærm"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Computertilstand"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Ingen nye elementer"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Indstillinger for appforbrug"</string>
diff --git a/quickstep/res/values-de/strings.xml b/quickstep/res/values-de/strings.xml
index 93fba3b..40c1cce 100644
--- a/quickstep/res/values-de/strings.xml
+++ b/quickstep/res/values-de/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform-Modus"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktopmodus"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Auf externes Display verschieben"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktopmodus"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Keine kürzlich verwendeten Elemente"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Einstellungen zur App-Nutzung"</string>
@@ -106,7 +108,7 @@
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Tutorial zur Bedienung überspringen?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Du findest es später auch in der <xliff:g id="NAME">%1$s</xliff:g> App"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Abbrechen"</string>
- <string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Überspringen"</string>
+ <string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Überspringen"</string>
<string name="accessibility_rotate_button" msgid="4771825231336502943">"Bildschirm drehen"</string>
<string name="taskbar_edu_a11y_title" msgid="5417986057866415355">"Informationen zur Taskleiste"</string>
<string name="taskbar_edu_splitscreen" msgid="5605512479258053350">"App zur Seite ziehen, um zwei Apps gleichzeitig zu nutzen"</string>
diff --git a/quickstep/res/values-el/strings.xml b/quickstep/res/values-el/strings.xml
index 960831e..046e047 100644
--- a/quickstep/res/values-el/strings.xml
+++ b/quickstep/res/values-el/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Ελεύθερη μορφή"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Υπολογιστής"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Μετακίνηση σε εξωτερική οθόνη"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Υπολογιστής"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Δεν υπάρχουν πρόσφατα στοιχεία"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Ρυθμίσεις χρήσης εφαρμογής"</string>
diff --git a/quickstep/res/values-en-rAU/strings.xml b/quickstep/res/values-en-rAU/strings.xml
index bf115f2..0c8384c 100644
--- a/quickstep/res/values-en-rAU/strings.xml
+++ b/quickstep/res/values-en-rAU/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Move to external display"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"No recent items"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"App usage settings"</string>
diff --git a/quickstep/res/values-en-rCA/strings.xml b/quickstep/res/values-en-rCA/strings.xml
index cd6abc3..4efd369 100644
--- a/quickstep/res/values-en-rCA/strings.xml
+++ b/quickstep/res/values-en-rCA/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Move to external display"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Close"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"No recent items"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"App usage settings"</string>
diff --git a/quickstep/res/values-en-rGB/strings.xml b/quickstep/res/values-en-rGB/strings.xml
index bf115f2..0c8384c 100644
--- a/quickstep/res/values-en-rGB/strings.xml
+++ b/quickstep/res/values-en-rGB/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Move to external display"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"No recent items"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"App usage settings"</string>
diff --git a/quickstep/res/values-en-rIN/strings.xml b/quickstep/res/values-en-rIN/strings.xml
index bf115f2..0c8384c 100644
--- a/quickstep/res/values-en-rIN/strings.xml
+++ b/quickstep/res/values-en-rIN/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Move to external display"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"No recent items"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"App usage settings"</string>
diff --git a/quickstep/res/values-es-rUS/strings.xml b/quickstep/res/values-es-rUS/strings.xml
index 0955f28..5ba3212 100644
--- a/quickstep/res/values-es-rUS/strings.xml
+++ b/quickstep/res/values-es-rUS/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Formato libre"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Escritorio"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Mover a pantalla externa"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Cerrar"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Computadoras"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"No hay elementos recientes"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Configuración de uso de la app"</string>
@@ -89,7 +90,7 @@
<string name="allset_title" msgid="5021126669778966707">"Todo listo"</string>
<string name="allset_hint" msgid="459504134589971527">"Desliza el dedo hacia arriba para ir a la página principal"</string>
<string name="allset_button_hint" msgid="2395219947744706291">"Presiona el botón de inicio para ir a la pantalla principal"</string>
- <string name="allset_description_generic" msgid="5385500062202019855">"Ya puedes comenzar a usar tu <xliff:g id="DEVICE">%1$s</xliff:g>."</string>
+ <string name="allset_description_generic" msgid="5385500062202019855">"Ya puedes comenzar a usar tu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="default_device_name" msgid="6660656727127422487">"dispositivo"</string>
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Configuración de navegación del sistema"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Compartir"</string>
diff --git a/quickstep/res/values-es/strings.xml b/quickstep/res/values-es/strings.xml
index a5ac1d2..6c412f7 100644
--- a/quickstep/res/values-es/strings.xml
+++ b/quickstep/res/values-es/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Formato libre"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Escritorio"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Mover a pantalla externa"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Ordenador"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"No hay nada reciente"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Ajustes de uso de la aplicación"</string>
diff --git a/quickstep/res/values-et/strings.xml b/quickstep/res/values-et/strings.xml
index bb56954..d4e041a 100644
--- a/quickstep/res/values-et/strings.xml
+++ b/quickstep/res/values-et/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Vabavorm"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Lauaarvuti režiim"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Liikuge välisele ekraanile"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Sule"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Töölaud"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Hiljutisi üksusi pole"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Rakenduse kasutuse seaded"</string>
@@ -89,7 +90,7 @@
<string name="allset_title" msgid="5021126669778966707">"Valmis!"</string>
<string name="allset_hint" msgid="459504134589971527">"Avalehele liikumiseks pühkige üles"</string>
<string name="allset_button_hint" msgid="2395219947744706291">"Avakuvale liikumiseks puudutage avakuva nuppu"</string>
- <string name="allset_description_generic" msgid="5385500062202019855">"<xliff:g id="DEVICE">%1$s</xliff:g> on nüüd kasutamiseks valmis"</string>
+ <string name="allset_description_generic" msgid="5385500062202019855">"Teie <xliff:g id="DEVICE">%1$s</xliff:g> on nüüd kasutamiseks valmis."</string>
<string name="default_device_name" msgid="6660656727127422487">"seade"</string>
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Süsteemi navigeerimisseaded"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Jaga"</string>
diff --git a/quickstep/res/values-eu/strings.xml b/quickstep/res/values-eu/strings.xml
index 9ee1255..892089d 100644
--- a/quickstep/res/values-eu/strings.xml
+++ b/quickstep/res/values-eu/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Modu librea"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Ordenagailua"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Eraman kanpoko pantailara"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Mahaigaina"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Ez dago azkenaldi honetako ezer"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Aplikazioen erabileraren ezarpenak"</string>
diff --git a/quickstep/res/values-fa/strings.xml b/quickstep/res/values-fa/strings.xml
index d1ca8ed..7dcdb7f 100644
--- a/quickstep/res/values-fa/strings.xml
+++ b/quickstep/res/values-fa/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"حالت رایانه"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"انتقال به نمایشگر خارجی"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"بستن"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"رایانه"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"چیز جدیدی اینجا نیست"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"تنظیمات استفاده از برنامه"</string>
diff --git a/quickstep/res/values-fi/strings.xml b/quickstep/res/values-fi/strings.xml
index 13750dc..0e360d0 100644
--- a/quickstep/res/values-fi/strings.xml
+++ b/quickstep/res/values-fi/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Vapaamuotoinen"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Tietokone"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Siirrä ulkoiselle näytölle"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Tietokone"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Ei viimeaikaisia kohteita"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Sovelluksen käyttöasetukset"</string>
diff --git a/quickstep/res/values-fr-rCA/strings.xml b/quickstep/res/values-fr-rCA/strings.xml
index b83e33c..e7619c2 100644
--- a/quickstep/res/values-fr-rCA/strings.xml
+++ b/quickstep/res/values-fr-rCA/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Forme libre"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Bureau"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Passer à un écran externe"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Fermer"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Bureau"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Aucun élément récent"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Paramètres d\'utilisation de l\'appli"</string>
diff --git a/quickstep/res/values-fr/strings.xml b/quickstep/res/values-fr/strings.xml
index 66102d5..9982b25 100644
--- a/quickstep/res/values-fr/strings.xml
+++ b/quickstep/res/values-fr/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Format libre"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Mode ordinateur"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Déplacer vers l\'écran externe"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Fermer"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Mode ordinateur"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Aucun élément récent"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Paramètres de consommation de l\'application"</string>
diff --git a/quickstep/res/values-gl/strings.xml b/quickstep/res/values-gl/strings.xml
index 16c448c..f4260f3 100644
--- a/quickstep/res/values-gl/strings.xml
+++ b/quickstep/res/values-gl/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Forma libre"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Escritorio"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Mover á pantalla externa"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Ordenador"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Non hai elementos recentes"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Configuración do uso de aplicacións"</string>
diff --git a/quickstep/res/values-gu/strings.xml b/quickstep/res/values-gu/strings.xml
index 0cc6091..2797f82 100644
--- a/quickstep/res/values-gu/strings.xml
+++ b/quickstep/res/values-gu/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"ફ્રિફોર્મ"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ડેસ્કટૉપ"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"બાહ્ય ડિસ્પ્લે પર ખસેડો"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"ડેસ્કટૉપ"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"તાજેતરની કોઈ આઇટમ નથી"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ઍપ વપરાશનું સેટિંગ"</string>
diff --git a/quickstep/res/values-hi/strings.xml b/quickstep/res/values-hi/strings.xml
index 2135dac..5ed6ef5 100644
--- a/quickstep/res/values-hi/strings.xml
+++ b/quickstep/res/values-hi/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"फ़्रीफ़ॉर्म"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"डेस्कटॉप"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"बाहरी डिसप्ले पर जाएं"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"डेस्कटॉप"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"हाल ही का कोई आइटम नहीं है"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ऐप्लिकेशन इस्तेमाल की सेटिंग"</string>
diff --git a/quickstep/res/values-hr/strings.xml b/quickstep/res/values-hr/strings.xml
index 0fd3123..67fe240 100644
--- a/quickstep/res/values-hr/strings.xml
+++ b/quickstep/res/values-hr/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Slobodni oblik"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Računalo"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Premještanje na vanjski zaslon"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Zatvori"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Radna površina"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nema nedavnih stavki"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Postavke upotrebe aplikacija"</string>
diff --git a/quickstep/res/values-hu/strings.xml b/quickstep/res/values-hu/strings.xml
index 92ada47..4f15c7d 100644
--- a/quickstep/res/values-hu/strings.xml
+++ b/quickstep/res/values-hu/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Szabad forma"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Asztali"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Áthelyezés külső kijelzőre"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Asztali"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nincsenek mostanában használt elemek"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Alkalmazáshasználati beállítások"</string>
diff --git a/quickstep/res/values-hy/strings.xml b/quickstep/res/values-hy/strings.xml
index 7751ea1..edaa5bd 100644
--- a/quickstep/res/values-hy/strings.xml
+++ b/quickstep/res/values-hy/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Կամայական ձև"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Համակարգիչ"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Տեղափոխել արտաքին էկրան"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Համակարգիչ"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Այստեղ դեռ ոչինչ չկա"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Հավելվածի օգտագործման կարգավորումներ"</string>
diff --git a/quickstep/res/values-in/strings.xml b/quickstep/res/values-in/strings.xml
index 1909376..4b2ffd7 100644
--- a/quickstep/res/values-in/strings.xml
+++ b/quickstep/res/values-in/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Format bebas"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Pindahkan ke layar eksternal"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Tidak ada item yang baru dibuka"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Setelan penggunaan aplikasi"</string>
diff --git a/quickstep/res/values-is/strings.xml b/quickstep/res/values-is/strings.xml
index 30d6dad..3619a2d 100644
--- a/quickstep/res/values-is/strings.xml
+++ b/quickstep/res/values-is/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Frjálst snið"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Tölva"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Færa í annað tæki"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Tölva"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Engin nýleg atriði"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Notkunarstillingar forrits"</string>
diff --git a/quickstep/res/values-it/strings.xml b/quickstep/res/values-it/strings.xml
index 7f80107..17aefa4 100644
--- a/quickstep/res/values-it/strings.xml
+++ b/quickstep/res/values-it/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Forma libera"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Sposta sul display esterno"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nessun elemento recente"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Impostazioni di utilizzo delle app"</string>
diff --git a/quickstep/res/values-iw/strings.xml b/quickstep/res/values-iw/strings.xml
index f93f741..1cba30f 100644
--- a/quickstep/res/values-iw/strings.xml
+++ b/quickstep/res/values-iw/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"מצב חופשי"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"במחשב"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"העברה למסך חיצוני"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"מחשב"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"אין פריטים אחרונים"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"הגדרות שימוש באפליקציה"</string>
diff --git a/quickstep/res/values-ja/strings.xml b/quickstep/res/values-ja/strings.xml
index e3625cb..ded498a 100644
--- a/quickstep/res/values-ja/strings.xml
+++ b/quickstep/res/values-ja/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"フリーフォーム"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"デスクトップ"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"外部ディスプレイに移動する"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"閉じる"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"デスクトップ"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"最近のアイテムはありません"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"アプリの使用状況の設定"</string>
diff --git a/quickstep/res/values-ka/strings.xml b/quickstep/res/values-ka/strings.xml
index 623319d..1604495 100644
--- a/quickstep/res/values-ka/strings.xml
+++ b/quickstep/res/values-ka/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"თავისუფალი ფორმა"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"დესკტოპი"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"გარე ეკრანზე გადასვლა"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"დახურვა"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"დესკტოპი"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"ბოლოს გამოყენებული ერთეულები არ არის"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"აპების გამოყენების პარამეტრები"</string>
diff --git a/quickstep/res/values-kk/strings.xml b/quickstep/res/values-kk/strings.xml
index f707689..b95e708 100644
--- a/quickstep/res/values-kk/strings.xml
+++ b/quickstep/res/values-kk/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Еркін форма"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Компьютер"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Сыртқы дисплейге ауыстыру"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Жұмыс үстелі"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Соңғы элементтер жоқ"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Қолданбаны пайдалану параметрлері"</string>
diff --git a/quickstep/res/values-km/strings.xml b/quickstep/res/values-km/strings.xml
index 25e1ef2..1fdcde9 100644
--- a/quickstep/res/values-km/strings.xml
+++ b/quickstep/res/values-km/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"មុខងារទម្រង់សេរី"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ដែសថប"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"ផ្លាស់ទីទៅផ្ទាំងអេក្រង់ខាងក្រៅ"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"បិទ"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"អេក្រង់ដើម"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"មិនមានធាតុថ្មីៗទេ"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ការកំណត់ការប្រើប្រាស់កម្មវិធី"</string>
diff --git a/quickstep/res/values-kn/strings.xml b/quickstep/res/values-kn/strings.xml
index ed4b806..19d3ec9 100644
--- a/quickstep/res/values-kn/strings.xml
+++ b/quickstep/res/values-kn/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"ಮುಕ್ತಸ್ವರೂಪ"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ಡೆಸ್ಕ್ಟಾಪ್"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"ಬಾಹ್ಯ ಡಿಸ್ಪ್ಲೇಗೆ ಸರಿಸಿ"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"ಡೆಸ್ಕ್ಟಾಪ್"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"ಯಾವುದೇ ಇತ್ತೀಚಿನ ಐಟಂಗಳಿಲ್ಲ"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ಆ್ಯಪ್ ಬಳಕೆಯ ಸೆಟ್ಟಿಂಗ್ಗಳು"</string>
diff --git a/quickstep/res/values-ko/strings.xml b/quickstep/res/values-ko/strings.xml
index d11e0b2..2e89174 100644
--- a/quickstep/res/values-ko/strings.xml
+++ b/quickstep/res/values-ko/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"자유 형식"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"데스크톱"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"외부 디스플레이로 이동"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"데스크톱"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"최근 항목이 없습니다."</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"앱 사용 설정"</string>
diff --git a/quickstep/res/values-ky/strings.xml b/quickstep/res/values-ky/strings.xml
index 3d59332..ac2b06d 100644
--- a/quickstep/res/values-ky/strings.xml
+++ b/quickstep/res/values-ky/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Эркин форма режими"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Компьютер"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Тышкы экранга жылдыруу"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Компьютер"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Акыркы колдонмолор жок"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Колдонмону пайдалануу параметрлери"</string>
diff --git a/quickstep/res/values-lo/strings.xml b/quickstep/res/values-lo/strings.xml
index e480766..2ebaa7a 100644
--- a/quickstep/res/values-lo/strings.xml
+++ b/quickstep/res/values-lo/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"ຮູບແບບອິດສະຫລະ"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ເດັສທັອບ"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"ຍ້າຍໄປຫາຈໍສະແດງຜົນພາຍນອກ"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"ປິດ"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"ເດັສທັອບ"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"ບໍ່ມີລາຍການຫຼ້າສຸດ"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ການຕັ້ງຄ່າການນຳໃຊ້ແອັບ"</string>
diff --git a/quickstep/res/values-lt/strings.xml b/quickstep/res/values-lt/strings.xml
index 2bcbb6d..b57fee4 100644
--- a/quickstep/res/values-lt/strings.xml
+++ b/quickstep/res/values-lt/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Laisva forma"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Stalinis kompiuteris"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Perkelkite į išorinį ekraną"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Stalinis kompiuteris"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nėra jokių naujausių elementų"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Programos naudojimo nustatymai"</string>
diff --git a/quickstep/res/values-lv/strings.xml b/quickstep/res/values-lv/strings.xml
index 7d88140..07e9e1a 100644
--- a/quickstep/res/values-lv/strings.xml
+++ b/quickstep/res/values-lv/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Brīva forma"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Dators"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Pārvietošana uz ārējo displeju"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Darbvirsma"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nav nesenu vienumu."</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Lietotņu izmantošanas iestatījumi"</string>
@@ -89,7 +91,7 @@
<string name="allset_title" msgid="5021126669778966707">"Gatavs!"</string>
<string name="allset_hint" msgid="459504134589971527">"Velciet augšup, lai pārietu uz sākuma ekrānu"</string>
<string name="allset_button_hint" msgid="2395219947744706291">"Pieskarieties pogai Sākums, lai dotos uz sākuma ekrānu"</string>
- <string name="allset_description_generic" msgid="5385500062202019855">"Varat sākt izmantot savu ierīci (<xliff:g id="DEVICE">%1$s</xliff:g>)"</string>
+ <string name="allset_description_generic" msgid="5385500062202019855">"Jūsu <xliff:g id="DEVICE">%1$s</xliff:g> ir gatava lietošanai"</string>
<string name="default_device_name" msgid="6660656727127422487">"ierīce"</string>
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Sistēmas navigācijas iestatījumi"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Kopīgot"</string>
diff --git a/quickstep/res/values-mk/strings.xml b/quickstep/res/values-mk/strings.xml
index 632196b..0a133a3 100644
--- a/quickstep/res/values-mk/strings.xml
+++ b/quickstep/res/values-mk/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Работна површина"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Префрлете се на надворешниот екран"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"За компјутер"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Нема неодамнешни ставки"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Поставки за користење на апликациите"</string>
diff --git a/quickstep/res/values-ml/strings.xml b/quickstep/res/values-ml/strings.xml
index 2246889..849315b 100644
--- a/quickstep/res/values-ml/strings.xml
+++ b/quickstep/res/values-ml/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"ഫ്രീഫോം"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ഡെസ്ക്ടോപ്പ്"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"ബാഹ്യ ഡിസ്പ്ലേയിലേക്ക് നീക്കുക"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"അടയ്ക്കുക"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"ഡെസ്ക്ടോപ്പ്"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"സമീപകാല ഇനങ്ങൾ ഒന്നുമില്ല"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ആപ്പ് ഉപയോഗ ക്രമീകരണം"</string>
diff --git a/quickstep/res/values-mn/strings.xml b/quickstep/res/values-mn/strings.xml
index 94adf88..c50e87a 100644
--- a/quickstep/res/values-mn/strings.xml
+++ b/quickstep/res/values-mn/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Чөлөөтэй хувьсах"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Компьютер"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Гадаад дэлгэц рүү зөөх"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Дэлгэц"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Сүүлийн үеийн зүйл алга"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Апп ашиглалтын тохиргоо"</string>
diff --git a/quickstep/res/values-mr/strings.xml b/quickstep/res/values-mr/strings.xml
index d2fdb42..63f4317 100644
--- a/quickstep/res/values-mr/strings.xml
+++ b/quickstep/res/values-mr/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"फ्रीफॉर्म"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"डेस्कटॉप"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"बाह्य डिस्प्लेवर हलवा"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"डेस्कटॉप"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"कोणतेही अलीकडील आयटम नाहीत"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"अॅप वापर सेटिंग्ज"</string>
diff --git a/quickstep/res/values-ms/strings.xml b/quickstep/res/values-ms/strings.xml
index 66c5bdb..b039666 100644
--- a/quickstep/res/values-ms/strings.xml
+++ b/quickstep/res/values-ms/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Bentuk bebas"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Alihkan kepada paparan luaran"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Tutup"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Tiada item terbaharu"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Tetapan penggunaan apl"</string>
diff --git a/quickstep/res/values-my/strings.xml b/quickstep/res/values-my/strings.xml
index c9ac441..9df3d66 100644
--- a/quickstep/res/values-my/strings.xml
+++ b/quickstep/res/values-my/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"အလွတ်ပုံစံ"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ဒက်စ်တော့"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"ပြင်ပဖန်သားပြင်သို့ ရွှေ့ရန်"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"ဒက်စ်တော့"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"မကြာမီကဖွင့်ထားသည်များ မရှိပါ"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"အက်ပ်အသုံးပြုမှု ဆက်တင်များ"</string>
@@ -89,7 +91,7 @@
<string name="allset_title" msgid="5021126669778966707">"အားလုံး အဆင်သင့်ပါ။"</string>
<string name="allset_hint" msgid="459504134589971527">"ပင်မစာမျက်နှာသို့သွားရန် အပေါ်သို့ ပွတ်ဆွဲပါ"</string>
<string name="allset_button_hint" msgid="2395219947744706291">"ပင်မစာမျက်နှာသို့ သွားရန် ပင်မခလုတ်ကို တို့ပါ"</string>
- <string name="allset_description_generic" msgid="5385500062202019855">"<xliff:g id="DEVICE">%1$s</xliff:g> ကို စသုံးရန် အသင့်ဖြစ်ပါပြီ"</string>
+ <string name="allset_description_generic" msgid="5385500062202019855">"<xliff:g id="DEVICE">%1$s</xliff:g> စသုံးရန် အသင့်ဖြစ်ပါပြီ"</string>
<string name="default_device_name" msgid="6660656727127422487">"စက်"</string>
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"စနစ် လမ်းညွှန် ဆက်တင်များ"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"မျှဝေရန်"</string>
diff --git a/quickstep/res/values-nb/strings.xml b/quickstep/res/values-nb/strings.xml
index aa19606..d05884f 100644
--- a/quickstep/res/values-nb/strings.xml
+++ b/quickstep/res/values-nb/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Fritt format"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Skrivebord"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Flytt til ekstern skjerm"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Skrivebord"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Ingen nylige elementer"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Innstillinger for appbruk"</string>
diff --git a/quickstep/res/values-ne/strings.xml b/quickstep/res/values-ne/strings.xml
index 4793ad3..6efdca9 100644
--- a/quickstep/res/values-ne/strings.xml
+++ b/quickstep/res/values-ne/strings.xml
@@ -23,8 +23,10 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"फ्रिफर्म"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"डेस्कटप"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"सारेर बाह्य डिस्प्लेमा लैजानुहोस्"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"डेस्कटप"</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>
@@ -125,7 +127,7 @@
<string name="taskbar_button_back" msgid="8558862226461164514">"पछाडि जानुहोस्"</string>
<string name="taskbar_button_ime_switcher" msgid="1730244360907588541">"IME स्विचर"</string>
<string name="taskbar_button_recents" msgid="7273376136216613134">"हालसालैका बटनहरू"</string>
- <string name="taskbar_button_notifications" msgid="7471740351507357318">"सूचनाहरू"</string>
+ <string name="taskbar_button_notifications" msgid="7471740351507357318">"नोटिफिकेसनहरू"</string>
<string name="taskbar_button_quick_settings" msgid="227662894293189391">"द्रुत सेटिङ"</string>
<string name="taskbar_a11y_title" msgid="6432169809852243110">"टास्कबार"</string>
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"टास्कबार देखाइएको छ"</string>
diff --git a/quickstep/res/values-nl/strings.xml b/quickstep/res/values-nl/strings.xml
index 3cd9457..6cf9683 100644
--- a/quickstep/res/values-nl/strings.xml
+++ b/quickstep/res/values-nl/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Vrije vorm"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Verplaatsen naar extern scherm"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Sluiten"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Geen recente items"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Instellingen voor app-gebruik"</string>
diff --git a/quickstep/res/values-or/strings.xml b/quickstep/res/values-or/strings.xml
index 20420d9..66cbcdd 100644
--- a/quickstep/res/values-or/strings.xml
+++ b/quickstep/res/values-or/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"ଫ୍ରିଫର୍ମ"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ଡେସ୍କଟପ"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"ଏକ୍ସଟର୍ନଲ ଡିସପ୍ଲେକୁ ମୁଭ କରନ୍ତୁ"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"ଡେସ୍କଟପ"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"ବର୍ତ୍ତମାନର କୌଣସି ଆଇଟମ ନାହିଁ"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ଆପ ବ୍ୟବହାର ସେଟିଂସ"</string>
diff --git a/quickstep/res/values-pa/strings.xml b/quickstep/res/values-pa/strings.xml
index 5cab154..8489fd1 100644
--- a/quickstep/res/values-pa/strings.xml
+++ b/quickstep/res/values-pa/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"ਫ੍ਰੀਫਾਰਮ"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ਡੈਸਕਟਾਪ"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"ਬਾਹਰੀ ਡਿਸਪਲੇ \'ਤੇ ਜਾਓ"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"ਬੰਦ ਕਰੋ"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"ਡੈਸਕਟਾਪ"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"ਕੋਈ ਹਾਲੀਆ ਆਈਟਮ ਨਹੀਂ"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ਐਪ ਵਰਤੋਂ ਦੀਆਂ ਸੈਟਿੰਗਾਂ"</string>
@@ -89,7 +90,7 @@
<string name="allset_title" msgid="5021126669778966707">"ਪੂਰੀ ਤਰ੍ਹਾਂ ਤਿਆਰ!"</string>
<string name="allset_hint" msgid="459504134589971527">"ਹੋਮ \'ਤੇ ਜਾਣ ਲਈ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ"</string>
<string name="allset_button_hint" msgid="2395219947744706291">"ਆਪਣੀ ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਜਾਣ ਲਈ ਹੋਮ ਬਟਨ \'ਤੇ ਟੈਪ ਕਰੋ"</string>
- <string name="allset_description_generic" msgid="5385500062202019855">"ਤੁਸੀਂ ਆਪਣਾ <xliff:g id="DEVICE">%1$s</xliff:g> ਵਰਤਣ ਲਈ ਤਿਆਰ ਹੋ"</string>
+ <string name="allset_description_generic" msgid="5385500062202019855">"ਹੁਣ ਤੁਹਾਡਾ <xliff:g id="DEVICE">%1$s</xliff:g> ਵਰਤੋਂ ਲਈ ਤਿਆਰ ਹੈ"</string>
<string name="default_device_name" msgid="6660656727127422487">"ਡੀਵਾਈਸ"</string>
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"ਸਿਸਟਮ ਨੈਵੀਗੇਸ਼ਨ ਸੈਟਿੰਗਾਂ"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"ਸਾਂਝਾ ਕਰੋ"</string>
diff --git a/quickstep/res/values-pl/strings.xml b/quickstep/res/values-pl/strings.xml
index 94e39d0..b571ba2 100644
--- a/quickstep/res/values-pl/strings.xml
+++ b/quickstep/res/values-pl/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Tryb dowolny"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Pulpit"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Przenieś na wyświetlacz zewnętrzny"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Pulpit"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Brak ostatnich elementów"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Ustawienia użycia aplikacji"</string>
diff --git a/quickstep/res/values-pt-rPT/strings.xml b/quickstep/res/values-pt-rPT/strings.xml
index cace987..84a4f58 100644
--- a/quickstep/res/values-pt-rPT/strings.xml
+++ b/quickstep/res/values-pt-rPT/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Forma livre"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Computador"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Mover para o ecrã externo"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Fechar"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Computador"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nenhum item recente"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Definições de utilização de aplicações"</string>
diff --git a/quickstep/res/values-pt/strings.xml b/quickstep/res/values-pt/strings.xml
index 434cf6b..bb7d97d 100644
--- a/quickstep/res/values-pt/strings.xml
+++ b/quickstep/res/values-pt/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Forma livre"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Modo área de trabalho"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Mover para a tela externa"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Computador"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nenhum item recente"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Configurações de uso do app"</string>
diff --git a/quickstep/res/values-ro/strings.xml b/quickstep/res/values-ro/strings.xml
index ac25e6d..856bef7 100644
--- a/quickstep/res/values-ro/strings.xml
+++ b/quickstep/res/values-ro/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Formă liberă"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Mută pe ecranul extern"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Computer"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Niciun element recent"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Setări de utilizare a aplicației"</string>
diff --git a/quickstep/res/values-ru/strings.xml b/quickstep/res/values-ru/strings.xml
index 9700f16..9ebbc02 100644
--- a/quickstep/res/values-ru/strings.xml
+++ b/quickstep/res/values-ru/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Произвольная форма"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Мультиоконный режим"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Перенести на внешний дисплей"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Закрыть"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Мультиоконный режим"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Здесь пока ничего нет."</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Настройки использования приложения"</string>
diff --git a/quickstep/res/values-si/strings.xml b/quickstep/res/values-si/strings.xml
index cd33d09..3b1cada 100644
--- a/quickstep/res/values-si/strings.xml
+++ b/quickstep/res/values-si/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ඩෙස්ක්ටොපය"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"බාහිර සංදර්ශකය වෙත ගෙන යන්න"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"ඩෙස්ක්ටොපය"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"මෑත අයිතම නැත"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"යෙදුම් භාවිත සැකසීම්"</string>
diff --git a/quickstep/res/values-sk/strings.xml b/quickstep/res/values-sk/strings.xml
index 7f44a23..bf9adff 100644
--- a/quickstep/res/values-sk/strings.xml
+++ b/quickstep/res/values-sk/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Voľný režim"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Počítač"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Presunúť na externú obrazovku"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Zavrieť"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Počítač"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Žiadne nedávne položky"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Nastavenia využívania aplikácie"</string>
diff --git a/quickstep/res/values-sl/strings.xml b/quickstep/res/values-sl/strings.xml
index f504abc..440c0f1 100644
--- a/quickstep/res/values-sl/strings.xml
+++ b/quickstep/res/values-sl/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Prosta oblika"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Namizni računalnik"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Premik v zunanji zaslon"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Zapri"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Namizni način"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Ni nedavnih elementov"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Nastavitve uporabe aplikacij"</string>
diff --git a/quickstep/res/values-sq/strings.xml b/quickstep/res/values-sq/strings.xml
index 36769cb..8f39f65 100644
--- a/quickstep/res/values-sq/strings.xml
+++ b/quickstep/res/values-sq/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Formë e lirë"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktopi"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Zhvendose tek ekrani i jashtëm"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktopi"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nuk ka asnjë artikull të fundit"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Cilësimet e përdorimit të aplikacionit"</string>
diff --git a/quickstep/res/values-sr/strings.xml b/quickstep/res/values-sr/strings.xml
index 0d89f74..e810d5a 100644
--- a/quickstep/res/values-sr/strings.xml
+++ b/quickstep/res/values-sr/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Слободни облик"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Рачунар"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Преместите на спољни екран"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Затвори"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Рачунари"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Нема недавних ставки"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Подешавања коришћења апликације"</string>
diff --git a/quickstep/res/values-sv/strings.xml b/quickstep/res/values-sv/strings.xml
index 9941818..c33de8a 100644
--- a/quickstep/res/values-sv/strings.xml
+++ b/quickstep/res/values-sv/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Fritt format"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Skrivbordsläge"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Flytta till extern skärm"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Skrivbordsläge"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Listan är tom"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Inställningar för appanvändning"</string>
diff --git a/quickstep/res/values-sw/strings.xml b/quickstep/res/values-sw/strings.xml
index c7e5c30..543663f 100644
--- a/quickstep/res/values-sw/strings.xml
+++ b/quickstep/res/values-sw/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Muundo huru"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Kompyuta ya mezani"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Hamishia programu kwenye skrini ya nje"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Kompyuta ya Mezani"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Hakuna vipengee vya hivi karibuni"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Mipangilio ya matumizi ya programu"</string>
@@ -46,9 +48,9 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Mapendekezo ya programu yamewashwa"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Umezima mapendekezo ya programu"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Programu iliyotabiriwa: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <string name="gesture_tutorial_title" msgid="2750751261768388354">"Mafunzo ya Usogezaji kwa Kutumia Ishara"</string>
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Mafunzo ya Usogezaji kwa Kutumia Miguso"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Zungusha kifaa chako"</string>
- <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Tafadhali zungusha kifaa chako ili ukamilishe mafunzo ya usogezaji kwa kutumia ishara"</string>
+ <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Tafadhali zungusha kifaa chako ili ukamilishe mafunzo ya usogezaji kwa kutumia miguso"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Hakikisha unatelezesha kidole kutoka ukingo wa kulia au kushoto kabisa"</string>
<string name="back_gesture_feedback_cancelled" msgid="762621530959111290">"Hakikisha unatelezesha kidole kutoka ukingo wa kulia au kushoto hadi katikati ya skrini na uachilie"</string>
<string name="back_gesture_feedback_complete_with_overview_follow_up" msgid="9176400654037014471">"Umejifunza jinsi ya kutelezesha kidole kuanzia kulia ili kurudi nyuma. Sasa jifunze jinsi ya kubadilisha programu."</string>
diff --git a/quickstep/res/values-ta/strings.xml b/quickstep/res/values-ta/strings.xml
index 042679c..1e6cb9b 100644
--- a/quickstep/res/values-ta/strings.xml
+++ b/quickstep/res/values-ta/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"குறிப்பிட்ட வடிவமில்லாத பயன்முறை"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"டெஸ்க்டாப்"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"வெளிப்புற டிஸ்ப்ளேவிற்கு நகர்த்துதல்"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"டெஸ்க்டாப்"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"சமீபத்தியவை எதுவுமில்லை"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ஆப்ஸ் உபயோக அமைப்புகள்"</string>
diff --git a/quickstep/res/values-te/strings.xml b/quickstep/res/values-te/strings.xml
index 081708c..74e59ba 100644
--- a/quickstep/res/values-te/strings.xml
+++ b/quickstep/res/values-te/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"సంప్రదాయేతర"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"డెస్క్టాప్"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"ఎక్స్టర్నల్ డిస్ప్లేకు తరలించండి"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"డెస్క్టాప్"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"ఇటీవలి ఐటెమ్లు ఏవీ లేవు"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"యాప్ వినియోగ సెట్టింగ్లు"</string>
diff --git a/quickstep/res/values-th/strings.xml b/quickstep/res/values-th/strings.xml
index 61dfaa6..03852ff 100644
--- a/quickstep/res/values-th/strings.xml
+++ b/quickstep/res/values-th/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"รูปแบบอิสระ"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"เดสก์ท็อป"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"ย้ายไปยังจอแสดงผลภายนอก"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"ปิด"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"เดสก์ท็อป"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"ไม่มีรายการล่าสุด"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"การตั้งค่าการใช้แอป"</string>
diff --git a/quickstep/res/values-tl/strings.xml b/quickstep/res/values-tl/strings.xml
index 8374628..1b3696e 100644
--- a/quickstep/res/values-tl/strings.xml
+++ b/quickstep/res/values-tl/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Ilipat sa external na display"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Isara"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Walang kamakailang item"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Mga setting ng paggamit ng app"</string>
diff --git a/quickstep/res/values-tr/strings.xml b/quickstep/res/values-tr/strings.xml
index 9a1bf54..ac06369 100644
--- a/quickstep/res/values-tr/strings.xml
+++ b/quickstep/res/values-tr/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Serbest çalışma"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Masaüstü"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Harici ekrana taşı"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Masaüstü"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Yeni öğe yok"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Uygulama kullanım ayarları"</string>
diff --git a/quickstep/res/values-uk/strings.xml b/quickstep/res/values-uk/strings.xml
index 1036700..2d27bb9 100644
--- a/quickstep/res/values-uk/strings.xml
+++ b/quickstep/res/values-uk/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Довільна форма"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Робочий стіл"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Перемістити на зовнішній екран"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Комп’ютер"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Немає нещодавніх додатків"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Налаштування використання додатка"</string>
diff --git a/quickstep/res/values-ur/strings.xml b/quickstep/res/values-ur/strings.xml
index fde634f..0a3e122 100644
--- a/quickstep/res/values-ur/strings.xml
+++ b/quickstep/res/values-ur/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"فری فارم"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ڈیسک ٹاپ"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"بیرونی ڈسپلے پر متقل کریں"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"بند کریں"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"ڈیسک ٹاپ"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"کوئی حالیہ آئٹم نہیں"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ایپ کے استعمال کی ترتیبات"</string>
diff --git a/quickstep/res/values-uz/strings.xml b/quickstep/res/values-uz/strings.xml
index ab514b3..1cd2e87 100644
--- a/quickstep/res/values-uz/strings.xml
+++ b/quickstep/res/values-uz/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Erkin shakl"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Tashqi displeyga olish"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Yopish"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Yaqinda ishlatilgan ilovalar yo‘q"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Ilovadan foydalanish sozlamalari"</string>
diff --git a/quickstep/res/values-vi/strings.xml b/quickstep/res/values-vi/strings.xml
index f35138b..e49f7e3 100644
--- a/quickstep/res/values-vi/strings.xml
+++ b/quickstep/res/values-vi/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Dạng tự do"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Máy tính"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Chuyển sang màn hình ngoài"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Máy tính"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Không có mục gần đây nào"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Cài đặt mức sử dụng ứng dụng"</string>
diff --git a/quickstep/res/values-zh-rCN/strings.xml b/quickstep/res/values-zh-rCN/strings.xml
index 9665800..c1cf2fe 100644
--- a/quickstep/res/values-zh-rCN/strings.xml
+++ b/quickstep/res/values-zh-rCN/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"自由窗口"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"桌面"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"移至外接显示屏"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"桌面设备"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"近期没有任何内容"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"应用使用设置"</string>
diff --git a/quickstep/res/values-zh-rHK/strings.xml b/quickstep/res/values-zh-rHK/strings.xml
index d65aba1..393565a 100644
--- a/quickstep/res/values-zh-rHK/strings.xml
+++ b/quickstep/res/values-zh-rHK/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"自由形式"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"桌面"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"移至外部顯示屏"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"桌面"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"最近沒有任何項目"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"應用程式使用情況設定"</string>
diff --git a/quickstep/res/values-zh-rTW/strings.xml b/quickstep/res/values-zh-rTW/strings.xml
index 0b94911..fbd465d 100644
--- a/quickstep/res/values-zh-rTW/strings.xml
+++ b/quickstep/res/values-zh-rTW/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"自由形式"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"電腦模式"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"移至外接螢幕"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"電腦模式"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"最近沒有任何項目"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"應用程式使用情況設定"</string>
diff --git a/quickstep/res/values-zu/strings.xml b/quickstep/res/values-zu/strings.xml
index 8ad0b94..39b4873 100644
--- a/quickstep/res/values-zu/strings.xml
+++ b/quickstep/res/values-zu/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"I-Freeform"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Ideskithophu"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Hambisa esibonisini sangaphandle"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Ideskithophu"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Azikho izinto zakamuva"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Izilungiselelo zokusetshenziswa kohlelo lokusebenza"</string>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index d2a7029..8e70a2b 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -30,6 +30,8 @@
<string name="recent_task_option_desktop">Desktop</string>
<!-- Title and content description for an option to move app to external display. -->
<string name="recent_task_option_external_display">Move to external display</string>
+ <!-- Title and content description for an option to close the app [CHAR LIMIT=30] -->
+ <string name="recent_task_option_close">Close</string>
<!-- Title and content description for Desktop tile in Recents screen that contains apps opened inside desktop windowing mode [CHAR LIMIT=NONE] -->
<string name="recent_task_desktop">Desktop</string>
diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
index dc0f899..1cf7dda 100644
--- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
+++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
@@ -23,6 +23,8 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static java.lang.Math.max;
+import static java.lang.Math.min;
import static java.util.Collections.emptyList;
import android.appwidget.AppWidgetManager;
@@ -53,6 +55,7 @@
import com.android.launcher3.widget.WidgetCell;
import com.android.launcher3.widget.model.WidgetsListBaseEntriesBuilder;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+import com.android.launcher3.widget.picker.WidgetCategoryFilter;
import com.android.launcher3.widget.picker.WidgetsFullSheet;
import com.android.launcher3.widget.picker.model.WidgetPickerDataProvider;
import com.android.systemui.animation.back.FlingOnBackAnimationCallback;
@@ -81,6 +84,10 @@
// the intent, then widgets will not be filtered for size.
private static final String EXTRA_DESIRED_WIDGET_WIDTH = "desired_widget_width";
private static final String EXTRA_DESIRED_WIDGET_HEIGHT = "desired_widget_height";
+ // Unlike the AppWidgetManager.EXTRA_CATEGORY_FILTER, this filter removes certain categories.
+ // Filter is ignore if it is not a negative value.
+ // Example usage: WIDGET_CATEGORY_HOME_SCREEN.inv() and WIDGET_CATEGORY_NOT_KEYGUARD.inv()
+ private static final String EXTRA_CATEGORY_EXCLUSION_FILTER = "category_exclusion_filter";
/**
* Widgets currently added by the user in the UI surface.
* <p>This allows widget picker to exclude existing widgets from suggestions.</p>
@@ -120,7 +127,8 @@
private int mDesiredWidgetWidth;
private int mDesiredWidgetHeight;
- private int mWidgetCategoryFilter;
+ private WidgetCategoryFilter mWidgetCategoryInclusionFilter;
+ private WidgetCategoryFilter mWidgetCategoryExclusionFilter;
@Nullable
private String mUiSurface;
// Widgets existing on the host surface.
@@ -194,8 +202,19 @@
getIntent().getIntExtra(EXTRA_DESIRED_WIDGET_HEIGHT, 0);
// Defaults to '0' to indicate that there isn't a category filter.
- mWidgetCategoryFilter =
- getIntent().getIntExtra(AppWidgetManager.EXTRA_CATEGORY_FILTER, 0);
+ // Negative value indicates it's an exclusion filter (e.g. NOT_KEYGUARD_CATEGORY.inv())
+ // Positive value indicates it's inclusion filter (e.g. HOME_SCREEN or KEYGUARD)
+ // Note: A filter can either be inclusion or exclusion filter; not both.
+ int inclusionFilter = getIntent().getIntExtra(AppWidgetManager.EXTRA_CATEGORY_FILTER, 0);
+ if (inclusionFilter < 0) {
+ Log.w(TAG, "Invalid EXTRA_CATEGORY_FILTER: " + inclusionFilter);
+ }
+ mWidgetCategoryInclusionFilter = new WidgetCategoryFilter(max(0, inclusionFilter));
+ int exclusionFilter = getIntent().getIntExtra(EXTRA_CATEGORY_EXCLUSION_FILTER, 0);
+ if (exclusionFilter > 0) {
+ Log.w(TAG, "Invalid EXTRA_CATEGORY_EXCLUSION_FILTER: " + exclusionFilter);
+ }
+ mWidgetCategoryExclusionFilter = new WidgetCategoryFilter(min(0 , exclusionFilter));
String uiSurfaceParam = getIntent().getStringExtra(EXTRA_UI_SURFACE);
if (uiSurfaceParam != null && UI_SURFACE_PATTERN.matcher(uiSurfaceParam).matches()) {
@@ -436,11 +455,13 @@
widget.user.getIdentifier());
}
- if (mWidgetCategoryFilter > 0 && (info.widgetCategory & mWidgetCategoryFilter) == 0) {
+ if (!mWidgetCategoryInclusionFilter.matches(info.widgetCategory)
+ || !mWidgetCategoryExclusionFilter.matches(info.widgetCategory)) {
return rejectWidget(
widget,
- "doesn't match category filter [filter=%d, widget=%d]",
- mWidgetCategoryFilter,
+ "doesn't match category filter [inclusion=%d, exclusion=%d, widget=%d]",
+ mWidgetCategoryInclusionFilter.getCategoryMask(),
+ mWidgetCategoryExclusionFilter.getCategoryMask(),
info.widgetCategory);
}
@@ -463,7 +484,7 @@
mDesiredWidgetWidth);
}
- final int minWidth = Math.min(info.minResizeWidth, info.minWidth);
+ final int minWidth = min(info.minResizeWidth, info.minWidth);
if (minWidth > mDesiredWidgetWidth) {
return rejectWidget(
widget,
@@ -487,7 +508,7 @@
mDesiredWidgetHeight);
}
- final int minHeight = Math.min(info.minResizeHeight, info.minHeight);
+ final int minHeight = min(info.minResizeHeight, info.minHeight);
if (minHeight > mDesiredWidgetHeight) {
return rejectWidget(
widget,
diff --git a/quickstep/src/com/android/launcher3/model/data/TaskViewItemInfo.kt b/quickstep/src/com/android/launcher3/model/data/TaskViewItemInfo.kt
index ee28d7a..c201ab1 100644
--- a/quickstep/src/com/android/launcher3/model/data/TaskViewItemInfo.kt
+++ b/quickstep/src/com/android/launcher3/model/data/TaskViewItemInfo.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.content.Intent
+import android.os.Process
import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.Companion.PRIVATE
import com.android.launcher3.Flags.privateSpaceRestrictAccessibilityDrag
@@ -26,34 +27,40 @@
import com.android.launcher3.pm.UserCache
import com.android.quickstep.TaskUtils
import com.android.quickstep.views.TaskContainer
+import com.android.quickstep.views.TaskView
-class TaskViewItemInfo(taskContainer: TaskContainer) : WorkspaceItemInfo() {
+class TaskViewItemInfo(taskView: TaskView, taskContainer: TaskContainer?) : WorkspaceItemInfo() {
@VisibleForTesting(otherwise = PRIVATE) val taskViewAtom: LauncherAtom.TaskView
init {
itemType = LauncherSettings.Favorites.ITEM_TYPE_TASK
container = LauncherSettings.Favorites.CONTAINER_TASKSWITCHER
- val componentKey = TaskUtils.getLaunchComponentKeyForTask(taskContainer.task.key)
- user = componentKey.user
- intent = Intent().setComponent(componentKey.componentName)
- title = taskContainer.task.title
- if (privateSpaceRestrictAccessibilityDrag()) {
- if (
- UserCache.getInstance(taskContainer.taskView.context)
- .getUserInfo(componentKey.user)
- .isPrivate
- ) {
- runtimeStatusFlags = runtimeStatusFlags or ItemInfoWithIcon.FLAG_NOT_PINNABLE
+ val componentName: String
+ if (taskContainer != null) {
+ val componentKey = TaskUtils.getLaunchComponentKeyForTask(taskContainer.task.key)
+ user = componentKey.user
+ intent = Intent().setComponent(componentKey.componentName)
+ title = taskContainer.task.title
+ if (privateSpaceRestrictAccessibilityDrag()) {
+ if (
+ UserCache.getInstance(taskView.context).getUserInfo(componentKey.user).isPrivate
+ ) {
+ runtimeStatusFlags = runtimeStatusFlags or ItemInfoWithIcon.FLAG_NOT_PINNABLE
+ }
}
+ componentName = componentKey.componentName.flattenToShortString()
+ } else {
+ user = Process.myUserHandle()
+ intent = Intent()
+ componentName = ""
}
taskViewAtom =
createTaskViewAtom(
- type = taskContainer.taskView.type.ordinal,
- index =
- taskContainer.taskView.recentsView?.indexOfChild(taskContainer.taskView) ?: -1,
- componentName = componentKey.componentName.flattenToShortString(),
- cardinality = taskContainer.taskView.taskContainers.size,
+ type = taskView.type.ordinal,
+ index = taskView.recentsView?.indexOfChild(taskView) ?: -1,
+ componentName,
+ cardinality = taskView.taskContainers.size,
)
}
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.kt b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.kt
index 0703a61..3c4bc91 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.kt
+++ b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.kt
@@ -33,6 +33,7 @@
import com.android.quickstep.GestureState.GestureEndTarget
import com.android.quickstep.SystemUiProxy
import com.android.quickstep.fallback.RecentsState
+import com.android.wm.shell.desktopmode.DisplayDeskState
import com.android.wm.shell.desktopmode.IDesktopTaskListener.Stub
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import java.io.PrintWriter
@@ -365,6 +366,11 @@
) : Stub() {
private val controller = WeakReference(controller)
+ // TODO: b/392986431 - Implement the new desks APIs.
+ override fun onListenerConnected(
+ displayDeskStates: Array<DisplayDeskState>,
+ ) {}
+
override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {
if (displayId != this.displayId) return
Executors.MAIN_EXECUTOR.execute {
@@ -398,6 +404,15 @@
override fun onEnterDesktopModeTransitionStarted(transitionDuration: Int) {}
override fun onExitDesktopModeTransitionStarted(transitionDuration: Int) {}
+
+ // TODO: b/392986431 - Implement all the below new desks APIs.
+ override fun onCanCreateDesksChanged(displayId: Int, canCreateDesks: Boolean) {}
+
+ override fun onDeskAdded(displayId: Int, deskId: Int) {}
+
+ override fun onDeskRemoved(displayId: Int, deskId: Int) {}
+
+ override fun onActiveDeskChanged(displayId: Int, newActiveDesk: Int, oldActiveDesk: Int) {}
}
/** A listener for Taskbar in Desktop Mode. */
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
index 3736e6d..23065b5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
@@ -233,7 +233,8 @@
}
private boolean shouldExcludeTask(GroupTask task, Set<Integer> taskIdsToExclude) {
- return Flags.taskbarOverflow() && taskIdsToExclude.contains(task.task1.key.id);
+ return Flags.taskbarOverflow() && task.getTasks().stream().anyMatch(
+ t -> taskIdsToExclude.contains(t.key.id));
}
private void processLoadedTasks(List<GroupTask> tasks, Set<Integer> taskIdsToExclude) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
index 306443e..4581119 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
@@ -51,6 +51,9 @@
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SingleTask;
+import com.android.quickstep.util.SplitTask;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import java.util.HashMap;
@@ -255,17 +258,24 @@
layoutInflater,
previousTaskView);
- final boolean firstTaskIsLeftTopTask =
- groupTask.mSplitBounds == null
- || groupTask.mSplitBounds.leftTopTaskId == groupTask.task1.key.id
- || groupTask.task2 == null;
+ Task task1;
+ Task task2;
+ if (groupTask instanceof SplitTask splitTask) {
+ task1 = splitTask.getTopLeftTask();
+ task2 = splitTask.getBottomRightTask();
+ } else if (groupTask instanceof SingleTask singleTask) {
+ task1 = singleTask.getTask();
+ task2 = null;
+ } else {
+ continue;
+ }
currentTaskView.setThumbnailsForSplitTasks(
- firstTaskIsLeftTopTask ? groupTask.task1 : groupTask.task2,
- firstTaskIsLeftTopTask ? groupTask.task2 : groupTask.task1,
+ task1,
+ task2,
updateTasks ? mViewCallbacks::updateThumbnailInBackground : null,
updateTasks ? mViewCallbacks::updateIconInBackground : null,
- groupTask.mSplitBounds);
+ groupTask instanceof SplitTask splitTask ? splitTask.getSplitBounds() : null);
previousTaskView = currentTaskView;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
index 8cb43d2..5af7ff8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
@@ -44,6 +44,7 @@
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SingleTask;
import com.android.quickstep.util.SlideInRemoteTransition;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -281,9 +282,10 @@
return -1;
}
RemoteTransition remoteTransition = slideInTransition;
- if (mOnDesktop
- && mControllers.taskbarActivityContext.canUnminimizeDesktopTask(task.task1.key.id)
- ) {
+ boolean canUnminimizeDesktopTask = task instanceof SingleTask singleTask
+ && mControllers.taskbarActivityContext.canUnminimizeDesktopTask(
+ singleTask.getTask().key.id);
+ if (mOnDesktop && canUnminimizeDesktopTask) {
// This app is being unminimized - use our own transition runner.
remoteTransition = new RemoteTransition(
new DesktopAppLaunchTransition(
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 5a8fba6..4143157 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -49,7 +49,7 @@
import com.android.quickstep.HomeVisibilityState;
import com.android.quickstep.RecentsAnimationCallbacks;
import com.android.quickstep.SystemUiProxy;
-import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SplitTask;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
@@ -480,8 +480,8 @@
@Override
public void launchSplitTasks(
- @NonNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition) {
- mLauncher.launchSplitTasks(groupTask, remoteTransition);
+ @NonNull SplitTask splitTask, @Nullable RemoteTransition remoteTransition) {
+ mLauncher.launchSplitTasks(splitTask, remoteTransition);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 8a06b11..9d1fc15 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -29,6 +29,7 @@
import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
import static com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_OVERLAY_PROXY;
import static com.android.launcher3.Flags.enableCursorHoverStates;
+import static com.android.launcher3.Flags.removeExcludeFromScreenMagnificationFlagUsage;
import static com.android.launcher3.Utilities.calculateTextHeight;
import static com.android.launcher3.Utilities.isRunningInTestHarness;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
@@ -156,6 +157,8 @@
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.DesktopTask;
import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SingleTask;
+import com.android.quickstep.util.SplitTask;
import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -1294,11 +1297,13 @@
mControllers.keyboardQuickSwitchController.closeQuickSwitchView(false);
- if (tag instanceof GroupTask groupTask) {
+ // TODO: b/316004172, b/343289567: Handle `DesktopTask` and `SplitTask`.
+ if (tag instanceof SingleTask singleTask) {
RemoteTransition remoteTransition =
- (areDesktopTasksVisible() && canUnminimizeDesktopTask(groupTask.task1.key.id))
+ (areDesktopTasksVisible() && canUnminimizeDesktopTask(
+ singleTask.getTask().key.id))
? createDesktopAppLaunchRemoteTransition(AppLaunchType.UNMINIMIZE,
- Cuj.CUJ_DESKTOP_MODE_APP_LAUNCH_FROM_ICON)
+ Cuj.CUJ_DESKTOP_MODE_APP_LAUNCH_FROM_ICON)
: null;
if (areDesktopTasksVisible() && mControllers.uiController.isInOverviewUi()) {
RunnableList runnableList = recents.launchRunningDesktopTaskView();
@@ -1306,12 +1311,12 @@
// launch.
if (runnableList != null) {
runnableList.add(() -> UI_HELPER_EXECUTOR.execute(
- () -> handleGroupTaskLaunch(groupTask, remoteTransition,
+ () -> handleGroupTaskLaunch(singleTask, remoteTransition,
areDesktopTasksVisible(),
DesktopTaskToFrontReason.TASKBAR_TAP)));
}
} else {
- handleGroupTaskLaunch(groupTask, remoteTransition, areDesktopTasksVisible(),
+ handleGroupTaskLaunch(singleTask, remoteTransition, areDesktopTasksVisible(),
DesktopTaskToFrontReason.TASKBAR_TAP);
}
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
@@ -1481,13 +1486,13 @@
remoteTransition));
return;
}
- if (onDesktop) {
- boolean useRemoteTransition = canUnminimizeDesktopTask(task.task1.key.id);
+ if (onDesktop && task instanceof SingleTask singleTask) {
+ boolean useRemoteTransition = canUnminimizeDesktopTask(singleTask.getTask().key.id);
UI_HELPER_EXECUTOR.execute(() -> {
if (onStartCallback != null) {
onStartCallback.run();
}
- SystemUiProxy.INSTANCE.get(this).showDesktopApp(task.task1.key.id,
+ SystemUiProxy.INSTANCE.get(this).showDesktopApp(singleTask.getTask().key.id,
useRemoteTransition ? remoteTransition : null, toFrontReason);
if (onFinishCallback != null) {
onFinishCallback.run();
@@ -1495,18 +1500,19 @@
});
return;
}
- if (task.task2 == null) {
+ if (task instanceof SingleTask singleTask) {
UI_HELPER_EXECUTOR.execute(() -> {
ActivityOptions activityOptions =
makeDefaultActivityOptions(SPLASH_SCREEN_STYLE_UNDEFINED).options;
activityOptions.setRemoteTransition(remoteTransition);
ActivityManagerWrapper.getInstance().startActivityFromRecents(
- task.task1.key, activityOptions);
+ singleTask.getTask().key, activityOptions);
});
return;
}
- mControllers.uiController.launchSplitTasks(task, remoteTransition);
+ assert task instanceof SplitTask;
+ mControllers.uiController.launchSplitTasks((SplitTask) task, remoteTransition);
}
/** Returns whether the given task is minimized and can be unminimized. */
@@ -1901,6 +1907,10 @@
return;
}
+ if (removeExcludeFromScreenMagnificationFlagUsage()) {
+ return;
+ }
+
mIsExcludeFromMagnificationRegion = exclude;
if (exclude) {
mWindowLayoutParams.privateFlags |=
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index a9e8d6d..3a83db2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -80,9 +80,9 @@
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.views.BubbleTextHolder;
-import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.LogUtils;
import com.android.quickstep.util.MultiValueUpdateListener;
+import com.android.quickstep.util.SingleTask;
import com.android.systemui.shared.recents.model.Task;
import com.android.wm.shell.shared.draganddrop.DragAndDropConstants;
@@ -433,8 +433,8 @@
null, item.user));
}
intent.putExtra(Intent.EXTRA_USER, item.user);
- } else if (tag instanceof GroupTask groupTask && !groupTask.hasMultipleTasks()) {
- Task task = groupTask.task1;
+ } else if (tag instanceof SingleTask singleTask) {
+ Task task = singleTask.getTask();
clipDescription = new ClipDescription(task.titleDescription,
new String[] {
ClipDescription.MIMETYPE_APPLICATION_TASK
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 17fb959..ea2dec1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -115,9 +115,7 @@
private static final Uri NAV_BAR_KIDS_MODE = Settings.Secure.getUriFor(
Settings.Secure.NAV_BAR_KIDS_MODE);
- private final Context mWindowContext;
- private final @Nullable Context mNavigationBarPanelContext;
- private WindowManager mWindowManager;
+ private final Context mParentContext;
private final TaskbarNavButtonController mDefaultNavButtonController;
private final ComponentCallbacks mDefaultComponentCallbacks;
@@ -132,6 +130,8 @@
new NonDestroyableScopedUnfoldTransitionProgressProvider();
/** DisplayId - {@link TaskbarActivityContext} map for Connected Display. */
private final SparseArray<TaskbarActivityContext> mTaskbars = new SparseArray<>();
+ /** DisplayId - {@link Context} map for Connected Display. */
+ private final SparseArray<Context> mWindowContexts = new SparseArray<>();
/** DisplayId - {@link FrameLayout} map for Connected Display. */
private final SparseArray<FrameLayout> mRootLayouts = new SparseArray<>();
/** DisplayId - {@link Boolean} map indicating if RootLayout was added to window. */
@@ -243,36 +243,30 @@
Context context,
AllAppsActionManager allAppsActionManager,
TaskbarNavButtonCallbacks navCallbacks) {
- Display display =
- context.getSystemService(DisplayManager.class).getDisplay(context.getDisplayId());
- mWindowContext = context.createWindowContext(display,
- ENABLE_TASKBAR_NAVBAR_UNIFICATION ? TYPE_NAVIGATION_BAR : TYPE_NAVIGATION_BAR_PANEL,
- null);
+ mParentContext = context;
+ createWindowContext(context.getDisplayId());
mAllAppsActionManager = allAppsActionManager;
- mNavigationBarPanelContext = ENABLE_TASKBAR_NAVBAR_UNIFICATION
- ? context.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null)
- : null;
if (enableTaskbarNoRecreate()) {
- mWindowManager = mWindowContext.getSystemService(WindowManager.class);
createTaskbarRootLayout(getDefaultDisplayId());
}
mDefaultNavButtonController = createDefaultNavButtonController(context, navCallbacks);
mDefaultComponentCallbacks = createDefaultComponentCallbacks();
- SettingsCache.INSTANCE.get(mWindowContext)
+ SettingsCache.INSTANCE.get(getPrimaryWindowContext())
.register(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
- SettingsCache.INSTANCE.get(mWindowContext)
+ SettingsCache.INSTANCE.get(getPrimaryWindowContext())
.register(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
Log.d(TASKBAR_NOT_DESTROYED_TAG, "registering component callbacks from constructor.");
- mWindowContext.registerComponentCallbacks(mDefaultComponentCallbacks);
- mShutdownReceiver.register(mWindowContext, Intent.ACTION_SHUTDOWN);
+ getPrimaryWindowContext().registerComponentCallbacks(mDefaultComponentCallbacks);
+ mShutdownReceiver.register(getPrimaryWindowContext(), Intent.ACTION_SHUTDOWN);
UI_HELPER_EXECUTOR.execute(() -> {
mSharedState.taskbarSystemActionPendingIntent = PendingIntent.getBroadcast(
- mWindowContext,
+ getPrimaryWindowContext(),
SYSTEM_ACTION_ID_TASKBAR,
- new Intent(ACTION_SHOW_TASKBAR).setPackage(mWindowContext.getPackageName()),
+ new Intent(ACTION_SHOW_TASKBAR).setPackage(
+ getPrimaryWindowContext().getPackageName()),
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
mTaskbarBroadcastReceiver.register(
- mWindowContext, RECEIVER_NOT_EXPORTED, ACTION_SHOW_TASKBAR);
+ getPrimaryWindowContext(), RECEIVER_NOT_EXPORTED, ACTION_SHOW_TASKBAR);
});
debugWhyTaskbarNotDestroyed("TaskbarManager created");
@@ -285,14 +279,15 @@
return new TaskbarNavButtonController(
context,
navCallbacks,
- SystemUiProxy.INSTANCE.get(mWindowContext),
+ SystemUiProxy.INSTANCE.get(getPrimaryWindowContext()),
new Handler(),
- new ContextualSearchInvoker(mWindowContext));
+ new ContextualSearchInvoker(getPrimaryWindowContext()));
}
private ComponentCallbacks createDefaultComponentCallbacks() {
return new ComponentCallbacks() {
- private Configuration mOldConfig = mWindowContext.getResources().getConfiguration();
+ private Configuration mOldConfig =
+ getPrimaryWindowContext().getResources().getConfiguration();
@Override
public void onConfigurationChanged(Configuration newConfig) {
@@ -302,7 +297,8 @@
"TaskbarManager#mComponentCallbacks.onConfigurationChanged: " + newConfig);
// TODO: adapt this logic to be specific to different displays.
DeviceProfile dp = mUserUnlocked
- ? LauncherAppState.getIDP(mWindowContext).getDeviceProfile(mWindowContext)
+ ? LauncherAppState.getIDP(getPrimaryWindowContext()).getDeviceProfile(
+ getPrimaryWindowContext())
: null;
int configDiff = mOldConfig.diff(newConfig) & ~SKIP_RECREATE_CONFIG_CHANGES;
@@ -354,6 +350,7 @@
int displayId = mTaskbars.keyAt(i);
destroyTaskbarForDisplay(displayId);
removeTaskbarRootViewFromWindow(displayId);
+ removeWindowContextFromMap(displayId);
}
}
@@ -372,7 +369,8 @@
}
// make this display-specific
DeviceProfile dp = mUserUnlocked ?
- LauncherAppState.getIDP(mWindowContext).getDeviceProfile(mWindowContext) : null;
+ LauncherAppState.getIDP(getWindowContext(displayId)).getDeviceProfile(
+ getWindowContext(displayId)) : null;
if (dp == null || !isTaskbarEnabled(dp)) {
removeTaskbarRootViewFromWindow(displayId);
}
@@ -419,7 +417,8 @@
*/
public void onUserUnlocked() {
mUserUnlocked = true;
- DisplayController.INSTANCE.get(mWindowContext).addChangeListener(mRecreationListener);
+ DisplayController.INSTANCE.get(getPrimaryWindowContext()).addChangeListener(
+ mRecreationListener);
recreateTaskbar();
addTaskbarRootViewToWindow(getDefaultDisplayId());
}
@@ -482,7 +481,8 @@
return ql.getUnfoldTransitionProgressProvider();
}
} else {
- return SystemUiProxy.INSTANCE.get(mWindowContext).getUnfoldTransitionProvider();
+ return SystemUiProxy.INSTANCE.get(
+ getPrimaryWindowContext()).getUnfoldTransitionProvider();
}
return null;
}
@@ -528,7 +528,8 @@
Log.d(ILLEGAL_ARGUMENT_WM_ADD_VIEW, "recreateTaskbarForDisplay: " + displayId);
// TODO: make this code display specific
DeviceProfile dp = mUserUnlocked ?
- LauncherAppState.getIDP(mWindowContext).getDeviceProfile(mWindowContext) : null;
+ LauncherAppState.getIDP(getWindowContext(displayId)).getDeviceProfile(
+ getWindowContext(displayId)) : null;
// All Apps action is unrelated to navbar unification, so we only need to check DP.
final boolean isLargeScreenTaskbar = dp != null && dp.isTaskbarPresent;
@@ -542,7 +543,7 @@
+ " FLAG_HIDE_NAVBAR_WINDOW=" + ENABLE_TASKBAR_NAVBAR_UNIFICATION
+ " dp.isTaskbarPresent=" + (dp == null ? "null" : dp.isTaskbarPresent));
if (!isTaskbarEnabled || !isLargeScreenTaskbar) {
- SystemUiProxy.INSTANCE.get(mWindowContext)
+ SystemUiProxy.INSTANCE.get(getPrimaryWindowContext())
.notifyTaskbarStatus(/* visible */ false, /* stashed */ false);
if (!isTaskbarEnabled) {
return;
@@ -754,23 +755,24 @@
mRecentsViewContainer = null;
debugWhyTaskbarNotDestroyed("TaskbarManager#destroy()");
removeActivityCallbacksAndListeners();
- mTaskbarBroadcastReceiver.unregisterReceiverSafely(mWindowContext);
- destroyAllTaskbars();
+ mTaskbarBroadcastReceiver.unregisterReceiverSafely(getPrimaryWindowContext());
+
if (mUserUnlocked) {
- DisplayController.INSTANCE.get(mWindowContext).removeChangeListener(
+ DisplayController.INSTANCE.get(getPrimaryWindowContext()).removeChangeListener(
mRecreationListener);
}
- SettingsCache.INSTANCE.get(mWindowContext)
+ SettingsCache.INSTANCE.get(getPrimaryWindowContext())
.unregister(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
- SettingsCache.INSTANCE.get(mWindowContext)
+ SettingsCache.INSTANCE.get(getPrimaryWindowContext())
.unregister(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
Log.d(TASKBAR_NOT_DESTROYED_TAG, "unregistering component callbacks from destroy().");
- mWindowContext.unregisterComponentCallbacks(mDefaultComponentCallbacks);
- mShutdownReceiver.unregisterReceiverSafely(mWindowContext);
+ getPrimaryWindowContext().unregisterComponentCallbacks(mDefaultComponentCallbacks);
+ mShutdownReceiver.unregisterReceiverSafely(getPrimaryWindowContext());
+ destroyAllTaskbars();
}
public @Nullable TaskbarActivityContext getCurrentActivityContext() {
- return getTaskbarForDisplay(mWindowContext.getDisplayId());
+ return getTaskbarForDisplay(getDefaultDisplayId());
}
public void dumpLogs(String prefix, PrintWriter pw) {
@@ -786,7 +788,6 @@
taskbar.dumpLogs(prefix + "\t\t", pw);
}
}
-
}
private void addTaskbarRootViewToWindow(int displayId) {
@@ -800,8 +801,7 @@
if (!isTaskbarRootLayoutAddedForDisplay(displayId)) {
FrameLayout rootLayout = getTaskbarRootLayoutForDisplay(displayId);
if (rootLayout != null) {
- mWindowManager.addView(getTaskbarRootLayoutForDisplay(displayId),
- taskbar.getWindowLayoutParams());
+ getWindowManager(displayId).addView(rootLayout, taskbar.getWindowLayoutParams());
mAddedRootLayouts.put(displayId, true);
} else {
Log.d(ILLEGAL_ARGUMENT_WM_ADD_VIEW,
@@ -822,7 +822,7 @@
}
if (isTaskbarRootLayoutAddedForDisplay(displayId)) {
- mWindowManager.removeViewImmediate(rootLayout);
+ getWindowManager(displayId).removeViewImmediate(rootLayout);
mAddedRootLayouts.put(displayId, false);
removeTaskbarRootLayoutFromMap(displayId);
}
@@ -855,10 +855,16 @@
* Creates a {@link TaskbarActivityContext} for the given display and adds it to the map.
*/
private TaskbarActivityContext createTaskbarActivityContext(DeviceProfile dp, int displayId) {
- TaskbarActivityContext newTaskbar = new TaskbarActivityContext(mWindowContext,
- mNavigationBarPanelContext, dp, mDefaultNavButtonController,
+ Display display = mParentContext.getSystemService(DisplayManager.class).getDisplay(
+ displayId);
+ Context navigationBarPanelContext = ENABLE_TASKBAR_NAVBAR_UNIFICATION
+ ? mParentContext.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null)
+ : null;
+
+ TaskbarActivityContext newTaskbar = new TaskbarActivityContext(getWindowContext(displayId),
+ navigationBarPanelContext, dp, mDefaultNavButtonController,
mUnfoldProgressProvider, isDefaultDisplay(displayId),
- SystemUiProxy.INSTANCE.get(mWindowContext));
+ SystemUiProxy.INSTANCE.get(getPrimaryWindowContext()));
addTaskbarToMap(displayId, newTaskbar);
return newTaskbar;
@@ -892,7 +898,7 @@
*/
private void createTaskbarRootLayout(int displayId) {
Log.d(ILLEGAL_ARGUMENT_WM_ADD_VIEW, "createTaskbarRootLayout: " + displayId);
- FrameLayout newTaskbarRootLayout = new FrameLayout(mWindowContext) {
+ FrameLayout newTaskbarRootLayout = new FrameLayout(getWindowContext(displayId)) {
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// The motion events can be outside the view bounds of task bar, and hence
@@ -958,8 +964,75 @@
Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG, "mRootLayouts.size()=" + mRootLayouts.size());
}
+ /**
+ * Creates {@link Context} for the taskbar on the specified display and›› adds it to map.
+ * @param displayId The ID of the display for which to create the window context.
+ */
+ private void createWindowContext(int displayId) {
+ DisplayManager displayManager = mParentContext.getSystemService(DisplayManager.class);
+ if (displayManager == null) {
+ return;
+ }
+
+ Display display = displayManager.getDisplay(displayId);
+ if (display != null) {
+ int windowType = (ENABLE_TASKBAR_NAVBAR_UNIFICATION && isDefaultDisplay(displayId))
+ ? TYPE_NAVIGATION_BAR : TYPE_NAVIGATION_BAR_PANEL;
+ Context newContext = mParentContext.createWindowContext(display, windowType, null);
+ addWindowContextToMap(displayId, newContext);
+ }
+ }
+
+ /**
+ * Retrieves the window context of the taskbar for the specified display.
+ *
+ * @param displayId The ID of the display for which to retrieve the window context.
+ * @return The Window Context {@link Context} for a given display or {@code null}.
+ */
+ private Context getWindowContext(int displayId) {
+ return mWindowContexts.get(displayId);
+ }
+
+ @VisibleForTesting
+ public Context getPrimaryWindowContext() {
+ return getWindowContext(getDefaultDisplayId());
+ }
+
+ /**
+ * Retrieves the window manager {@link WindowManager} of the taskbar for the specified display.
+ *
+ * @param displayId The ID of the display for which to retrieve the window manager.
+ * @return The window manager {@link WindowManager} for a given display or {@code null}.
+ */
+ private WindowManager getWindowManager(int displayId) {
+ return getWindowContext(displayId).getSystemService(WindowManager.class);
+ }
+
+ /**
+ * Adds the window context {@link Context} to taskbar map, mapped to display ID.
+ *
+ * @param displayId The ID of the display to associate with the taskbar root layout.
+ * @param windowContext The window context {@link Context} to add to the map.
+ */
+ private void addWindowContextToMap(int displayId, @NonNull Context windowContext) {
+ if (!mWindowContexts.contains(displayId)) {
+ mWindowContexts.put(displayId, windowContext);
+ }
+ }
+
+ /**
+ * Removes the window context {@link Context} for given display ID from the taskbar map.
+ *
+ * @param displayId The ID of the display for which to remove the taskbar root layout.
+ */
+ private void removeWindowContextFromMap(int displayId) {
+ if (mWindowContexts.contains(displayId)) {
+ mWindowContexts.delete(displayId);
+ }
+ }
+
private int getDefaultDisplayId() {
- return mWindowContext.getDisplayId();
+ return mParentContext.getDisplayId();
}
/** Temp logs for b/254119092. */
@@ -974,9 +1047,14 @@
boolean activityTaskbarPresent = mActivity != null
&& mActivity.getDeviceProfile().isTaskbarPresent;
- // TODO: make this display specific
- boolean contextTaskbarPresent = mUserUnlocked && LauncherAppState.getIDP(mWindowContext)
- .getDeviceProfile(mWindowContext).isTaskbarPresent;
+ Context windowContext = getWindowContext(displayId);
+ if (windowContext == null) {
+ log.add("window context for displayId" + displayId);
+ return;
+ }
+
+ boolean contextTaskbarPresent = mUserUnlocked && LauncherAppState.getIDP(windowContext)
+ .getDeviceProfile(windowContext).isTaskbarPresent;
if (activityTaskbarPresent == contextTaskbarPresent) {
log.add("mActivity and mWindowContext agree taskbarIsPresent=" + contextTaskbarPresent);
Log.d(TASKBAR_NOT_DESTROYED_TAG, log.toString());
@@ -993,12 +1071,12 @@
log.add("\t\tmActivity.getDeviceProfile().isTaskbarPresent="
+ activityTaskbarPresent);
}
- log.add("\tmWindowContext logs:");
- log.add("\t\tmWindowContext=" + mWindowContext);
- log.add("\t\tmWindowContext.getResources().getConfiguration()="
- + mWindowContext.getResources().getConfiguration());
+ log.add("\tWindowContext logs:");
+ log.add("\t\tWindowContext=" + windowContext);
+ log.add("\t\tWindowContext.getResources().getConfiguration()="
+ + windowContext.getResources().getConfiguration());
if (mUserUnlocked) {
- log.add("\t\tLauncherAppState.getIDP().getDeviceProfile(mWindowContext)"
+ log.add("\t\tLauncherAppState.getIDP().getDeviceProfile(getPrimaryWindowContext())"
+ ".isTaskbarPresent=" + contextTaskbarPresent);
} else {
log.add("\t\tCouldn't get DeviceProfile because !mUserUnlocked");
@@ -1010,8 +1088,4 @@
private final DeviceProfile.OnDeviceProfileChangeListener mDebugActivityDeviceProfileChanged =
dp -> debugWhyTaskbarNotDestroyed("mActivity onDeviceProfileChanged");
- @VisibleForTesting
- public Context getWindowContext() {
- return mWindowContext;
- }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
index 6047999..417ef7e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
@@ -76,12 +76,14 @@
var shownTasks: List<GroupTask> = emptyList()
private set
+ val shownTaskIds: List<Int>
+ get() = shownTasks.flatMap { shownTask -> shownTask.tasks }.map { it.key.id }
+
/**
- * The task-state of an app, i.e. whether the app has a task and what state
- * that task is in.
+ * The task-state of an app, i.e. whether the app has a task and what state that task is in.
*
- * @property taskId The ID of the task if one exists (i.e. if the state is
- * RUNNING or MINIMIZED), null otherwise (NOT_RUNNING).
+ * @property taskId The ID of the task if one exists (i.e. if the state is RUNNING or
+ * MINIMIZED), null otherwise (NOT_RUNNING).
*/
data class TaskState(val runningAppState: RunningAppState, val taskId: Int? = null)
@@ -214,9 +216,9 @@
return shownHotseatItems.toTypedArray()
}
- private fun getOrderedAndWrappedDesktopTasks(): List<GroupTask> {
+ private fun getOrderedAndWrappedDesktopTasks(): List<SingleTask> {
val tasks = desktopTask?.tasks ?: emptyList()
- // Kind of hacky, we wrap each single task in the Desktop as a GroupTask.
+ // We wrap each task in the Desktop as a `SingleTask`.
val orderFromId = orderedRunningTaskIds.withIndex().associate { (index, id) -> id to index }
val sortedTasks = tasks.sortedWith(compareBy(nullsLast()) { orderFromId[it.key.id] })
return sortedTasks.map { SingleTask(it) }
@@ -286,7 +288,7 @@
}
private fun updateOrderedRunningTaskIds(): MutableList<Int> {
- val desktopTasksAsList = getOrderedAndWrappedDesktopTasks().flatMap { it.tasks }
+ val desktopTasksAsList = getOrderedAndWrappedDesktopTasks().map { it.task }
val desktopTaskIds = desktopTasksAsList.map { it.key.id }
var newOrder =
orderedRunningTaskIds
@@ -311,42 +313,43 @@
val newShownTasks =
if (Flags.enableMultiInstanceMenuTaskbar()) {
val deduplicatedDesktopTasks =
- desktopTasks.distinctBy { Pair(it.task1.key.packageName, it.task1.key.userId) }
+ desktopTasks.distinctBy { Pair(it.task.key.packageName, it.task.key.userId) }
shownTasks
.filter {
- !it.supportsMultipleTasks() &&
- it.task1.key.id in deduplicatedDesktopTasks.map { it.task1.key.id }
+ it is SingleTask &&
+ it.task.key.id in deduplicatedDesktopTasks.map { it.task.key.id }
}
.toMutableList()
.apply {
addAll(
deduplicatedDesktopTasks.filter { currentTask ->
- val currentTaskKey = currentTask.task1.key
- currentTaskKey.id !in shownTasks.map { it.task1.key.id } &&
+ val currentTaskKey = currentTask.task.key
+ currentTaskKey.id !in shownTaskIds &&
shownHotseatItems.none { hotseatItem ->
- hotseatItem.targetPackage == currentTaskKey.packageName &&
- hotseatItem.user.identifier == currentTaskKey.userId
+ currentTask.containsPackage(
+ hotseatItem.targetPackage,
+ hotseatItem.user.identifier,
+ )
}
}
)
}
} else {
- val desktopTaskIds = desktopTasks.map { it.task1.key.id }
+ val desktopTaskIds = desktopTasks.map { it.task.key.id }
val shownHotseatItemTaskIds =
shownHotseatItems.mapNotNull { it as? TaskItemInfo }.map { it.taskId }
shownTasks
- .filter { !it.supportsMultipleTasks() && it.task1.key.id in desktopTaskIds }
+ .filter { it is SingleTask && it.task.key.id in desktopTaskIds }
.toMutableList()
.apply {
addAll(
desktopTasks.filter { desktopTask ->
- desktopTask.task1.key.id !in
- shownTasks.map { shownTask -> shownTask.task1.key.id }
+ desktopTask.task.key.id !in shownTaskIds
}
)
- removeAll { it.task1.key.id in shownHotseatItemTaskIds }
+ removeAll { it is SingleTask && it.task.key.id in shownHotseatItemTaskIds }
}
}
@@ -371,21 +374,28 @@
groupTasks: List<GroupTask>,
shownHotseatItems: List<ItemInfo>,
): List<GroupTask> {
+ // TODO: b/393476333 - Check the behavior of the Taskbar recents section when empty desks
+ // become supported.
return if (Flags.enableMultiInstanceMenuTaskbar()) {
groupTasks.filter { groupTask ->
- val taskKey = groupTask.task1.key
// Keep tasks that are group tasks or unique package name/user combinations
- groupTask.hasMultipleTasks() ||
- shownHotseatItems.none {
- it.targetPackage == taskKey.packageName &&
- it.user.identifier == taskKey.userId
- }
+ when (groupTask) {
+ is SingleTask ->
+ shownHotseatItems.none {
+ groupTask.containsPackage(it.targetPackage, it.user.identifier)
+ }
+
+ else -> true
+ }
}
} else {
val hotseatPackages = shownHotseatItems.map { it.targetPackage }
groupTasks.filter { groupTask ->
- groupTask.hasMultipleTasks() ||
- !hotseatPackages.contains(groupTask.task1.key.packageName)
+ when (groupTask) {
+ is SingleTask -> hotseatPackages.none { groupTask.containsPackage(it) }
+
+ else -> true
+ }
}
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index f29f95d..e5d642d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -39,7 +39,7 @@
import com.android.launcher3.taskbar.bubbles.BubbleBarController;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.SplitConfigurationOptions;
-import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SplitTask;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskContainer;
import com.android.quickstep.views.TaskView;
@@ -332,7 +332,7 @@
* Launches the given task in split-screen.
*/
public void launchSplitTasks(
- @NonNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition) { }
+ @NonNull SplitTask splitTask, @Nullable RemoteTransition remoteTransition) { }
/**
* Returns the matching view (if any) in the taskbar.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 457ba3d..a59c9e3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -26,8 +26,6 @@
import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
-import static java.util.function.Predicate.not;
-
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
@@ -68,6 +66,7 @@
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SingleTask;
import com.android.quickstep.views.TaskViewType;
import com.android.systemui.shared.recents.model.Task;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
@@ -398,7 +397,7 @@
.filter(Objects::nonNull)
.toArray(ItemInfo[]::new);
// TODO(b/343289567 and b/316004172): support app pairs and desktop mode.
- recentTasks = recentTasks.stream().filter(not(GroupTask::supportsMultipleTasks)).toList();
+ recentTasks = recentTasks.stream().filter(it -> it instanceof SingleTask).toList();
if (taskbarRecentsLayoutTransition()) {
updateItemsWithLayoutTransition(hotseatItemInfos, recentTasks);
@@ -636,9 +635,10 @@
final Set<GroupTask> recentTasksSet = new ArraySet<>(recentTasks);
for (GroupTask task : recentTasks) {
if (mTaskbarOverflowView != null && overflownTasks != null
- && overflownTasks.size() < itemsToAddToOverflow) {
+ && overflownTasks.size() < itemsToAddToOverflow
+ && task instanceof SingleTask singleTask) {
// TODO(b/343289567 and b/316004172): support app pairs and desktop mode.
- overflownTasks.add(task.task1);
+ overflownTasks.add(singleTask.getTask());
if (overflownTasks.size() == itemsToAddToOverflow) {
mTaskbarOverflowView.setItems(overflownTasks);
}
@@ -648,7 +648,7 @@
// Replace any Recent views with the appropriate type if it's not already that type.
final int expectedLayoutResId;
boolean isCollection = false;
- if (task.supportsMultipleTasks()) {
+ if (!(task instanceof SingleTask)) {
if (task.taskViewType == TaskViewType.DESKTOP) {
// TODO(b/316004172): use Desktop tile layout.
expectedLayoutResId = -1;
@@ -712,18 +712,22 @@
&& tagClass.isInstance(getChildAt(mNextViewIndex).getTag());
}
- /** Binds the GroupTask to the BubbleTextView to be ready to present to the user. */
+ /** Binds the SingleTask to the BubbleTextView to be ready to present to the user. */
public void applyGroupTaskToBubbleTextView(BubbleTextView btv, GroupTask groupTask) {
- // TODO(b/343289567): support app pairs.
- Task task1 = groupTask.task1;
+ if (!(groupTask instanceof SingleTask singleTask)) {
+ // TODO(b/343289567 and b/316004172): support app pairs and desktop mode.
+ return;
+ }
+
+ Task task = singleTask.getTask();
// TODO(b/344038728): use FastBitmapDrawable instead of Drawable, to get disabled state
// while dragging.
- Drawable taskIcon = groupTask.task1.icon;
+ Drawable taskIcon = task.icon;
if (taskIcon != null) {
taskIcon = taskIcon.getConstantState().newDrawable().mutate();
}
- btv.applyIconAndLabel(taskIcon, task1.titleDescription);
- btv.setTag(groupTask);
+ btv.applyIconAndLabel(taskIcon, task.titleDescription);
+ btv.setTag(singleTask);
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 0f05887..cbc5d3d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -92,6 +92,7 @@
import com.android.launcher3.util.MultiTranslateDelegate;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SingleTask;
import com.android.systemui.shared.recents.model.Task;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
@@ -739,9 +740,9 @@
return mControllers.taskbarRecentAppsController.getRunningAppState(
itemInfo.getTaskId());
}
- if (tag instanceof GroupTask groupTask && !groupTask.hasMultipleTasks()) {
+ if (tag instanceof SingleTask singleTask) {
return mControllers.taskbarRecentAppsController.getRunningAppState(
- groupTask.task1.key.id);
+ singleTask.getTask().key.id);
}
return BubbleTextView.RunningAppState.NOT_RUNNING;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index f672840..23f4f67 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -65,7 +65,6 @@
import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback;
import static com.android.quickstep.util.SplitAnimationTimings.TABLET_HOME_TO_SPLIT;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -150,8 +149,9 @@
import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.QuickSwitchTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewDismissTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewLaunchTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.TaskViewRecentsTouchContext;
-import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchControllerDeprecated;
import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.TwoButtonNavbarTouchController;
@@ -178,10 +178,10 @@
import com.android.quickstep.TouchInteractionService.TISBinder;
import com.android.quickstep.util.ActiveGestureProtoLogProxy;
import com.android.quickstep.util.AsyncClockEventDelegate;
-import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.LauncherUnfoldAnimationController;
import com.android.quickstep.util.QuickstepOnboardingPrefs;
import com.android.quickstep.util.SplitSelectStateController;
+import com.android.quickstep.util.SplitTask;
import com.android.quickstep.util.SplitToWorkspaceController;
import com.android.quickstep.util.SplitWithKeyboardShortcutController;
import com.android.quickstep.util.TISBindHelper;
@@ -460,7 +460,7 @@
protected void onItemClicked(View view) {
if (!mSplitToWorkspaceController.handleSecondAppSelectionForSplit(view)) {
- QuickstepLauncher.super.getItemOnClickListener().onClick(view);
+ super.getItemOnClickListener().onClick(view);
}
}
@@ -688,7 +688,8 @@
}
if (enableExpressiveDismissTaskMotion()) {
- list.add(new TaskViewTouchController<>(this, mTaskViewRecentsTouchContext));
+ list.add(new TaskViewLaunchTouchController<>(this, mTaskViewRecentsTouchContext));
+ list.add(new TaskViewDismissTouchController<>(this, mTaskViewRecentsTouchContext));
} else {
list.add(new TaskViewTouchControllerDeprecated<>(this, mTaskViewRecentsTouchContext));
}
@@ -732,6 +733,9 @@
final boolean ret = super.initDeviceProfile(idp);
mDeviceProfile.isPredictiveBackSwipe =
getApplicationInfo().isOnBackInvokedCallbackEnabled();
+ if (ret) {
+ SystemUiProxy.INSTANCE.get(this).setLauncherAppIconSize(mDeviceProfile.iconSizePx);
+ }
return ret;
}
@@ -1161,9 +1165,9 @@
.getHotseatTranslationXForNavBar(this, isBubblesOnLeft);
}
if (isBubbleBarEnabled()
- && mDeviceProfile.shouldAdjustHotseatForBubbleBar(getContext(), hasBubbles())) {
+ && mDeviceProfile.shouldAdjustHotseatForBubbleBar(asContext(), hasBubbles())) {
translationX += (int) mDeviceProfile
- .getHotseatAdjustedTranslation(getContext(), itemInfo.cellX);
+ .getHotseatAdjustedTranslation(asContext(), itemInfo.cellX);
}
return translationX;
}
@@ -1238,6 +1242,7 @@
}
}
+ @NonNull
@Override
public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) {
ActivityOptionsWrapper activityOptions = mAppTransitionManager.getActivityLaunchOptions(
@@ -1368,12 +1373,6 @@
}
@Override
- protected void onDeviceProfileInitiated() {
- super.onDeviceProfileInitiated();
- SystemUiProxy.INSTANCE.get(this).setLauncherAppIconSize(mDeviceProfile.iconSizePx);
- }
-
- @Override
public void dispatchDeviceProfileChanged() {
super.dispatchDeviceProfileChanged();
Trace.instantForTrack(TRACE_TAG_APP, "QuickstepLauncher#DeviceProfileChanged",
@@ -1386,33 +1385,20 @@
}
/**
- * Launches the given {@link GroupTask} in splitscreen.
+ * Launches the given {@link SplitTask} in splitscreen.
*/
public void launchSplitTasks(
- @NonNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition) {
- // SplitBounds can be null if coming from Taskbar launch.
- final boolean firstTaskIsLeftTopTask = isFirstTaskLeftTopTask(groupTask);
- // task2 should never be null when calling this method. Allow a crash to catch invalid calls
- Task task1 = firstTaskIsLeftTopTask ? groupTask.task1 : groupTask.task2;
- Task task2 = firstTaskIsLeftTopTask ? groupTask.task2 : groupTask.task1;
- mSplitSelectStateController.launchExistingSplitPair(
- null /* launchingTaskView */,
- task1.key.id,
- task2.key.id,
+ @NonNull SplitTask splitTask, @Nullable RemoteTransition remoteTransition) {
+ mSplitSelectStateController.launchExistingSplitPair(null /* launchingTaskView */,
+ splitTask.getTopLeftTask().key.id,
+ splitTask.getBottomRightTask().key.id,
SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
/* callback= */ success -> mSplitSelectStateController.resetState(),
/* freezeTaskList= */ false,
- groupTask.mSplitBounds == null
- ? SNAP_TO_2_50_50
- : groupTask.mSplitBounds.snapPosition,
+ splitTask.getSplitBounds().snapPosition,
remoteTransition);
}
- private static boolean isFirstTaskLeftTopTask(@NonNull GroupTask groupTask) {
- return groupTask.mSplitBounds == null
- || groupTask.mSplitBounds.leftTopTaskId == groupTask.task1.key.id;
- }
-
/**
* Launches two apps as an app pair.
*/
@@ -1522,4 +1508,9 @@
public void setCanShowAllAppsEducationView(boolean canShowAllAppsEducationView) {
mCanShowAllAppsEducationView = canShowAllAppsEducationView;
}
+
+ @Override
+ public void returnToHomescreen() {
+ getStateManager().goToState(LauncherState.NORMAL);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt b/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
index d097dba..6e901ee 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
@@ -144,6 +144,14 @@
override fun isNonResizeableActivity(lai: LauncherActivityInfo) =
lai.activityInfo.resizeMode == ActivityInfo.RESIZE_MODE_UNRESIZEABLE
+ override fun supportsMultiInstance(lai: LauncherActivityInfo) : Boolean {
+ return try {
+ super.supportsMultiInstance(lai) || lai.supportsMultiInstance()
+ } catch (e: Exception) {
+ false
+ }
+ }
+
/**
* Starts an Activity which can be used to set this Launcher as the HOME app, via a consent
* screen. In case the consent screen cannot be shown, or the user does not set current Launcher
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index dae63af..10513c0 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -48,8 +48,7 @@
}
@Override
- public <DEVICE_PROFILE_CONTEXT extends Context & ActivityContext>
- int getTransitionDuration(DEVICE_PROFILE_CONTEXT context, boolean isToState) {
+ public int getTransitionDuration(ActivityContext context, boolean isToState) {
return isToState
? context.getDeviceProfile().allAppsOpenDuration
: context.getDeviceProfile().allAppsCloseDuration;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
index 932d241..80fc5fa 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
@@ -17,12 +17,12 @@
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
-import android.content.Context;
import android.graphics.Rect;
import com.android.launcher3.Flags;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.views.RecentsView;
/**
@@ -39,7 +39,7 @@
}
@Override
- public int getTransitionDuration(Context launcher, boolean isToState) {
+ public int getTransitionDuration(ActivityContext launcher, boolean isToState) {
return 300;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 5c16a62..15216fe 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -29,6 +29,7 @@
import com.android.launcher3.R;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.Themes;
+import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.util.BaseDepthController;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.views.RecentsView;
@@ -62,10 +63,10 @@
}
@Override
- public int getTransitionDuration(Context context, boolean isToState) {
+ public int getTransitionDuration(ActivityContext context, boolean isToState) {
if (isToState) {
// In gesture modes, overview comes in all the way from the side, so give it more time.
- return DisplayController.getNavigationMode(context).hasGestures
+ return DisplayController.getNavigationMode(context.asContext()).hasGestures
? OVERVIEW_SLIDE_IN_DURATION
: OVERVIEW_POP_IN_DURATION;
} else {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
index 3ae221b..2631fbf 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
@@ -16,9 +16,8 @@
package com.android.launcher3.uioverrides.states;
-import android.content.Context;
-
import com.android.launcher3.Launcher;
+import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.util.SplitAnimationTimings;
import com.android.quickstep.views.RecentsView;
@@ -43,11 +42,10 @@
}
@Override
- public int getTransitionDuration(Context context, boolean isToState) {
- boolean isTablet = ((Launcher) context).getDeviceProfile().isTablet;
- if (isToState && isTablet) {
+ public int getTransitionDuration(ActivityContext context, boolean isToState) {
+ if (isToState && context.getDeviceProfile().isTablet) {
return SplitAnimationTimings.TABLET_ENTER_DURATION;
- } else if (isToState && !isTablet) {
+ } else if (isToState) {
return SplitAnimationTimings.PHONE_ENTER_DURATION;
} else {
return SplitAnimationTimings.ABORT_DURATION;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index cbfcda7..05d12c3 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -186,7 +186,7 @@
mIsTrackpadSwipe = isTrackpadFourFingerSwipe(ev);
return mIsTrackpadSwipe;
}
- if (DesktopModeStatus.canEnterDesktopMode(mLauncher.getContext())
+ if (DesktopModeStatus.canEnterDesktopMode(mLauncher)
//TODO(b/345296916): replace with dev option once in teamfood
&& Flags.enableQuickswitchDesktopSplitBugfix()
&& mRecentsView.getNonDesktopTaskViewCount() < 1) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index 2b296c8..f582324 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -149,9 +149,9 @@
mOverviewPanel.setFullscreenProgress(progress);
if (progress > UPDATE_SYSUI_FLAGS_THRESHOLD) {
int sysuiFlags = 0;
- TaskView tv = mOverviewPanel.getFirstTaskView();
- if (tv != null) {
- sysuiFlags = tv.getSysUiStatusNavFlags();
+ TaskView firstTaskView = mOverviewPanel.getFirstTaskView();
+ if (firstTaskView != null) {
+ sysuiFlags = firstTaskView.getSysUiStatusNavFlags();
}
mLauncher.getSystemUiController().updateUiState(UI_STATE_FULLSCREEN_TASK, sysuiFlags);
} else {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt
new file mode 100644
index 0000000..99b962b
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.uioverrides.touchcontrollers
+
+import android.content.Context
+import android.view.MotionEvent
+import androidx.dynamicanimation.animation.SpringAnimation
+import com.android.app.animation.Interpolators.DECELERATE
+import com.android.launcher3.AbstractFloatingView
+import com.android.launcher3.Utilities.EDGE_NAV_BAR
+import com.android.launcher3.Utilities.boundToRange
+import com.android.launcher3.Utilities.isRtl
+import com.android.launcher3.Utilities.mapToRange
+import com.android.launcher3.touch.SingleAxisSwipeDetector
+import com.android.launcher3.util.TouchController
+import com.android.quickstep.views.RecentsView
+import com.android.quickstep.views.RecentsViewContainer
+import com.android.quickstep.views.TaskView
+import kotlin.math.abs
+import kotlin.math.sign
+
+/** Touch controller for handling task view card dismiss swipes */
+class TaskViewDismissTouchController<CONTAINER>(
+ private val container: CONTAINER,
+ private val taskViewRecentsTouchContext: TaskViewRecentsTouchContext,
+) : TouchController, SingleAxisSwipeDetector.Listener where
+CONTAINER : Context,
+CONTAINER : RecentsViewContainer {
+ private val recentsView: RecentsView<*, *> = container.getOverviewPanel()
+ private val detector: SingleAxisSwipeDetector =
+ SingleAxisSwipeDetector(
+ container as Context,
+ this,
+ recentsView.pagedOrientationHandler.upDownSwipeDirection,
+ )
+ private val isRtl = isRtl(container.resources)
+
+ private var taskBeingDragged: TaskView? = null
+ private var springAnimation: SpringAnimation? = null
+ private var dismissLength: Int = 0
+ private var verticalFactor: Int = 0
+ private var initialDisplacement: Float = 0f
+
+ private fun canInterceptTouch(ev: MotionEvent): Boolean =
+ when {
+ // Don't intercept swipes on the nav bar, as user might be trying to go home during a
+ // task dismiss animation.
+ (ev.edgeFlags and EDGE_NAV_BAR) != 0 -> {
+ false
+ }
+
+ // Floating views that a TouchController should not try to intercept touches from.
+ AbstractFloatingView.getTopOpenViewWithType(
+ container,
+ AbstractFloatingView.TYPE_TOUCH_CONTROLLER_NO_INTERCEPT,
+ ) != null -> false
+
+ // Disable swiping if the task overlay is modal.
+ taskViewRecentsTouchContext.isRecentsModal -> {
+ false
+ }
+
+ else -> taskViewRecentsTouchContext.isRecentsInteractive
+ }
+
+ override fun onControllerInterceptTouchEvent(ev: MotionEvent): Boolean {
+ if ((ev.action == MotionEvent.ACTION_UP || ev.action == MotionEvent.ACTION_CANCEL)) {
+ clearState()
+ }
+ if (ev.action == MotionEvent.ACTION_DOWN) {
+ if (!onActionDown(ev)) {
+ return false
+ }
+ }
+
+ onControllerTouchEvent(ev)
+ return detector.isDraggingState && detector.wasInitialTouchPositive()
+ }
+
+ override fun onControllerTouchEvent(ev: MotionEvent?): Boolean = detector.onTouchEvent(ev)
+
+ private fun onActionDown(ev: MotionEvent): Boolean {
+ springAnimation?.cancel()
+ if (!canInterceptTouch(ev)) {
+ return false
+ }
+
+ taskBeingDragged =
+ recentsView.taskViews
+ .firstOrNull {
+ recentsView.isTaskViewVisible(it) && container.dragLayer.isEventOverView(it, ev)
+ }
+ ?.also {
+ dismissLength = recentsView.pagedOrientationHandler.getSecondaryDimension(it)
+ verticalFactor =
+ recentsView.pagedOrientationHandler.secondaryTranslationDirectionFactor
+ }
+
+ detector.setDetectableScrollConditions(
+ recentsView.pagedOrientationHandler.getUpDirection(isRtl),
+ /* ignoreSlop = */ false,
+ )
+
+ return true
+ }
+
+ override fun onDragStart(start: Boolean, startDisplacement: Float) {
+ val taskBeingDragged = taskBeingDragged ?: return
+
+ initialDisplacement =
+ taskBeingDragged.secondaryDismissTranslationProperty.get(taskBeingDragged)
+
+ // Add a tiny bit of translation Z, so that it draws on top of other views. This is relevant
+ // (e.g.) when we dismiss a task by sliding it upward: if there is a row of icons above, we
+ // want the dragged task to stay above all other views.
+ taskBeingDragged.translationZ = 0.1f
+ }
+
+ override fun onDrag(displacement: Float): Boolean {
+ val taskBeingDragged = taskBeingDragged ?: return false
+ val currentDisplacement = displacement + initialDisplacement
+ val boundedDisplacement =
+ boundToRange(abs(currentDisplacement), 0f, dismissLength.toFloat())
+ // When swiping below origin, allow slight undershoot to simulate resisting the movement.
+ val totalDisplacement =
+ if (isDisplacementPositiveDirection(currentDisplacement))
+ boundedDisplacement * sign(currentDisplacement)
+ else
+ mapToRange(
+ boundedDisplacement,
+ 0f,
+ dismissLength.toFloat(),
+ 0f,
+ DISMISS_MAX_UNDERSHOOT,
+ DECELERATE,
+ )
+ taskBeingDragged.secondaryDismissTranslationProperty.setValue(
+ taskBeingDragged,
+ totalDisplacement,
+ )
+ if (taskBeingDragged.isRunningTask && recentsView.enableDrawingLiveTile) {
+ recentsView.runActionOnRemoteHandles { remoteTargetHandle ->
+ remoteTargetHandle.taskViewSimulator.taskSecondaryTranslation.value =
+ totalDisplacement
+ }
+ recentsView.redrawLiveTile()
+ }
+ return true
+ }
+
+ override fun onDragEnd(velocity: Float) {
+ val taskBeingDragged = taskBeingDragged ?: return
+
+ val currentDisplacement =
+ taskBeingDragged.secondaryDismissTranslationProperty.get(taskBeingDragged)
+ if (currentDisplacement == 0f) {
+ clearState()
+ return
+ }
+ val isBeyondDismissThreshold =
+ abs(currentDisplacement) > abs(DISMISS_THRESHOLD_FRACTION * dismissLength)
+ val isFlingingTowardsDismiss = detector.isFling(velocity) && velocity < 0
+ val isFlingingTowardsRestState = detector.isFling(velocity) && velocity > 0
+ val isDismissing =
+ isFlingingTowardsDismiss || (isBeyondDismissThreshold && !isFlingingTowardsRestState)
+ springAnimation =
+ recentsView
+ .createTaskDismissSettlingSpringAnimation(
+ taskBeingDragged,
+ velocity,
+ isDismissing,
+ detector,
+ dismissLength,
+ this::clearState,
+ )
+ .apply {
+ animateToFinalPosition(
+ if (isDismissing) (dismissLength * verticalFactor).toFloat() else 0f
+ )
+ }
+ }
+
+ // Returns if the current task being dragged is towards "positive" (e.g. dismissal).
+ private fun isDisplacementPositiveDirection(displacement: Float): Boolean =
+ sign(displacement) == sign(verticalFactor.toFloat())
+
+ private fun clearState() {
+ detector.finishedScrolling()
+ detector.setDetectableScrollConditions(0, false)
+ taskBeingDragged?.translationZ = 0f
+ taskBeingDragged = null
+ springAnimation = null
+ }
+
+ companion object {
+ private const val DISMISS_THRESHOLD_FRACTION = 0.5f
+ private const val DISMISS_MAX_UNDERSHOOT = 25f
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewLaunchTouchController.kt b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewLaunchTouchController.kt
new file mode 100644
index 0000000..c740dad
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewLaunchTouchController.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.uioverrides.touchcontrollers
+
+import android.content.Context
+import android.graphics.Rect
+import android.view.MotionEvent
+import com.android.app.animation.Interpolators.ZOOM_IN
+import com.android.launcher3.AbstractFloatingView
+import com.android.launcher3.LauncherAnimUtils
+import com.android.launcher3.Utilities.EDGE_NAV_BAR
+import com.android.launcher3.Utilities.boundToRange
+import com.android.launcher3.Utilities.isRtl
+import com.android.launcher3.anim.AnimatorPlaybackController
+import com.android.launcher3.touch.BaseSwipeDetector
+import com.android.launcher3.touch.SingleAxisSwipeDetector
+import com.android.launcher3.util.DisplayController
+import com.android.launcher3.util.FlingBlockCheck
+import com.android.launcher3.util.TouchController
+import com.android.quickstep.views.RecentsView
+import com.android.quickstep.views.RecentsViewContainer
+import com.android.quickstep.views.TaskView
+import kotlin.math.abs
+
+/** Touch controller which handles dragging task view cards for launch. */
+class TaskViewLaunchTouchController<CONTAINER>(
+ private val container: CONTAINER,
+ private val taskViewRecentsTouchContext: TaskViewRecentsTouchContext,
+) : TouchController, SingleAxisSwipeDetector.Listener where
+CONTAINER : Context,
+CONTAINER : RecentsViewContainer {
+ private val tempRect = Rect()
+ private val flingBlockCheck = FlingBlockCheck()
+ private val recentsView: RecentsView<*, *> = container.getOverviewPanel()
+ private val detector: SingleAxisSwipeDetector =
+ SingleAxisSwipeDetector(
+ container as Context,
+ this,
+ recentsView.pagedOrientationHandler.upDownSwipeDirection,
+ )
+ private val isRtl = isRtl(container.resources)
+
+ private var taskBeingDragged: TaskView? = null
+ private var launchEndDisplacement: Float = 0f
+ private var playbackController: AnimatorPlaybackController? = null
+ private var verticalFactor: Int = 0
+
+ private fun canTaskLaunchTaskView(taskView: TaskView?) =
+ taskView != null &&
+ taskView === recentsView.currentPageTaskView &&
+ DisplayController.getNavigationMode(container).hasGestures &&
+ (!recentsView.showAsGrid() || taskView.isLargeTile) &&
+ recentsView.isTaskInExpectedScrollPosition(taskView)
+
+ private fun canInterceptTouch(ev: MotionEvent): Boolean =
+ when {
+ // Don't intercept swipes on the nav bar, as user might be trying to go home during a
+ // task dismiss animation.
+ (ev.edgeFlags and EDGE_NAV_BAR) != 0 -> {
+ false
+ }
+
+ // Floating views that a TouchController should not try to intercept touches from.
+ AbstractFloatingView.getTopOpenViewWithType(
+ container,
+ AbstractFloatingView.TYPE_TOUCH_CONTROLLER_NO_INTERCEPT,
+ ) != null -> {
+ false
+ }
+
+ // Disable swiping if the task overlay is modal.
+ taskViewRecentsTouchContext.isRecentsModal -> {
+ false
+ }
+
+ else -> taskViewRecentsTouchContext.isRecentsInteractive
+ }
+
+ override fun onControllerInterceptTouchEvent(ev: MotionEvent): Boolean {
+ if (
+ (ev.action == MotionEvent.ACTION_UP || ev.action == MotionEvent.ACTION_CANCEL) &&
+ playbackController == null
+ ) {
+ clearState()
+ }
+ if (ev.action == MotionEvent.ACTION_DOWN) {
+ if (!onActionDown(ev)) {
+ clearState()
+ return false
+ }
+ }
+ onControllerTouchEvent(ev)
+ return detector.isDraggingState && !detector.wasInitialTouchPositive()
+ }
+
+ override fun onControllerTouchEvent(ev: MotionEvent) = detector.onTouchEvent(ev)
+
+ private fun onActionDown(ev: MotionEvent): Boolean {
+ if (!canInterceptTouch(ev)) {
+ return false
+ }
+ taskBeingDragged =
+ recentsView.taskViews
+ .firstOrNull {
+ recentsView.isTaskViewVisible(it) && container.dragLayer.isEventOverView(it, ev)
+ }
+ ?.also {
+ verticalFactor =
+ recentsView.pagedOrientationHandler.secondaryTranslationDirectionFactor
+ }
+ if (!canTaskLaunchTaskView(taskBeingDragged)) {
+ return false
+ }
+ detector.setDetectableScrollConditions(
+ recentsView.pagedOrientationHandler.getDownDirection(isRtl),
+ /* ignoreSlop = */ false,
+ )
+ return true
+ }
+
+ override fun onDragStart(start: Boolean, startDisplacement: Float) {
+ val taskBeingDragged = taskBeingDragged ?: return
+
+ val secondaryLayerDimension: Int =
+ recentsView.pagedOrientationHandler.getSecondaryDimension(container.getDragLayer())
+ val maxDuration = 2L * secondaryLayerDimension
+ recentsView.clearPendingAnimation()
+ val pendingAnimation =
+ recentsView.createTaskLaunchAnimation(taskBeingDragged, maxDuration, ZOOM_IN)
+ // Since the thumbnail is what is filling the screen, based the end displacement on it.
+ taskBeingDragged.getThumbnailBounds(tempRect, /* relativeToDragLayer= */ true)
+ launchEndDisplacement = (secondaryLayerDimension - tempRect.bottom).toFloat()
+ playbackController =
+ pendingAnimation.createPlaybackController()?.apply {
+ taskViewRecentsTouchContext.onUserControlledAnimationCreated(this)
+ dispatchOnStart()
+ }
+ }
+
+ override fun onDrag(displacement: Float): Boolean {
+ playbackController?.setPlayFraction(
+ boundToRange(displacement / launchEndDisplacement, 0f, 1f)
+ )
+ return true
+ }
+
+ override fun onDragEnd(velocity: Float) {
+ val playbackController = playbackController ?: return
+
+ val isBeyondLaunchThreshold =
+ abs(playbackController.progressFraction) > abs(LAUNCH_THRESHOLD_FRACTION)
+ val isFlingingTowardsLaunch = detector.isFling(velocity) && velocity > 0
+ val isFlingingTowardsRestState = detector.isFling(velocity) && velocity < 0
+ val isLaunching =
+ isFlingingTowardsLaunch || (isBeyondLaunchThreshold && !isFlingingTowardsRestState)
+
+ val progress = playbackController.progressFraction
+ var animationDuration =
+ BaseSwipeDetector.calculateDuration(
+ velocity,
+ if (isLaunching) (1 - progress) else progress,
+ )
+ if (detector.isFling(velocity) && flingBlockCheck.isBlocked && !isLaunching) {
+ animationDuration *= LauncherAnimUtils.blockedFlingDurationFactor(velocity).toLong()
+ }
+
+ playbackController.setEndAction(this::clearState)
+ playbackController.startWithVelocity(
+ container,
+ isLaunching,
+ velocity,
+ launchEndDisplacement,
+ animationDuration,
+ )
+ }
+
+ private fun clearState() {
+ detector.finishedScrolling()
+ detector.setDetectableScrollConditions(0, false)
+ taskBeingDragged = null
+ playbackController = null
+ }
+
+ companion object {
+ private const val LAUNCH_THRESHOLD_FRACTION: Float = 0.5f
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.kt b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.kt
deleted file mode 100644
index c996f34..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.kt
+++ /dev/null
@@ -1,394 +0,0 @@
-/*
- * Copyright (C) 2025 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.uioverrides.touchcontrollers
-
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
-import android.content.Context
-import android.graphics.Rect
-import android.os.VibrationEffect
-import android.view.MotionEvent
-import android.view.animation.Interpolator
-import com.android.app.animation.Interpolators
-import com.android.launcher3.AbstractFloatingView
-import com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS
-import com.android.launcher3.LauncherAnimUtils.blockedFlingDurationFactor
-import com.android.launcher3.R
-import com.android.launcher3.Utilities
-import com.android.launcher3.anim.AnimatorPlaybackController
-import com.android.launcher3.anim.PendingAnimation
-import com.android.launcher3.touch.BaseSwipeDetector
-import com.android.launcher3.touch.SingleAxisSwipeDetector
-import com.android.launcher3.util.DisplayController
-import com.android.launcher3.util.FlingBlockCheck
-import com.android.launcher3.util.TouchController
-import com.android.launcher3.util.VibratorWrapper
-import com.android.quickstep.util.VibrationConstants
-import com.android.quickstep.views.RecentsView
-import com.android.quickstep.views.RecentsViewContainer
-import com.android.quickstep.views.TaskView
-import kotlin.math.abs
-
-/** Touch controller for handling task view card swipes */
-class TaskViewTouchController<CONTAINER>(
- private val container: CONTAINER,
- private val taskViewRecentsTouchContext: TaskViewRecentsTouchContext,
-) : AnimatorListenerAdapter(), TouchController, SingleAxisSwipeDetector.Listener where
-CONTAINER : Context,
-CONTAINER : RecentsViewContainer {
- private val recentsView: RecentsView<*, *> = container.getOverviewPanel()
- private val detector: SingleAxisSwipeDetector =
- SingleAxisSwipeDetector(
- container as Context,
- this,
- recentsView.pagedOrientationHandler.upDownSwipeDirection,
- )
- private val tempRect = Rect()
- private val isRtl = Utilities.isRtl(container.resources)
- private val flingBlockCheck = FlingBlockCheck()
-
- private var currentAnimation: AnimatorPlaybackController? = null
- private var currentAnimationIsGoingUp = false
- private var allowGoingUp = false
- private var allowGoingDown = false
- private var noIntercept = false
- private var displacementShift = 0f
- private var progressMultiplier = 0f
- private var endDisplacement = 0f
- private var draggingEnabled = true
- private var overrideVelocity: Float? = null
- private var taskBeingDragged: TaskView? = null
- private var isDismissHapticRunning = false
-
- private fun canInterceptTouch(ev: MotionEvent): Boolean {
- val currentAnimation = currentAnimation
- return when {
- (ev.edgeFlags and Utilities.EDGE_NAV_BAR) != 0 -> {
- // Don't intercept swipes on the nav bar, as user might be trying to go home
- // during a task dismiss animation.
- currentAnimation?.animationPlayer?.end()
- false
- }
- currentAnimation != null -> {
- currentAnimation.forceFinishIfCloseToEnd()
- true
- }
- AbstractFloatingView.getTopOpenViewWithType(
- container,
- AbstractFloatingView.TYPE_TOUCH_CONTROLLER_NO_INTERCEPT,
- ) != null -> false
- else -> taskViewRecentsTouchContext.isRecentsInteractive
- }
- }
-
- override fun onAnimationCancel(animation: Animator) {
- if (animation === currentAnimation?.target) {
- clearState()
- }
- }
-
- override fun onControllerInterceptTouchEvent(ev: MotionEvent): Boolean {
- if (
- (ev.action == MotionEvent.ACTION_UP || ev.action == MotionEvent.ACTION_CANCEL) &&
- currentAnimation == null
- ) {
- clearState()
- }
- if (ev.action == MotionEvent.ACTION_DOWN) {
- // Disable swiping up and down if the task overlay is modal.
- if (taskViewRecentsTouchContext.isRecentsModal) {
- noIntercept = true
- return false
- }
- noIntercept = !canInterceptTouch(ev)
- if (noIntercept) {
- return false
- }
- // Now figure out which direction scroll events the controller will start
- // calling the callbacks.
- var directionsToDetectScroll = 0
- var ignoreSlopWhenSettling = false
- if (currentAnimation != null) {
- directionsToDetectScroll = SingleAxisSwipeDetector.DIRECTION_BOTH
- ignoreSlopWhenSettling = true
- } else {
- taskBeingDragged = null
- recentsView.taskViews.forEach { taskView ->
- if (
- recentsView.isTaskViewVisible(taskView) &&
- container.dragLayer.isEventOverView(taskView, ev)
- ) {
- taskBeingDragged = taskView
- val upDirection = recentsView.pagedOrientationHandler.getUpDirection(isRtl)
-
- // The task can be dragged up to dismiss it
- allowGoingUp = true
-
- // The task can be dragged down to open it if:
- // - It's the current page
- // - We support gestures to enter overview
- // - It's the focused task if in grid view
- // - The task is snapped
- allowGoingDown =
- taskView === recentsView.currentPageTaskView &&
- DisplayController.getNavigationMode(container).hasGestures &&
- (!recentsView.showAsGrid() || taskView.isLargeTile) &&
- recentsView.isTaskInExpectedScrollPosition(taskView)
-
- directionsToDetectScroll =
- if (allowGoingDown) SingleAxisSwipeDetector.DIRECTION_BOTH
- else upDirection
- return@forEach
- }
- }
- if (taskBeingDragged == null) {
- noIntercept = true
- return false
- }
- }
- detector.setDetectableScrollConditions(directionsToDetectScroll, ignoreSlopWhenSettling)
- }
- if (noIntercept) {
- return false
- }
- onControllerTouchEvent(ev)
- return detector.isDraggingOrSettling
- }
-
- override fun onControllerTouchEvent(ev: MotionEvent): Boolean = detector.onTouchEvent(ev)
-
- private fun reInitAnimationController(goingUp: Boolean) {
- if (currentAnimation != null && currentAnimationIsGoingUp == goingUp) {
- // No need to init
- return
- }
- if ((goingUp && !allowGoingUp) || (!goingUp && !allowGoingDown)) {
- // Trying to re-init in an unsupported direction.
- return
- }
- val taskBeingDragged = taskBeingDragged ?: return
- currentAnimation?.setPlayFraction(0f)
- currentAnimation?.target?.removeListener(this)
- currentAnimation?.dispatchOnCancel()
-
- val orientationHandler = recentsView.pagedOrientationHandler
- currentAnimationIsGoingUp = goingUp
- val dl = container.dragLayer
- val secondaryLayerDimension = orientationHandler.getSecondaryDimension(dl)
- val maxDuration = 2L * secondaryLayerDimension
- val verticalFactor = orientationHandler.getTaskDragDisplacementFactor(isRtl)
- val secondaryTaskDimension = orientationHandler.getSecondaryDimension(taskBeingDragged)
- // The interpolator controlling the most prominent visual movement. We use this to determine
- // whether we passed SUCCESS_TRANSITION_PROGRESS.
- val currentInterpolator: Interpolator
- val pa: PendingAnimation
- if (goingUp) {
- currentInterpolator = Interpolators.LINEAR
- pa = PendingAnimation(maxDuration)
- recentsView.createTaskDismissAnimation(
- pa,
- taskBeingDragged,
- true, /* animateTaskView */
- true, /* removeTask */
- maxDuration,
- false, /* dismissingForSplitSelection*/
- )
-
- endDisplacement = -secondaryTaskDimension.toFloat()
- } else {
- currentInterpolator = Interpolators.ZOOM_IN
- pa =
- recentsView.createTaskLaunchAnimation(
- taskBeingDragged,
- maxDuration,
- currentInterpolator,
- )
-
- // Since the thumbnail is what is filling the screen, based the end displacement on it.
- taskBeingDragged.getThumbnailBounds(tempRect, /* relativeToDragLayer= */ true)
- endDisplacement = (secondaryLayerDimension - tempRect.bottom).toFloat()
- }
- endDisplacement *= verticalFactor.toFloat()
- currentAnimation =
- pa.createPlaybackController().apply {
- // Setting this interpolator doesn't affect the visual motion, but is used to
- // determine whether we successfully reached the target state in onDragEnd().
- target.interpolator = currentInterpolator
- taskViewRecentsTouchContext.onUserControlledAnimationCreated(this)
- target.addListener(this@TaskViewTouchController)
- dispatchOnStart()
- }
- progressMultiplier = 1 / endDisplacement
- }
-
- override fun onDragStart(start: Boolean, startDisplacement: Float) {
- if (!draggingEnabled) return
- val currentAnimation = currentAnimation
-
- val orientationHandler = recentsView.pagedOrientationHandler
- if (currentAnimation == null) {
- reInitAnimationController(orientationHandler.isGoingUp(startDisplacement, isRtl))
- displacementShift = 0f
- } else {
- displacementShift = currentAnimation.progressFraction / progressMultiplier
- currentAnimation.pause()
- }
- flingBlockCheck.unblockFling()
- overrideVelocity = null
- }
-
- override fun onDrag(displacement: Float): Boolean {
- if (!draggingEnabled) return true
- val taskBeingDragged = taskBeingDragged ?: return true
- val currentAnimation = currentAnimation ?: return true
-
- val orientationHandler = recentsView.pagedOrientationHandler
- val totalDisplacement = displacement + displacementShift
- val isGoingUp =
- if (totalDisplacement == 0f) currentAnimationIsGoingUp
- else orientationHandler.isGoingUp(totalDisplacement, isRtl)
- if (isGoingUp != currentAnimationIsGoingUp) {
- reInitAnimationController(isGoingUp)
- flingBlockCheck.blockFling()
- } else {
- flingBlockCheck.onEvent()
- }
-
- if (isGoingUp) {
- if (currentAnimation.progressFraction < ANIMATION_PROGRESS_FRACTION_MIDPOINT) {
- // Halve the value when dismissing, as we are animating the drag across the full
- // length for only the first half of the progress
- currentAnimation.setPlayFraction(
- Utilities.boundToRange(totalDisplacement * progressMultiplier / 2, 0f, 1f)
- )
- } else {
- // Set mOverrideVelocity to control task dismiss velocity in onDragEnd
- var velocityDimenId = R.dimen.default_task_dismiss_drag_velocity
- if (recentsView.showAsGrid()) {
- velocityDimenId =
- if (taskBeingDragged.isLargeTile) {
- R.dimen.default_task_dismiss_drag_velocity_grid_focus_task
- } else {
- R.dimen.default_task_dismiss_drag_velocity_grid
- }
- }
- overrideVelocity = -taskBeingDragged.resources.getDimension(velocityDimenId)
-
- // Once halfway through task dismissal interpolation, switch from reversible
- // dragging-task animation to playing the remaining task translation animations,
- // while this is in progress disable dragging.
- draggingEnabled = false
- }
- } else {
- currentAnimation.setPlayFraction(
- Utilities.boundToRange(totalDisplacement * progressMultiplier, 0f, 1f)
- )
- }
-
- return true
- }
-
- override fun onDragEnd(velocity: Float) {
- val taskBeingDragged = taskBeingDragged ?: return
- val currentAnimation = currentAnimation ?: return
-
- // Limit velocity, as very large scalar values make animations play too quickly
- val maxTaskDismissDragVelocity =
- taskBeingDragged.resources.getDimension(R.dimen.max_task_dismiss_drag_velocity)
- val endVelocity =
- Utilities.boundToRange(
- overrideVelocity ?: velocity,
- -maxTaskDismissDragVelocity,
- maxTaskDismissDragVelocity,
- )
- overrideVelocity = null
-
- var fling = draggingEnabled && detector.isFling(endVelocity)
- val goingToEnd: Boolean
- val blockedFling = fling && flingBlockCheck.isBlocked
- if (blockedFling) {
- fling = false
- }
- val orientationHandler = recentsView.pagedOrientationHandler
- val goingUp = orientationHandler.isGoingUp(endVelocity, isRtl)
- val progress = currentAnimation.progressFraction
- val interpolatedProgress = currentAnimation.interpolatedProgress
- goingToEnd =
- if (fling) {
- goingUp == currentAnimationIsGoingUp
- } else {
- interpolatedProgress > SUCCESS_TRANSITION_PROGRESS
- }
- var animationDuration =
- BaseSwipeDetector.calculateDuration(
- endVelocity,
- if (goingToEnd) (1 - progress) else progress,
- )
- if (blockedFling && !goingToEnd) {
- animationDuration *= blockedFlingDurationFactor(endVelocity).toLong()
- }
- // Due to very high or low velocity dismissals, animation durations can be inconsistently
- // long or short. Bound the duration for animation of task translations for a more
- // standardized feel.
- animationDuration =
- Utilities.boundToRange(
- animationDuration,
- MIN_TASK_DISMISS_ANIMATION_DURATION,
- MAX_TASK_DISMISS_ANIMATION_DURATION,
- )
-
- currentAnimation.setEndAction { this.clearState() }
- currentAnimation.startWithVelocity(
- container,
- goingToEnd,
- abs(endVelocity.toDouble()).toFloat(),
- endDisplacement,
- animationDuration,
- )
- if (goingUp && goingToEnd && !isDismissHapticRunning) {
- VibratorWrapper.INSTANCE.get(container)
- .vibrate(
- TASK_DISMISS_VIBRATION_PRIMITIVE,
- TASK_DISMISS_VIBRATION_PRIMITIVE_SCALE,
- TASK_DISMISS_VIBRATION_FALLBACK,
- )
- isDismissHapticRunning = true
- }
-
- draggingEnabled = true
- }
-
- private fun clearState() {
- detector.finishedScrolling()
- detector.setDetectableScrollConditions(0, false)
- draggingEnabled = true
- taskBeingDragged = null
- currentAnimation = null
- isDismissHapticRunning = false
- }
-
- companion object {
- private const val ANIMATION_PROGRESS_FRACTION_MIDPOINT = 0.5f
- private const val MIN_TASK_DISMISS_ANIMATION_DURATION: Long = 300
- private const val MAX_TASK_DISMISS_ANIMATION_DURATION: Long = 600
-
- private const val TASK_DISMISS_VIBRATION_PRIMITIVE: Int =
- VibrationEffect.Composition.PRIMITIVE_TICK
- private const val TASK_DISMISS_VIBRATION_PRIMITIVE_SCALE: Float = 1f
- private val TASK_DISMISS_VIBRATION_FALLBACK: VibrationEffect =
- VibrationConstants.EFFECT_TEXTURE_TICK
- }
-}
diff --git a/quickstep/src/com/android/launcher3/widget/picker/WidgetCategoryFilter.kt b/quickstep/src/com/android/launcher3/widget/picker/WidgetCategoryFilter.kt
new file mode 100644
index 0000000..69feb4a
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/widget/picker/WidgetCategoryFilter.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.picker
+
+/**
+ * A filter that can be applied on the widgetCategory attribute from appwidget-provider to identify
+ * if the widget can be displayed on a specific widget surface.
+ * - Negative value (e.g. "category_a.inv() and category_b.inv()" excludes the widgets with given
+ * categories.
+ * - Positive value (e.g. "category_a or category_b" includes widgets with those categories.
+ * - 0 means no filter.
+ */
+class WidgetCategoryFilter(val categoryMask: Int) {
+ /** Applies the [categoryMask] to return if the [widgetCategory] matches. */
+ fun matches(widgetCategory: Int): Boolean {
+ return if (categoryMask > 0) { // inclusion filter
+ (widgetCategory and categoryMask) != 0
+ } else if (categoryMask < 0) { // exclusion filter
+ (widgetCategory and categoryMask) == widgetCategory
+ } else {
+ true // no filter
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index a1baba1..f46f9ae 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -1490,7 +1490,7 @@
startShift, endShift, duration, interpolator, endTarget, velocityPxPerMs);
}
- private void doLogGesture(GestureEndTarget endTarget, @Nullable TaskView targetTask) {
+ private void doLogGesture(GestureEndTarget endTarget, @Nullable TaskView targetTaskView) {
if (mDp == null || !mDp.isGestureMode) {
// We probably never received an animation controller, skip logging.
return;
@@ -1508,9 +1508,9 @@
case NEW_TASK:
events.add(mLogDirectionUpOrLeft ? LAUNCHER_QUICKSWITCH_LEFT
: LAUNCHER_QUICKSWITCH_RIGHT);
- if (targetTask != null && DesktopModeStatus.canEnterDesktopMode(mContext)
+ if (targetTaskView != null && DesktopModeStatus.canEnterDesktopMode(mContext)
&& DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH.isTrue()) {
- if (targetTask.getType() == TaskViewType.DESKTOP) {
+ if (targetTaskView.getType() == TaskViewType.DESKTOP) {
events.add(LAUNCHER_QUICKSWITCH_ENTER_DESKTOP_MODE);
} else if (mPreviousTaskViewType == TaskViewType.DESKTOP) {
events.add(LAUNCHER_QUICKSWITCH_EXIT_DESKTOP_MODE);
@@ -1527,8 +1527,8 @@
.withInputType(mGestureState.isTrackpadGesture()
? SysUiStatsLog.LAUNCHER_UICHANGED__INPUT_TYPE__TRACKPAD
: SysUiStatsLog.LAUNCHER_UICHANGED__INPUT_TYPE__TOUCH);
- if (targetTask != null) {
- logger.withItemInfo(targetTask.getFirstItemInfo());
+ if (targetTaskView != null) {
+ logger.withItemInfo(targetTaskView.getItemInfo());
}
int pageIndex = endTarget == LAST_TASK || mRecentsView == null
@@ -2369,9 +2369,6 @@
ActiveGestureLog.CompoundString nextTaskLog =
ActiveGestureLog.CompoundString.newEmptyString();
for (TaskContainer container : nextTask.getTaskContainers()) {
- if (container == null) {
- continue;
- }
nextTaskLog.append("[id: %d, pkg: %s] | ",
container.getTask().key.id,
container.getTask().key.getPackageName());
diff --git a/quickstep/src/com/android/quickstep/HomeVisibilityState.kt b/quickstep/src/com/android/quickstep/HomeVisibilityState.kt
index 241e16d..1345e0b 100644
--- a/quickstep/src/com/android/quickstep/HomeVisibilityState.kt
+++ b/quickstep/src/com/android/quickstep/HomeVisibilityState.kt
@@ -18,6 +18,7 @@
import android.os.RemoteException
import android.util.Log
+import com.android.launcher3.Utilities
import com.android.launcher3.config.FeatureFlags
import com.android.launcher3.util.Executors
import com.android.wm.shell.shared.IHomeTransitionListener.Stub
@@ -41,10 +42,13 @@
transitions?.setHomeTransitionListener(
object : Stub() {
override fun onHomeVisibilityChanged(isVisible: Boolean) {
- Executors.MAIN_EXECUTOR.execute {
- isHomeVisible = isVisible
- listeners.forEach { it.onHomeVisibilityChanged(isVisible) }
- }
+ Utilities.postAsyncCallback(
+ Executors.MAIN_EXECUTOR.handler,
+ {
+ isHomeVisible = isVisible
+ listeners.forEach { it.onHomeVisibilityChanged(isVisible) }
+ },
+ )
}
}
)
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index c60d3e8..e1e9c99 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -56,6 +56,7 @@
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.animation.TransitionAnimator;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.InputConsumerController;
import java.util.Collections;
@@ -300,7 +301,9 @@
// Disable if swiping to PIP
return null;
}
- if (sourceTaskView == null || sourceTaskView.getFirstTask().key.getComponent() == null) {
+ Task firstTask;
+ if (sourceTaskView == null || ((firstTask = sourceTaskView.getFirstTask()) == null)
+ || firstTask.key.getComponent() == null) {
// Disable if it's an invalid task
return null;
}
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 5e8ea37..fca67c3 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -26,6 +26,7 @@
import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_PRE_DELAY;
import static com.android.launcher3.testing.shared.TestProtocol.LAUNCHER_ACTIVITY_STOPPED_MESSAGE;
import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL;
+import static com.android.launcher3.util.WallpaperThemeManager.setWallpaperDependentTheme;
import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely;
import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
import static com.android.quickstep.TaskViewUtils.createRecentsWindowAnimator;
@@ -247,7 +248,6 @@
@Override
public void returnToHomescreen() {
- super.returnToHomescreen();
// TODO(b/137318995) This should go home, but doing so removes freeform windows
}
@@ -261,6 +261,7 @@
}
}
+ @NonNull
@Override
public ActivityOptionsWrapper getActivityLaunchOptions(final View v, @Nullable ItemInfo item) {
if (!(v instanceof TaskView)) {
@@ -371,6 +372,7 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ setWallpaperDependentTheme(this);
mStateManager = new StateManager<>(this, RecentsState.BG_LAUNCHER);
@@ -431,7 +433,6 @@
*/
private void initDeviceProfile() {
mDeviceProfile = createDeviceProfile();
- onDeviceProfileInitiated();
}
@Override
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index e1e962a..090ccdc 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -114,7 +114,6 @@
private final RotationTouchHelper mRotationTouchHelper;
private final TaskStackChangeListener mPipListener;
- private final DaggerSingletonTracker mLifeCycle;
// Cache for better performance since it doesn't change at runtime.
private final boolean mCanImeRenderGesturalNavButtons =
InputMethodService.canImeRenderGesturalNavButtons();
@@ -152,16 +151,15 @@
mExclusionManager = exclusionManager;
mContextualSearchStateManager = contextualSearchStateManager;
mRotationTouchHelper = rotationTouchHelper;
- mLifeCycle = lifeCycle;
mIsOneHandedModeSupported = SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false);
// Register for exclusion updates
- mLifeCycle.addCloseable(this::unregisterExclusionListener);
+ lifeCycle.addCloseable(this::unregisterExclusionListener);
// Register for display changes changes
mDisplayController.addChangeListener(this);
onDisplayInfoChanged(context, mDisplayController.getInfo(), CHANGE_ALL);
- mLifeCycle.addCloseable(() -> mDisplayController.removeChangeListener(this));
+ lifeCycle.addCloseable(() -> mDisplayController.removeChangeListener(this));
if (mIsOneHandedModeSupported) {
Uri oneHandedUri = Settings.Secure.getUriFor(ONE_HANDED_ENABLED);
@@ -169,7 +167,7 @@
enabled -> mIsOneHandedModeEnabled = enabled;
settingsCache.register(oneHandedUri, onChangeListener);
mIsOneHandedModeEnabled = settingsCache.getValue(oneHandedUri);
- mLifeCycle.addCloseable(() -> settingsCache.unregister(oneHandedUri, onChangeListener));
+ lifeCycle.addCloseable(() -> settingsCache.unregister(oneHandedUri, onChangeListener));
} else {
mIsOneHandedModeEnabled = false;
}
@@ -180,7 +178,7 @@
enabled -> mIsSwipeToNotificationEnabled = enabled;
settingsCache.register(swipeBottomNotificationUri, onChangeListener);
mIsSwipeToNotificationEnabled = settingsCache.getValue(swipeBottomNotificationUri);
- mLifeCycle.addCloseable(
+ lifeCycle.addCloseable(
() -> settingsCache.unregister(swipeBottomNotificationUri, onChangeListener));
Uri setupCompleteUri = Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE);
@@ -188,7 +186,7 @@
if (!mIsUserSetupComplete) {
SettingsCache.OnChangeListener userSetupChangeListener = e -> mIsUserSetupComplete = e;
settingsCache.register(setupCompleteUri, userSetupChangeListener);
- mLifeCycle.addCloseable(
+ lifeCycle.addCloseable(
() -> settingsCache.unregister(setupCompleteUri, userSetupChangeListener));
}
@@ -210,15 +208,19 @@
}
};
TaskStackChangeListeners.getInstance().registerTaskStackListener(mPipListener);
- mLifeCycle.addCloseable(() ->
+ lifeCycle.addCloseable(() ->
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mPipListener));
}
/**
* Adds a listener for the nav mode change, guaranteed to be called after the device state's
* mode has changed.
+ *
+ * @return Added {@link DisplayInfoChangeListener} so that caller is
+ * responsible for removing the listener from {@link DisplayController} to avoid memory leak.
*/
- public void addNavigationModeChangedCallback(Runnable callback) {
+ public DisplayController.DisplayInfoChangeListener addNavigationModeChangedCallback(
+ Runnable callback) {
DisplayController.DisplayInfoChangeListener listener = (context, info, flags) -> {
if ((flags & CHANGE_NAVIGATION_MODE) != 0) {
callback.run();
@@ -226,7 +228,16 @@
};
mDisplayController.addChangeListener(listener);
callback.run();
- mLifeCycle.addCloseable(() -> mDisplayController.removeChangeListener(listener));
+ return listener;
+ }
+
+ /**
+ * Remove the {DisplayController.DisplayInfoChangeListener} added from
+ * {@link #addNavigationModeChangedCallback} when {@link TouchInteractionService} is destroyed.
+ */
+ public void removeDisplayInfoChangeListener(
+ DisplayController.DisplayInfoChangeListener listener) {
+ mDisplayController.removeChangeListener(listener);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index dc5d59f..87b58e6 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -22,6 +22,7 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
+import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.app.KeyguardManager;
@@ -41,6 +42,7 @@
import com.android.launcher3.graphics.ThemeManager.ThemeChangeListener;
import com.android.launcher3.icons.IconProvider;
import com.android.launcher3.util.Executors.SimpleThreadFactory;
+import com.android.launcher3.util.LockedUserState;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.SafeCloseable;
import com.android.quickstep.recents.data.RecentTasksDataSource;
@@ -63,6 +65,8 @@
import java.util.function.Consumer;
import java.util.function.Predicate;
+import javax.inject.Provider;
+
/**
* Singleton class to load and manage recents model.
*/
@@ -86,15 +90,19 @@
private final TaskIconCache mIconCache;
private final TaskThumbnailCache mThumbnailCache;
private final ComponentCallbacks mCallbacks;
- private final ThemeManager mThemeManager;
private final TaskStackChangeListeners mTaskStackChangeListeners;
private final SafeCloseable mIconChangeCloseable;
+ private final LockedUserState mLockedUserState;
+ private final Provider<ThemeManager> mThemeManagerProvider;
+ private final Runnable mUnlockCallback;
+
private RecentsModel(Context context) {
this(context, new IconProvider(context));
}
+ @SuppressLint("VisibleForTests")
private RecentsModel(Context context, IconProvider iconProvider) {
this(context,
new RecentTasksList(
@@ -107,14 +115,16 @@
new TaskThumbnailCache(context, RECENTS_MODEL_EXECUTOR),
iconProvider,
TaskStackChangeListeners.getInstance(),
- ThemeManager.INSTANCE.get(context));
+ LockedUserState.get(context),
+ () -> ThemeManager.INSTANCE.get(context));
}
@VisibleForTesting
RecentsModel(Context context, RecentTasksList taskList, TaskIconCache iconCache,
TaskThumbnailCache thumbnailCache, IconProvider iconProvider,
TaskStackChangeListeners taskStackChangeListeners,
- ThemeManager themeManager) {
+ LockedUserState lockedUserState,
+ Provider<ThemeManager> themeManagerProvider) {
mContext = context;
mTaskList = taskList;
mIconCache = iconCache;
@@ -140,8 +150,11 @@
mTaskStackChangeListeners.registerTaskStackListener(this);
mIconChangeCloseable = iconProvider.registerIconChangeListener(
this::onAppIconChanged, MAIN_EXECUTOR.getHandler());
- mThemeManager = themeManager;
- themeManager.addChangeListener(this);
+
+ mLockedUserState = lockedUserState;
+ mThemeManagerProvider = themeManagerProvider;
+ mUnlockCallback = () -> mThemeManagerProvider.get().addChangeListener(this);
+ lockedUserState.runOnUserUnlocked(mUnlockCallback);
}
public TaskIconCache getIconCache() {
@@ -402,7 +415,12 @@
mIconCache.removeTaskVisualsChangeListener();
mTaskStackChangeListeners.unregisterTaskStackListener(this);
mIconChangeCloseable.close();
- mThemeManager.removeChangeListener(this);
+
+ if (mLockedUserState.isUserUnlocked()) {
+ mThemeManagerProvider.get().removeChangeListener(this);
+ } else {
+ mLockedUserState.removeOnUserUnlockedRunnable(mUnlockCallback);
+ }
}
private boolean isCachePreloadingEnabled() {
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index ff9c9f6..a594e49 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -120,7 +120,8 @@
TaskShortcutFactory.WELLBEING,
TaskShortcutFactory.SAVE_APP_PAIR,
TaskShortcutFactory.SCREENSHOT,
- TaskShortcutFactory.MODAL
+ TaskShortcutFactory.MODAL,
+ TaskShortcutFactory.CLOSE,
};
/**
@@ -233,7 +234,7 @@
RecentsView overviewPanel = mTaskContainer.getTaskView().getRecentsView();
// Task has already been dismissed
if (overviewPanel == null) return;
- overviewPanel.initiateSplitSelect(mTaskContainer.getTaskView());
+ overviewPanel.initiateSplitSelect(mTaskContainer);
}
protected void saveAppPair() {
@@ -369,7 +370,7 @@
@Override
public void onClick(View view) {
- saveScreenshot(mTaskContainer.getTaskView().getFirstTask());
+ saveScreenshot(mTaskContainer.getTask());
dismissTaskMenuView();
}
}
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index ab5e830..7990aae 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -21,6 +21,7 @@
import static android.view.Surface.ROTATION_0;
import static com.android.launcher3.Flags.enableRefactorTaskThumbnail;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_CLOSE_APP_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
@@ -48,6 +49,7 @@
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.popup.SystemShortcut.AppInfo;
import com.android.launcher3.util.InstantAppResolver;
+import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
@@ -128,20 +130,28 @@
};
class SplitSelectSystemShortcut extends SystemShortcut {
- private final TaskView mTaskView;
+ private final TaskContainer mTaskContainer;
private final SplitPositionOption mSplitPositionOption;
- public SplitSelectSystemShortcut(RecentsViewContainer container, TaskView taskView,
+ public SplitSelectSystemShortcut(RecentsViewContainer container,
+ TaskContainer taskContainer, TaskView taskView,
SplitPositionOption option) {
- super(option.iconResId, option.textResId, container, taskView.getFirstItemInfo(),
+ super(option.iconResId, option.textResId, container, taskContainer.getItemInfo(),
taskView);
- mTaskView = taskView;
+ mTaskContainer = taskContainer;
mSplitPositionOption = option;
}
@Override
public void onClick(View view) {
- mTaskView.initiateSplitSelect(mSplitPositionOption);
+ RecentsView recentsView = mTaskContainer.getTaskView().getRecentsView();
+ if (recentsView != null) {
+ recentsView.initiateSplitSelect(
+ mTaskContainer,
+ mSplitPositionOption.stagePosition,
+ SplitConfigurationOptions.getLogEventForPosition(
+ mSplitPositionOption.stagePosition));
+ }
}
}
@@ -152,11 +162,9 @@
class SaveAppPairSystemShortcut extends SystemShortcut<RecentsViewContainer> {
private final GroupedTaskView mTaskView;
-
public SaveAppPairSystemShortcut(RecentsViewContainer container, GroupedTaskView taskView,
int iconResId) {
- super(iconResId, R.string.save_app_pair, container, taskView.getFirstItemInfo(),
- taskView);
+ super(iconResId, R.string.save_app_pair, container, taskView.getItemInfo(), taskView);
mTaskView = taskView;
}
@@ -202,14 +210,14 @@
}
private void startActivity() {
- final Task.TaskKey taskKey = mTaskView.getFirstTask().key;
- final int taskId = taskKey.id;
final ActivityOptions options = makeLaunchOptions(mTarget);
- if (options != null) {
- options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
+ if (options == null) {
+ return;
}
- if (options != null
- && ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId,
+ final Task.TaskKey taskKey = mTaskContainer.getTask().key;
+ final int taskId = taskKey.id;
+ options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
+ if (ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId,
options)) {
final Runnable animStartedListener = () -> {
// Hide the task view and wait for the window to be resized
@@ -252,8 +260,8 @@
overridePendingAppTransitionMultiThumbFuture(
future, animStartedListener, mHandler, true /* scaleUp */,
taskKey.displayId);
- mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getFirstItemInfo())
- .log(mLauncherEvent);
+ mTarget.getStatsLogManager().logger().withItemInfo(mTaskContainer.getItemInfo())
+ .log(mLauncherEvent);
}
}
@@ -289,6 +297,29 @@
}
}
+ class CloseSystemShortcut extends SystemShortcut {
+ private final TaskContainer mTaskContainer;
+
+ public CloseSystemShortcut(int iconResId, int textResId, RecentsViewContainer container,
+ TaskContainer taskContainer) {
+ super(iconResId, textResId, container, taskContainer.getTaskView().getFirstItemInfo(),
+ taskContainer.getTaskView());
+ mTaskContainer = taskContainer;
+ }
+
+ @Override
+ public void onClick(View view) {
+ TaskView taskView = mTaskContainer.getTaskView();
+ RecentsView<?, ?> recentsView = taskView.getRecentsView();
+ if (recentsView != null) {
+ dismissTaskMenuView();
+ recentsView.dismissTask(taskView, true, true);
+ mTarget.getStatsLogManager().logger().withItemInfo(mTaskContainer.getItemInfo())
+ .log(LAUNCHER_SYSTEM_SHORTCUT_CLOSE_APP_TAP);
+ }
+ }
+ }
+
/**
* Does NOT add split options in the following scenarios:
* * 1. Taskbar is not present AND aren't at least 2 tasks in overview to show split options for
@@ -327,7 +358,8 @@
return orientationHandler.getSplitPositionOptions(deviceProfile)
.stream()
.map((Function<SplitPositionOption, SystemShortcut>) option ->
- new SplitSelectSystemShortcut(container, taskView, option))
+ new SplitSelectSystemShortcut(container, taskContainer, taskView,
+ option))
.collect(Collectors.toList());
}
};
@@ -420,24 +452,24 @@
private static final String TAG = "PinSystemShortcut";
- private final TaskView mTaskView;
+ private final TaskContainer mTaskContainer;
public PinSystemShortcut(RecentsViewContainer target,
TaskContainer taskContainer) {
super(R.drawable.ic_pin, R.string.recent_task_option_pin, target,
taskContainer.getItemInfo(), taskContainer.getTaskView());
- mTaskView = taskContainer.getTaskView();
+ mTaskContainer = taskContainer;
}
@Override
public void onClick(View view) {
- if (mTaskView.launchAsStaticTile() != null) {
+ if (mTaskContainer.getTaskView().launchAsStaticTile() != null) {
SystemUiProxy.INSTANCE.get(mTarget.asContext()).startScreenPinning(
- mTaskView.getFirstTask().key.id);
+ mTaskContainer.getTask().key.id);
}
dismissTaskMenuView();
- mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getFirstItemInfo())
- .log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP);
+ mTarget.getStatsLogManager().logger().withItemInfo(mTaskContainer.getItemInfo())
+ .log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP);
}
}
@@ -508,4 +540,24 @@
return createSingletonShortcutList(modalStateSystemShortcut);
}
};
+
+ TaskShortcutFactory CLOSE = new TaskShortcutFactory() {
+ @Override
+ public List<SystemShortcut> getShortcuts(RecentsViewContainer container,
+ TaskContainer taskContainer) {
+ return Collections.singletonList(new CloseSystemShortcut(
+ R.drawable.ic_close_option,
+ R.string.recent_task_option_close, container, taskContainer));
+ }
+
+ @Override
+ public boolean showForGroupedTask() {
+ return true;
+ }
+
+ @Override
+ public boolean showForDesktopTask() {
+ return true;
+ }
+ };
}
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 3133907..e47223b 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -123,8 +123,9 @@
int userId = itemInfo.user.getIdentifier();
if (componentName != null) {
for (TaskView taskView : recentsView.getTaskViews()) {
- if (recentsView.isTaskViewVisible(taskView)) {
- Task.TaskKey key = taskView.getFirstTask().key;
+ Task firstTask = taskView.getFirstTask();
+ if (firstTask != null && recentsView.isTaskViewVisible(taskView)) {
+ Task.TaskKey key = firstTask.key;
if (componentName.equals(key.getComponent()) && userId == key.userId) {
return taskView;
}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 51e59ff..2df4a45 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -302,80 +302,65 @@
@BinderThread
@Override
public void onDisplayAddSystemDecorations(int displayId) {
- MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(
- tis -> executeForTaskbarManager(taskbarManager ->
- taskbarManager.onDisplayAddSystemDecorations(displayId))));
+ executeForTaskbarManager(taskbarManager ->
+ taskbarManager.onDisplayAddSystemDecorations(displayId));
}
@BinderThread
@Override
public void onDisplayRemoved(int displayId) {
- MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(
- tis -> executeForTaskbarManager(
- taskbarManager -> taskbarManager.onDisplayRemoved(displayId))));
+ executeForTaskbarManager(taskbarManager ->
+ taskbarManager.onDisplayRemoved(displayId));
}
@BinderThread
@Override
public void onDisplayRemoveSystemDecorations(int displayId) {
- // TODO(b/391786915): Replace all
- // `executeForTouchInteractionService(executeForTaskbarManager())` with just
- // `executeForTaskbarManager` directly (since `tis` is unused).
- MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(
- tis -> executeForTaskbarManager(taskbarManager -> taskbarManager
- .onDisplayRemoveSystemDecorations(displayId))));
+ executeForTaskbarManager(taskbarManager ->
+ taskbarManager.onDisplayRemoveSystemDecorations(displayId));
}
@BinderThread
@Override
public void updateWallpaperVisibility(int displayId, boolean visible) {
- MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(
- tis -> executeForTaskbarManager(
- taskbarManager -> taskbarManager.setWallpaperVisible(displayId,
- visible))));
+ executeForTaskbarManager(taskbarManager ->
+ taskbarManager.setWallpaperVisible(displayId, visible));
}
@BinderThread
@Override
public void checkNavBarModes(int displayId) {
- MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis ->
- executeForTaskbarManager(
- taskbarManager -> taskbarManager.checkNavBarModes(displayId))));
+ executeForTaskbarManager(taskbarManager ->
+ taskbarManager.checkNavBarModes(displayId));
}
@BinderThread
@Override
public void finishBarAnimations(int displayId) {
- MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(
- tis -> executeForTaskbarManager(
- taskbarManager -> taskbarManager.finishBarAnimations(displayId))));
+ executeForTaskbarManager(taskbarManager ->
+ taskbarManager.finishBarAnimations(displayId));
}
@BinderThread
@Override
public void touchAutoDim(int displayId, boolean reset) {
- MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(
- tis -> executeForTaskbarManager(
- taskbarManager -> taskbarManager.touchAutoDim(displayId, reset))));
+ executeForTaskbarManager(taskbarManager ->
+ taskbarManager.touchAutoDim(displayId, reset));
}
@BinderThread
@Override
public void transitionTo(int displayId, @BarTransitions.TransitionMode int barMode,
boolean animate) {
- MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(
- tis -> executeForTaskbarManager(
- taskbarManager -> taskbarManager.transitionTo(displayId, barMode,
- animate))));
+ executeForTaskbarManager(taskbarManager ->
+ taskbarManager.transitionTo(displayId, barMode, animate));
}
@BinderThread
@Override
public void appTransitionPending(boolean pending) {
- MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis ->
- executeForTaskbarManager(
- taskbarManager -> taskbarManager.appTransitionPending(pending))
- ));
+ executeForTaskbarManager(taskbarManager ->
+ taskbarManager.appTransitionPending(pending));
}
@Override
@@ -576,6 +561,8 @@
private DesktopAppLaunchTransitionManager mDesktopAppLaunchTransitionManager;
+ private DisplayController.DisplayInfoChangeListener mDisplayInfoChangeListener;
+
@Override
public void onCreate() {
super.onCreate();
@@ -605,7 +592,8 @@
// Call runOnUserUnlocked() before any other callbacks to ensure everything is initialized.
LockedUserState.get(this).runOnUserUnlocked(mUserUnlockedRunnable);
- mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
+ mDisplayInfoChangeListener =
+ mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
ScreenOnTracker.INSTANCE.get(this).addListener(mScreenOnListener);
}
@@ -757,7 +745,7 @@
mDesktopAppLaunchTransitionManager.unregisterTransitions();
}
mDesktopAppLaunchTransitionManager = null;
-
+ mDeviceState.removeDisplayInfoChangeListener(mDisplayInfoChangeListener);
LockedUserState.get(this).removeOnUserUnlockedRunnable(mUserUnlockedRunnable);
ScreenOnTracker.INSTANCE.get(this).removeListener(mScreenOnListener);
super.onDestroy();
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index 44fdaec..b4b80c5 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -128,8 +128,7 @@
state.getScrimColor(mRecentsViewContainer.asContext()),
config.getInterpolator(ANIM_SCRIM_FADE, LINEAR));
if (isSplitSelectionState(state)) {
- int duration =
- state.getTransitionDuration(mRecentsViewContainer.asContext(), true);
+ int duration = state.getTransitionDuration(mRecentsViewContainer, true);
// TODO (b/246851887): Pass in setter as a NO_ANIM PendingAnimation instead
PendingAnimation pa = new PendingAnimation(duration);
mRecentsView.createSplitSelectInitAnimation(pa, duration);
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 89bd294..f426bf5 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -52,6 +52,7 @@
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.RecentsViewContainer;
+import com.android.quickstep.views.TaskContainer;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
@@ -239,10 +240,10 @@
}
@Override
- public void initiateSplitSelect(TaskView taskView,
+ public void initiateSplitSelect(TaskContainer taskContainer,
@SplitConfigurationOptions.StagePosition int stagePosition,
StatsLogManager.EventEnum splitEvent) {
- super.initiateSplitSelect(taskView, stagePosition, splitEvent);
+ super.initiateSplitSelect(taskContainer, stagePosition, splitEvent);
mContainer.getStateManager().goToState(OVERVIEW_SPLIT_SELECT);
}
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsDragLayer.java b/quickstep/src/com/android/quickstep/fallback/RecentsDragLayer.java
index 7e5afc3..5d4f1db 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsDragLayer.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsDragLayer.java
@@ -21,8 +21,9 @@
import android.util.AttributeSet;
import com.android.launcher3.statemanager.StatefulContainer;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewDismissTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewLaunchTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.TaskViewRecentsTouchContext;
-import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchControllerDeprecated;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.views.BaseDragLayer;
@@ -54,10 +55,18 @@
@Override
public void recreateControllers() {
- mControllers = new TouchController[]{
- enableExpressiveDismissTaskMotion() ? new TaskViewTouchController<>(mContainer,
- mTaskViewRecentsTouchContext) : new TaskViewTouchControllerDeprecated<>(
- mContainer, mTaskViewRecentsTouchContext),
- new FallbackNavBarTouchController(mContainer)};
+ mControllers = enableExpressiveDismissTaskMotion()
+ ? new TouchController[]{
+ new TaskViewLaunchTouchController<>(mContainer,
+ mTaskViewRecentsTouchContext),
+ new TaskViewDismissTouchController<>(mContainer,
+ mTaskViewRecentsTouchContext),
+ new FallbackNavBarTouchController(mContainer)
+ }
+ : new TouchController[]{
+ new TaskViewTouchControllerDeprecated<>(mContainer,
+ mTaskViewRecentsTouchContext),
+ new FallbackNavBarTouchController(mContainer)
+ };
}
}
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsState.java b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
index c2e7536..f27b60c 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsState.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
@@ -27,6 +27,7 @@
import com.android.launcher3.R;
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.util.Themes;
+import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.views.RecentsViewContainer;
/**
@@ -92,7 +93,7 @@
}
@Override
- public int getTransitionDuration(Context context, boolean isToState) {
+ public int getTransitionDuration(ActivityContext context, boolean isToState) {
return 250;
}
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
index 3082dc6..07288d8 100644
--- a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
@@ -390,10 +390,6 @@
return systemUiController
}
- override fun getContext(): Context {
- return this
- }
-
override fun getScrimView(): ScrimView? {
return scrimView
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
index 42e8694..be47df9 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
@@ -24,11 +24,10 @@
import android.graphics.PointF;
import android.view.MotionEvent;
-import com.android.launcher3.BaseActivity;
-import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.GestureState;
import com.android.quickstep.InputConsumer;
import com.android.quickstep.RecentsAnimationDeviceState;
@@ -80,7 +79,7 @@
@Override
public void onSwipeUp(boolean wasFling, PointF finalVelocity) {
startHomeIntentSafely(mContext, mGestureState.getHomeIntent(), null, TAG);
- BaseActivity activity = BaseDraggingActivity.fromContext(mContext);
+ ActivityContext activity = ActivityContext.lookupContext(mContext);
int state = (mGestureState != null && mGestureState.getEndTarget() != null)
? mGestureState.getEndTarget().containerType
: LAUNCHER_STATE_HOME;
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index b2a30ca..594c99a 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -425,6 +425,22 @@
case LAUNCHER_PRIVATE_SPACE_UNLOCK_ANIMATION_END:
InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK);
break;
+ case LAUNCHER_WORK_UTILITY_VIEW_EXPAND_ANIMATION_BEGIN:
+ InteractionJankMonitorWrapper.begin(
+ view,
+ Cuj.CUJ_LAUNCHER_WORK_UTILITY_VIEW_EXPAND);
+ break;
+ case LAUNCHER_WORK_UTILITY_VIEW_EXPAND_ANIMATION_END:
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_WORK_UTILITY_VIEW_EXPAND);
+ break;
+ case LAUNCHER_WORK_UTILITY_VIEW_SHRINK_ANIMATION_BEGIN:
+ InteractionJankMonitorWrapper.begin(
+ view,
+ Cuj.CUJ_LAUNCHER_WORK_UTILITY_VIEW_SHRINK);
+ break;
+ case LAUNCHER_WORK_UTILITY_VIEW_SHRINK_ANIMATION_END:
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_WORK_UTILITY_VIEW_SHRINK);
+ break;
default:
break;
}
diff --git a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
index 88ef0a8..e72ccbf 100644
--- a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
@@ -306,6 +306,10 @@
if (isRtl) SingleAxisSwipeDetector.DIRECTION_NEGATIVE
else SingleAxisSwipeDetector.DIRECTION_POSITIVE
+ override fun getDownDirection(isRtl: Boolean): Int =
+ if (isRtl) SingleAxisSwipeDetector.DIRECTION_POSITIVE
+ else SingleAxisSwipeDetector.DIRECTION_NEGATIVE
+
override fun isGoingUp(displacement: Float, isRtl: Boolean): Boolean =
if (isRtl) displacement < 0 else displacement > 0
diff --git a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
index c0b697d..c1e1c2b 100644
--- a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
+++ b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
@@ -310,6 +310,12 @@
}
@Override
+ public int getDownDirection(boolean isRtl) {
+ // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
+ return SingleAxisSwipeDetector.DIRECTION_NEGATIVE;
+ }
+
+ @Override
public boolean isGoingUp(float displacement, boolean isRtl) {
// Ignore rtl since it only affects X value displacement, Y displacement doesn't change
return displacement < 0;
diff --git a/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt b/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt
index b8d0412..78f9a0a 100644
--- a/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt
@@ -332,6 +332,9 @@
/** @return Given [.getUpDownSwipeDirection], whether POSITIVE or NEGATIVE is up. */
fun getUpDirection(isRtl: Boolean): Int
+ /** @return Given [.getUpDownSwipeDirection], whether POSITIVE or NEGATIVE is down. */
+ fun getDownDirection(isRtl: Boolean): Int
+
/** @return Whether the displacement is going towards the top of the screen. */
fun isGoingUp(displacement: Float, isRtl: Boolean): Boolean
diff --git a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
index bc91911..3fb4f54 100644
--- a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
@@ -351,6 +351,10 @@
if (isRtl) SingleAxisSwipeDetector.DIRECTION_POSITIVE
else SingleAxisSwipeDetector.DIRECTION_NEGATIVE
+ override fun getDownDirection(isRtl: Boolean): Int =
+ if (isRtl) SingleAxisSwipeDetector.DIRECTION_NEGATIVE
+ else SingleAxisSwipeDetector.DIRECTION_POSITIVE
+
override fun isGoingUp(displacement: Float, isRtl: Boolean): Boolean =
if (isRtl) displacement > 0 else displacement < 0
diff --git a/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModel.kt b/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModel.kt
index 4936e30..961446f 100644
--- a/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModel.kt
+++ b/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModel.kt
@@ -68,8 +68,6 @@
)
}
- val tintAmount: Flow<Float> = recentsViewData.tintAmount
-
val state: Flow<TaskTileUiState> =
combine(taskData, isLiveTile) { tasks, isLiveTile -> mapToTaskTile(tasks, isLiveTile) }
.distinctUntilChanged()
diff --git a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt
index 6ccf372..a1f8454 100644
--- a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt
+++ b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt
@@ -20,8 +20,6 @@
// This is far from complete but serves the purpose of enabling refactoring in other areas
class RecentsViewData {
- val fullscreenProgress = MutableStateFlow(1f)
-
// Whether the current RecentsView state supports task overlays.
// TODO(b/331753115): Derive from RecentsView state flow once migrated to MVVM.
val overlayEnabled = MutableStateFlow(false)
@@ -29,9 +27,6 @@
// The settled set of visible taskIds that is updated after RecentsView scroll settles.
val settledFullyVisibleTaskIds = MutableStateFlow(emptySet<Int>())
- // Color tint on foreground scrim
- val tintAmount = MutableStateFlow(0f)
-
val thumbnailSplashProgress = MutableStateFlow(0f)
// A list of taskIds that are associated with a RecentsAnimationController. */
diff --git a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
index cfebb81..73332fc 100644
--- a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
+++ b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
@@ -34,10 +34,6 @@
recentsTasksRepository.setVisibleTasks(visibleTaskIdList.toSet())
}
- fun updateFullscreenProgress(fullscreenProgress: Float) {
- recentsViewData.fullscreenProgress.value = fullscreenProgress
- }
-
fun updateTasksFullyVisible(taskIds: Set<Int>) {
recentsViewData.settledFullyVisibleTaskIds.value = taskIds
}
@@ -46,10 +42,6 @@
recentsViewData.overlayEnabled.value = isOverlayEnabled
}
- fun setTintAmount(tintAmount: Float) {
- recentsViewData.tintAmount.value = tintAmount
- }
-
fun updateThumbnailSplashProgress(taskThumbnailSplashAlpha: Float) {
recentsViewData.thumbnailSplashProgress.value = taskThumbnailSplashAlpha
}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
index 34b0206..28152ec 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
@@ -151,8 +151,8 @@
}
}
- fun setState(state: TaskThumbnailUiState) {
- Log.d(TAG, "viewModelUiState changed from: $uiState to: $state")
+ fun setState(state: TaskThumbnailUiState, taskId: Int? = null) {
+ logDebug("taskId: $taskId - uiState changed from: $uiState to: $state")
if (uiState == state) return
uiState = state
resetViews()
@@ -245,6 +245,10 @@
thumbnailView.imageMatrix = viewModel.getThumbnailPositionState(width, height, isLayoutRtl)
}
+ private fun logDebug(message: String) {
+ Log.d(TAG, "[TaskThumbnailView@${Integer.toHexString(hashCode())}] $message")
+ }
+
private fun maybeCreateHeader() {
if (enableDesktopExplodedView() && taskThumbnailViewHeader == null) {
taskThumbnailViewHeader =
diff --git a/quickstep/src/com/android/quickstep/util/ActiveTrackpadList.kt b/quickstep/src/com/android/quickstep/util/ActiveTrackpadList.kt
index 63bd03d..a1ff0ce 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveTrackpadList.kt
+++ b/quickstep/src/com/android/quickstep/util/ActiveTrackpadList.kt
@@ -30,7 +30,7 @@
init {
inputManager.registerInputDeviceListener(this, Executors.UI_HELPER_EXECUTOR.handler)
- inputManager.inputDeviceIds.forEach { deviceId -> onInputDeviceAdded(deviceId) }
+ inputManager.inputDeviceIds.filter(this::isTrackpadDevice).forEach(this::add)
}
override fun onInputDeviceAdded(deviceId: Int) {
diff --git a/quickstep/src/com/android/quickstep/util/AnimUtils.java b/quickstep/src/com/android/quickstep/util/AnimUtils.java
index 31aca03..fda0c29 100644
--- a/quickstep/src/com/android/quickstep/util/AnimUtils.java
+++ b/quickstep/src/com/android/quickstep/util/AnimUtils.java
@@ -78,8 +78,7 @@
@NonNull SplitAnimationController animationController) {
StateAnimationConfig config = new StateAnimationConfig();
BaseState startState = stateManager.getState();
- long duration = startState.getTransitionDuration(container.asContext(),
- false /*isToState*/);
+ long duration = startState.getTransitionDuration(container, false /*isToState*/);
if (duration == 0) {
// Case where we're in contextual on workspace (NORMAL), which by default has 0
// transition duration
diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java
index 6b8650f..f20d7a5 100644
--- a/quickstep/src/com/android/quickstep/util/AppPairsController.java
+++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java
@@ -128,26 +128,25 @@
& ItemInfoWithIcon.FLAG_NOT_PINNABLE) != 0));
if (!taskView.containsMultipleTasks()
|| hasUnpinnableApp
- || !(taskView instanceof GroupedTaskView)) {
+ || !(taskView instanceof GroupedTaskView groupedTaskView)) {
return false;
}
- GroupedTaskView gtv = (GroupedTaskView) taskView;
- List<TaskContainer> containers = gtv.getTaskContainers();
- ComponentKey taskKey1 = TaskUtils.getLaunchComponentKeyForTask(
- containers.get(0).getTask().key);
- ComponentKey taskKey2 = TaskUtils.getLaunchComponentKeyForTask(
- containers.get(1).getTask().key);
- AppInfo app1 = resolveAppInfoByComponent(taskKey1);
- AppInfo app2 = resolveAppInfoByComponent(taskKey2);
+ ComponentKey leftTopComponentKey = TaskUtils.getLaunchComponentKeyForTask(
+ groupedTaskView.getLeftTopTaskContainer().getTask().key);
+ ComponentKey rightBottomComponentKey = TaskUtils.getLaunchComponentKeyForTask(
+ groupedTaskView.getRightBottomTaskContainer().getTask().key);
+ AppInfo leftTopAppInfo = resolveAppInfoByComponent(leftTopComponentKey);
+ AppInfo rightBottomAppInfo = resolveAppInfoByComponent(rightBottomComponentKey);
- if (app1 == null || app2 == null) {
+ if (leftTopAppInfo == null || rightBottomAppInfo == null) {
// Disallow saving app pairs for apps that don't have a front-door in Launcher
return false;
}
- if (PackageManagerHelper.isSameAppForMultiInstance(app1, app2)) {
- if (!app1.supportsMultiInstance() || !app2.supportsMultiInstance()) {
+ if (PackageManagerHelper.isSameAppForMultiInstance(leftTopAppInfo, rightBottomAppInfo)) {
+ if (!leftTopAppInfo.supportsMultiInstance()
+ || !rightBottomAppInfo.supportsMultiInstance()) {
return false;
}
}
@@ -183,9 +182,8 @@
return;
}
- List<TaskContainer> containers = gtv.getTaskContainers();
List<TaskViewItemInfo> recentsInfos =
- containers.stream().map(TaskContainer::getItemInfo).toList();
+ gtv.getTaskContainers().stream().map(TaskContainer::getItemInfo).toList();
List<WorkspaceItemInfo> apps =
recentsInfos.stream().map(this::resolveAppPairWorkspaceInfo).toList();
diff --git a/quickstep/src/com/android/quickstep/util/DesktopTask.kt b/quickstep/src/com/android/quickstep/util/DesktopTask.kt
index 5463cf7..53ea022 100644
--- a/quickstep/src/com/android/quickstep/util/DesktopTask.kt
+++ b/quickstep/src/com/android/quickstep/util/DesktopTask.kt
@@ -20,16 +20,9 @@
/**
* A [Task] container that can contain N number of tasks that are part of the desktop in recent
- * tasks list.
+ * tasks list. Note that desktops can be empty with no tasks in them.
*/
-class DesktopTask(override val tasks: List<Task>) :
- GroupTask(tasks[0], null, null, TaskViewType.DESKTOP) {
-
- override fun containsTask(taskId: Int) = tasks.any { it.key.id == taskId }
-
- override fun hasMultipleTasks() = tasks.size > 1
-
- override fun supportsMultipleTasks() = true
+class DesktopTask(tasks: List<Task>) : GroupTask(tasks, TaskViewType.DESKTOP) {
override fun copy() = DesktopTask(tasks)
diff --git a/quickstep/src/com/android/quickstep/util/ExternalDisplays.kt b/quickstep/src/com/android/quickstep/util/ExternalDisplays.kt
index e1a8578..455b312 100644
--- a/quickstep/src/com/android/quickstep/util/ExternalDisplays.kt
+++ b/quickstep/src/com/android/quickstep/util/ExternalDisplays.kt
@@ -17,6 +17,7 @@
package com.android.quickstep.util
import android.view.Display.DEFAULT_DISPLAY
+import android.view.Display.INVALID_DISPLAY
import com.android.systemui.shared.recents.model.Task
/** Whether this displayId belongs to an external display */
@@ -25,7 +26,14 @@
/** Returns displayId of this [Task], default to [DEFAULT_DISPLAY] */
val Task?.displayId
- get() = this?.key?.displayId ?: DEFAULT_DISPLAY
+ get() =
+ this?.key?.displayId.let { displayId ->
+ when (displayId) {
+ null -> DEFAULT_DISPLAY
+ INVALID_DISPLAY -> DEFAULT_DISPLAY
+ else -> displayId
+ }
+ }
/** Returns if this task belongs tto [DEFAULT_DISPLAY] */
val Task?.isExternalDisplay
diff --git a/quickstep/src/com/android/quickstep/util/GroupTask.kt b/quickstep/src/com/android/quickstep/util/GroupTask.kt
index d5bbcd3..49c37dc 100644
--- a/quickstep/src/com/android/quickstep/util/GroupTask.kt
+++ b/quickstep/src/com/android/quickstep/util/GroupTask.kt
@@ -15,7 +15,6 @@
*/
package com.android.quickstep.util
-import androidx.annotation.VisibleForTesting
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.quickstep.views.TaskViewType
import com.android.systemui.shared.recents.model.Task
@@ -25,47 +24,26 @@
* An abstract class for creating [Task] containers that can be [SingleTask]s, [SplitTask]s, or
* [DesktopTask]s in the recent tasks list.
*/
-abstract class GroupTask
-@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
-constructor(
- @Deprecated("Prefer using `getTasks()` instead") @JvmField val task1: Task,
- @Deprecated("Prefer using `getTasks()` instead") @JvmField val task2: Task?,
- @JvmField val mSplitBounds: SplitConfigurationOptions.SplitBounds?,
- @JvmField val taskViewType: TaskViewType,
-) {
- protected constructor(
- task1: Task,
- task2: Task?,
- splitBounds: SplitConfigurationOptions.SplitBounds?,
- ) : this(
- task1,
- task2,
- splitBounds,
- if (task2 != null) TaskViewType.GROUPED else TaskViewType.SINGLE,
- )
-
- open fun containsTask(taskId: Int) =
- task1.key.id == taskId || (task2 != null && task2.key.id == taskId)
+abstract class GroupTask(val tasks: List<Task>, @JvmField val taskViewType: TaskViewType) {
+ fun containsTask(taskId: Int) = tasks.any { it.key.id == taskId }
/**
* Returns true if a task in this group has a package name that matches the given `packageName`.
*/
- fun containsPackage(packageName: String) = tasks.any { it.key.packageName == packageName }
+ fun containsPackage(packageName: String?) = tasks.any { it.key.packageName == packageName }
- open fun hasMultipleTasks() = task2 != null
+ /**
+ * Returns true if a task in this group has a package name that matches the given `packageName`,
+ * and its user ID matches the given `userId`.
+ */
+ fun containsPackage(packageName: String?, userId: Int) =
+ tasks.any { it.key.packageName == packageName && it.key.userId == userId }
- /** Returns whether this task supports multiple tasks or not. */
- open fun supportsMultipleTasks() = taskViewType == TaskViewType.GROUPED
-
- /** Returns a List of all the Tasks in this GroupTask */
- open val tasks: List<Task>
- get() = listOfNotNull(task1, task2)
+ fun isEmpty() = tasks.isEmpty()
/** Creates a copy of this instance */
abstract fun copy(): GroupTask
- override fun toString() = "type=$taskViewType task1=$task1 task2=$task2"
-
override fun equals(o: Any?): Boolean {
if (this === o) return true
if (o !is GroupTask) return false
@@ -76,11 +54,14 @@
}
/** A [Task] container that must contain exactly one task in the recent tasks list. */
-class SingleTask(task: Task) :
- GroupTask(task, task2 = null, mSplitBounds = null, TaskViewType.SINGLE) {
- override fun copy() = SingleTask(task1)
+class SingleTask(task: Task) : GroupTask(listOf(task), TaskViewType.SINGLE) {
- override fun toString() = "type=$taskViewType task=$task1"
+ val task: Task
+ get() = tasks[0]
+
+ override fun copy() = SingleTask(task)
+
+ override fun toString() = "type=$taskViewType task=$task"
override fun equals(o: Any?): Boolean {
if (this === o) return true
@@ -93,19 +74,26 @@
* A [Task] container that must contain exactly two tasks and split bounds to represent an app-pair
* in the recent tasks list.
*/
-class SplitTask(task1: Task, task2: Task, splitBounds: SplitConfigurationOptions.SplitBounds) :
- GroupTask(task1, task2, splitBounds, TaskViewType.GROUPED) {
+class SplitTask(task1: Task, task2: Task, val splitBounds: SplitConfigurationOptions.SplitBounds) :
+ GroupTask(listOf(task1, task2), TaskViewType.GROUPED) {
- override fun copy() = SplitTask(task1, task2!!, mSplitBounds!!)
+ val topLeftTask: Task
+ get() = if (splitBounds.leftTopTaskId == tasks[0].key.id) tasks[0] else tasks[1]
- override fun toString() = "type=$taskViewType task1=$task1 task2=$task2"
+ val bottomRightTask: Task
+ get() = if (topLeftTask == tasks[0]) tasks[1] else tasks[0]
+
+ override fun copy() = SplitTask(tasks[0], tasks[1], splitBounds)
+
+ override fun toString() =
+ "type=$taskViewType topLeftTask=$topLeftTask bottomRightTask=$bottomRightTask"
override fun equals(o: Any?): Boolean {
if (this === o) return true
if (o !is SplitTask) return false
- if (mSplitBounds!! != o.mSplitBounds!!) return false
+ if (splitBounds != o.splitBounds) return false
return super.equals(o)
}
- override fun hashCode() = Objects.hash(super.hashCode(), mSplitBounds)
+ override fun hashCode() = Objects.hash(super.hashCode(), splitBounds)
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index 9b4c772..0182969 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -749,8 +749,8 @@
// launcher side animation)
val leftTopApp =
leafRoots.single { change ->
- (isLeftRightSplit && change.endAbsBounds.left == 0) ||
- (!isLeftRightSplit && change.endAbsBounds.top == 0)
+ (isLeftRightSplit && change.endAbsBounds.left <= 0) ||
+ (!isLeftRightSplit && change.endAbsBounds.top <= 0)
}
val dividerPos =
if (isLeftRightSplit) leftTopApp.endAbsBounds.right
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 5f8b4d9..fd8b356 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -71,7 +71,6 @@
import com.android.launcher3.R;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.apppairs.AppPairIcon;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.IconProvider;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.ItemInfo;
@@ -260,7 +259,7 @@
GroupTask groupTask = taskGroups.get(i);
if (isInstanceOfAppPair(
groupTask, componentKeys.get(0), componentKeys.get(1))) {
- lastActiveTasks[0] = groupTask.task1;
+ lastActiveTasks[0] = ((SplitTask) groupTask).getTopLeftTask();
break;
}
}
@@ -314,11 +313,15 @@
*/
public boolean isInstanceOfAppPair(GroupTask groupTask, @NonNull ComponentKey componentKey1,
@NonNull ComponentKey componentKey2) {
- return ((isInstanceOfComponent(groupTask.task1, componentKey1)
- && isInstanceOfComponent(groupTask.task2, componentKey2))
- ||
- (isInstanceOfComponent(groupTask.task1, componentKey2)
- && isInstanceOfComponent(groupTask.task2, componentKey1)));
+ if (groupTask instanceof SplitTask splitTask) {
+ return ((isInstanceOfComponent(splitTask.getTopLeftTask(), componentKey1)
+ && isInstanceOfComponent(splitTask.getBottomRightTask(), componentKey2))
+ ||
+ (isInstanceOfComponent(splitTask.getTopLeftTask(), componentKey2)
+ && isInstanceOfComponent(splitTask.getBottomRightTask(),
+ componentKey1)));
+ }
+ return false;
}
/**
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
index 38ffe50..229c8f5 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
@@ -55,6 +55,12 @@
private val MINIMUM_RATIO_TO_SHOW_ICON = 0.2f
+ val leftTopTaskContainer: TaskContainer
+ get() = taskContainers[0]
+
+ val rightBottomTaskContainer: TaskContainer
+ get() = taskContainers[1]
+
// TODO(b/336612373): Support new TTV for GroupedTaskView
var splitBoundsConfig: SplitConfigurationOptions.SplitBounds? = null
private set
@@ -72,8 +78,8 @@
val splitBoundsConfig = splitBoundsConfig ?: return
val inSplitSelection = getThisTaskCurrentlyInSplitSelection() != INVALID_TASK_ID
pagedOrientationHandler.measureGroupedTaskViewThumbnailBounds(
- taskContainers[0].snapshotView,
- taskContainers[1].snapshotView,
+ leftTopTaskContainer.snapshotView,
+ rightBottomTaskContainer.snapshotView,
widthSize,
heightSize,
splitBoundsConfig,
@@ -165,10 +171,10 @@
val iconMargins = (iconViewMarginStart + iconViewBackgroundMarginStart) * 2
// setMaxWidth() needs to be called before mIconView.setIconOrientation which is
// called in the super below.
- (taskContainers[0].iconView as IconAppChipView).setMaxWidth(
+ (leftTopTaskContainer.iconView as IconAppChipView).setMaxWidth(
groupedTaskViewSizes.first.x - iconMargins
)
- (taskContainers[1].iconView as IconAppChipView).setMaxWidth(
+ (rightBottomTaskContainer.iconView as IconAppChipView).setMaxWidth(
groupedTaskViewSizes.second.x - iconMargins
)
}
@@ -189,16 +195,12 @@
if (deviceProfile.isLeftRightSplit) splitBoundsConfig.leftTaskPercent
else splitBoundsConfig.topTaskPercent
val bottomRightTaskPercent = 1 - topLeftTaskPercent
- taskContainers[0]
- .iconView
- .setFlexSplitAlpha(
- if (topLeftTaskPercent < MINIMUM_RATIO_TO_SHOW_ICON) 0f else 1f
- )
- taskContainers[1]
- .iconView
- .setFlexSplitAlpha(
- if (bottomRightTaskPercent < MINIMUM_RATIO_TO_SHOW_ICON) 0f else 1f
- )
+ leftTopTaskContainer.iconView.setFlexSplitAlpha(
+ if (topLeftTaskPercent < MINIMUM_RATIO_TO_SHOW_ICON) 0f else 1f
+ )
+ rightBottomTaskContainer.iconView.setFlexSplitAlpha(
+ if (bottomRightTaskPercent < MINIMUM_RATIO_TO_SHOW_ICON) 0f else 1f
+ )
}
if (enableOverviewIconMenu()) {
@@ -210,8 +212,8 @@
layoutParams.height,
)
pagedOrientationHandler.setSplitIconParams(
- taskContainers[0].iconView.asView(),
- taskContainers[1].iconView.asView(),
+ leftTopTaskContainer.iconView.asView(),
+ rightBottomTaskContainer.iconView.asView(),
taskIconHeight,
groupedTaskViewSizes.first.x,
groupedTaskViewSizes.first.y,
@@ -224,11 +226,11 @@
)
} else {
pagedOrientationHandler.setSplitIconParams(
- taskContainers[0].iconView.asView(),
- taskContainers[1].iconView.asView(),
+ leftTopTaskContainer.iconView.asView(),
+ rightBottomTaskContainer.iconView.asView(),
taskIconHeight,
- taskContainers[0].snapshotView.measuredWidth,
- taskContainers[0].snapshotView.measuredHeight,
+ leftTopTaskContainer.snapshotView.measuredWidth,
+ leftTopTaskContainer.snapshotView.measuredHeight,
measuredHeight,
measuredWidth,
isRtl,
@@ -288,8 +290,8 @@
recentsView?.let {
it.splitSelectController.launchExistingSplitPair(
if (launchingExistingTaskView) this else null,
- taskContainers[0].task.key.id,
- taskContainers[1].task.key.id,
+ leftTopTaskContainer.task.key.id,
+ rightBottomTaskContainer.task.key.id,
STAGE_POSITION_TOP_OR_LEFT,
callback,
isQuickSwitch,
@@ -319,14 +321,14 @@
// checks below aren't reliable since both of those views may be gone/transformed
val initSplitTaskId = getThisTaskCurrentlyInSplitSelection()
if (initSplitTaskId != INVALID_TASK_ID) {
- return if (initSplitTaskId == taskContainers[0].task.key.id) 1 else 0
+ return if (initSplitTaskId == leftTopTaskContainer.task.key.id) 1 else 0
}
}
// Check which of the two apps was selected
if (
- taskContainers[1].iconView.asView().containsPoint(lastTouchDownPosition) ||
- taskContainers[1].snapshotView.containsPoint(lastTouchDownPosition)
+ rightBottomTaskContainer.iconView.asView().containsPoint(lastTouchDownPosition) ||
+ rightBottomTaskContainer.snapshotView.containsPoint(lastTouchDownPosition)
) {
return 1
}
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 9be462c..c6bd677 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -36,7 +36,6 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.desktop.DesktopRecentsTransitionController;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.statehandlers.DepthController;
@@ -127,9 +126,11 @@
// If Launcher needs to return to split select state, do it now, after the icon has updated.
if (mContainer.hasPendingSplitSelectInfo()) {
PendingSplitSelectInfo recoveryData = mContainer.getPendingSplitSelectInfo();
- if (recoveryData.getStagedTaskId() == taskId) {
+ TaskContainer taskContainer;
+ if (recoveryData != null && recoveryData.getStagedTaskId() == taskId && (taskContainer =
+ mUtils.getTaskContainerById(taskId)) != null) {
initiateSplitSelect(
- getTaskViewByTaskId(recoveryData.getStagedTaskId()),
+ taskContainer,
recoveryData.getStagePosition(), recoveryData.getSource()
);
mContainer.finishSplitSelectRecovery();
@@ -240,10 +241,10 @@
}
@Override
- public void initiateSplitSelect(TaskView taskView,
+ public void initiateSplitSelect(TaskContainer taskContainer,
@SplitConfigurationOptions.StagePosition int stagePosition,
StatsLogManager.EventEnum splitEvent) {
- super.initiateSplitSelect(taskView, stagePosition, splitEvent);
+ super.initiateSplitSelect(taskContainer, stagePosition, splitEvent);
getStateManager().goToState(LauncherState.OVERVIEW_SPLIT_SELECT);
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 0270219..9d3b23a 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -137,6 +137,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.core.graphics.ColorUtils;
+import androidx.dynamicanimation.animation.SpringAnimation;
import com.android.internal.jank.Cuj;
import com.android.launcher3.AbstractFloatingView;
@@ -166,6 +167,7 @@
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.touch.OverScroll;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.util.CancellableTask;
import com.android.launcher3.util.DynamicResource;
import com.android.launcher3.util.IntArray;
@@ -214,9 +216,11 @@
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.RecentsAtomicAnimationFactory;
import com.android.quickstep.util.RecentsOrientedState;
+import com.android.quickstep.util.SingleTask;
import com.android.quickstep.util.SplitAnimationController.Companion.SplitAnimInitProps;
import com.android.quickstep.util.SplitAnimationTimings;
import com.android.quickstep.util.SplitSelectStateController;
+import com.android.quickstep.util.SplitTask;
import com.android.quickstep.util.SurfaceTransaction;
import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.quickstep.util.TaskGridNavHelper;
@@ -238,6 +242,7 @@
import kotlin.Unit;
import kotlin.collections.CollectionsKt;
+import kotlin.jvm.functions.Function0;
import kotlinx.coroutines.CoroutineScope;
@@ -653,13 +658,13 @@
return;
}
- TaskView taskView = getTaskViewByTaskId(taskId);
- if (taskView == null) {
- Log.d(TAG, "onTaskRemoved: " + taskId + ", no associated TaskView");
+ TaskContainer taskContainer = mUtils.getTaskContainerById(taskId);
+ if (taskContainer == null) {
+ Log.d(TAG, "onTaskRemoved: " + taskId + ", no associated Task");
return;
}
Log.d(TAG, "onTaskRemoved: " + taskId);
- Task.TaskKey taskKey = taskView.getFirstTask().key;
+ Task.TaskKey taskKey = taskContainer.getTask().key;
UI_HELPER_EXECUTOR.execute(new CancellableTask<>(
() -> PackageManagerWrapper.getInstance()
.getActivityInfo(taskKey.getComponent(), taskKey.userId) == null,
@@ -848,7 +853,7 @@
private final RecentsViewModel mRecentsViewModel;
private final RecentsViewModelHelper mHelper;
- private final RecentsViewUtils mUtils = new RecentsViewUtils(this);
+ protected final RecentsViewUtils mUtils = new RecentsViewUtils(this);
private final Matrix mTmpMatrix = new Matrix();
@@ -1118,7 +1123,7 @@
TaskView taskView = getTaskViewByTaskId(taskId);
if (taskView != null) {
for (TaskContainer container : taskView.getTaskContainers()) {
- if (container == null || taskId != container.getTask().key.id) {
+ if (taskId != container.getTask().key.id) {
continue;
}
container.getThumbnailViewDeprecated().setThumbnail(container.getTask(),
@@ -1133,9 +1138,10 @@
@Override
public void onTaskIconChanged(@NonNull String pkg, @NonNull UserHandle user) {
for (TaskView taskView : getTaskViews()) {
- Task task = taskView.getFirstTask();
- if (pkg.equals(task.key.getPackageName()) && task.key.userId == user.getIdentifier()) {
- task.icon = null;
+ Task firstTask = taskView.getFirstTask();
+ if (firstTask != null && pkg.equals(firstTask.key.getPackageName())
+ && firstTask.key.userId == user.getIdentifier()) {
+ firstTask.icon = null;
if (taskView.getTaskContainers().stream().anyMatch(
container -> container.getIconView().getDrawable() != null)) {
taskView.onTaskListVisibilityChanged(true /* visible */);
@@ -1952,7 +1958,7 @@
GroupTask groupTask = taskGroups.get(i);
boolean containsStagedTask = stagedTaskIdToBeRemoved != INVALID_TASK_ID
&& groupTask.containsTask(stagedTaskIdToBeRemoved);
- boolean shouldSkipGroupTask = containsStagedTask && !groupTask.hasMultipleTasks();
+ boolean shouldSkipGroupTask = containsStagedTask && groupTask instanceof SingleTask;
if ((isSplitSelectionActive() && groupTask.taskViewType == TaskViewType.DESKTOP)
|| shouldSkipGroupTask) {
@@ -1966,25 +1972,27 @@
// to be a temporary container for the remaining task.
TaskView taskView = getTaskViewFromPool(
containsStagedTask ? TaskViewType.SINGLE : groupTask.taskViewType);
- if (taskView instanceof GroupedTaskView) {
- boolean firstTaskIsLeftTopTask =
- groupTask.mSplitBounds.leftTopTaskId == groupTask.task1.key.id;
- Task leftTopTask = firstTaskIsLeftTopTask ? groupTask.task1 : groupTask.task2;
- Task rightBottomTask = firstTaskIsLeftTopTask ? groupTask.task2 : groupTask.task1;
- ((GroupedTaskView) taskView).bind(leftTopTask, rightBottomTask, mOrientationState,
- mTaskOverlayFactory, groupTask.mSplitBounds);
- } else if (taskView instanceof DesktopTaskView) {
+ if (taskView instanceof GroupedTaskView groupedTaskView) {
+ var splitTask = (SplitTask) groupTask;
+ groupedTaskView.bind(splitTask.getTopLeftTask(),
+ splitTask.getBottomRightTask(), mOrientationState,
+ mTaskOverlayFactory, splitTask.getSplitBounds());
+ } else if (taskView instanceof DesktopTaskView desktopTaskView) {
// Minimized tasks should not be shown in Overview
List<Task> nonMinimizedTasks =
groupTask.getTasks().stream()
.filter(task -> !task.isMinimized)
.toList();
- ((DesktopTaskView) taskView).bind(nonMinimizedTasks, mOrientationState,
+ desktopTaskView.bind(nonMinimizedTasks, mOrientationState,
mTaskOverlayFactory);
- } else {
- Task task = groupTask.task1.key.id == stagedTaskIdToBeRemoved ? groupTask.task2
- : groupTask.task1;
+ } else if (groupTask instanceof SplitTask splitTask) {
+ Task task = splitTask.getTopLeftTask().key.id == stagedTaskIdToBeRemoved
+ ? splitTask.getBottomRightTask()
+ : splitTask.getTopLeftTask();
taskView.bind(task, mOrientationState, mTaskOverlayFactory);
+ } else {
+ taskView.bind(((SingleTask) groupTask).getTask(), mOrientationState,
+ mTaskOverlayFactory);
}
addView(taskView);
@@ -2182,9 +2190,6 @@
public void setFullscreenProgress(float fullscreenProgress) {
mFullscreenProgress = fullscreenProgress;
- if (enableRefactorTaskThumbnail()) {
- mRecentsViewModel.updateFullscreenProgress(mFullscreenProgress);
- }
for (TaskView taskView : getTaskViews()) {
taskView.setFullscreenProgress(mFullscreenProgress);
}
@@ -4106,7 +4111,7 @@
removeTaskInternal(dismissedTaskView);
}
mContainer.getStatsLogManager().logger()
- .withItemInfo(dismissedTaskView.getFirstItemInfo())
+ .withItemInfo(dismissedTaskView.getItemInfo())
.log(LAUNCHER_TASK_DISMISS_SWIPE_UP);
}
@@ -5192,18 +5197,20 @@
* Primarily used by overview actions to initiate split from focused task, logs the source
* of split invocation as such.
*/
- public void initiateSplitSelect(TaskView taskView) {
+ public void initiateSplitSelect(TaskContainer taskContainer) {
int defaultSplitPosition = getPagedOrientationHandler()
.getDefaultSplitPosition(mContainer.getDeviceProfile());
- initiateSplitSelect(taskView, defaultSplitPosition, LAUNCHER_OVERVIEW_ACTIONS_SPLIT);
+ initiateSplitSelect(taskContainer, defaultSplitPosition, LAUNCHER_OVERVIEW_ACTIONS_SPLIT);
}
/** TODO(b/266477929): Consolidate this call w/ the one below */
- public void initiateSplitSelect(TaskView taskView, @StagePosition int stagePosition,
+ public void initiateSplitSelect(TaskContainer taskContainer,
+ @StagePosition int stagePosition,
StatsLogManager.EventEnum splitEvent) {
+ TaskView taskView = taskContainer.getTaskView();
mSplitHiddenTaskView = taskView;
mSplitSelectStateController.setInitialTaskSelect(null /*intent*/, stagePosition,
- taskView.getFirstItemInfo(), splitEvent, taskView.getFirstTask().key.id);
+ taskContainer.getItemInfo(), splitEvent, taskContainer.getTask().key.id);
mSplitSelectStateController.setAnimateCurrentTaskDismissal(
true /*animateCurrentTaskDismissal*/);
mSplitHiddenTaskViewIndex = indexOfChild(taskView);
@@ -5294,15 +5301,16 @@
boolean isInitiatingTaskViewSplitPair =
mSplitSelectStateController.isDismissingFromSplitPair();
if (isInitiatingSplitFromTaskView && isInitiatingTaskViewSplitPair
- && mSplitHiddenTaskView instanceof GroupedTaskView) {
+ && mSplitHiddenTaskView instanceof GroupedTaskView groupedTaskView) {
// Splitting from Overview for split pair task
createInitialSplitSelectAnimation(builder);
// Animate pair thumbnail into full thumbnail
- boolean primaryTaskSelected = mSplitHiddenTaskView.getTaskIds()[0]
+ boolean primaryTaskSelected = groupedTaskView.getLeftTopTaskContainer().getTask().key.id
== mSplitSelectStateController.getInitialTaskId();
- TaskContainer taskContainer = mSplitHiddenTaskView
- .getTaskContainers().get(primaryTaskSelected ? 1 : 0);
+ TaskContainer taskContainer =
+ primaryTaskSelected ? groupedTaskView.getRightBottomTaskContainer()
+ : groupedTaskView.getLeftTopTaskContainer();
mSplitSelectStateController.getSplitAnimationController()
.addInitialSplitFromPair(taskContainer, builder,
mContainer.getDeviceProfile(),
@@ -5715,6 +5723,13 @@
mTempRect, mContainer.getDeviceProfile(), mTempPointF);
}
+ /**
+ * Clears the existing PendingAnimation.
+ */
+ public void clearPendingAnimation() {
+ mPendingAnimation = null;
+ }
+
public PendingAnimation createTaskLaunchAnimation(
TaskView taskView, long duration, Interpolator interpolator) {
if (FeatureFlags.IS_STUDIO_BUILD && mPendingAnimation != null) {
@@ -5788,7 +5803,7 @@
} else {
taskView.launchWithoutAnimation(this::onTaskLaunchAnimationEnd);
}
- mContainer.getStatsLogManager().logger().withItemInfo(taskView.getFirstItemInfo())
+ mContainer.getStatsLogManager().logger().withItemInfo(taskView.getItemInfo())
.log(LAUNCHER_TASK_LAUNCH_SWIPE_DOWN);
} else {
onTaskLaunchAnimationEnd(false);
@@ -5872,6 +5887,10 @@
mEnableDrawingLiveTile = enableDrawingLiveTile;
}
+ public boolean getEnableDrawingLiveTile() {
+ return mEnableDrawingLiveTile;
+ }
+
public void redrawLiveTile() {
runActionOnRemoteHandles(remoteTargetHandle -> {
TransformParams params = remoteTargetHandle.getTransformParams();
@@ -6564,10 +6583,6 @@
private void setColorTint(float tintAmount) {
mColorTint = tintAmount;
- if (enableRefactorTaskThumbnail()) {
- mRecentsViewModel.setTintAmount(tintAmount);
- }
-
for (TaskView taskView : getTaskViews()) {
taskView.setColorTint(mColorTint, mTintingColor);
}
@@ -6905,6 +6920,19 @@
return Typeface.Builder.NORMAL_WEIGHT;
}
+ /**
+ * Creates the spring animations which run as a task settles back into its place in overview.
+ *
+ * <p>When a task dismiss is cancelled, the task will return to its original position via a
+ * spring animation.
+ */
+ public SpringAnimation createTaskDismissSettlingSpringAnimation(TaskView draggedTaskView,
+ float velocity, boolean isDismissing, SingleAxisSwipeDetector detector,
+ int dismissLength, Function0<Unit> onEndRunnable) {
+ return mUtils.createTaskDismissSettlingSpringAnimation(draggedTaskView, velocity,
+ isDismissing, detector, dismissLength, onEndRunnable);
+ }
+
public interface TaskLaunchListener {
void onTaskLaunched();
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java b/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
index b1a4808..e61d402 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
@@ -63,19 +63,6 @@
<T extends View> T getOverviewPanel();
/**
- * Returns the RootView
- */
- View getRootView();
-
- /**
- * Dispatches a generic motion event to the view hierarchy.
- * Returns the current RecentsViewContainer as context
- */
- default Context asContext() {
- return (Context) this;
- }
-
- /**
* @see Window.Callback#dispatchGenericMotionEvent(MotionEvent)
*/
boolean dispatchGenericMotionEvent(MotionEvent ev);
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
index bce5a5e..f610335 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
@@ -19,14 +19,21 @@
import android.graphics.Rect
import android.view.View
import androidx.core.view.children
+import androidx.dynamicanimation.animation.FloatPropertyCompat
+import androidx.dynamicanimation.animation.SpringAnimation
+import androidx.dynamicanimation.animation.SpringForce
import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
import com.android.launcher3.Flags.enableSeparateExternalDisplayTasks
+import com.android.launcher3.R
+import com.android.launcher3.touch.SingleAxisSwipeDetector
+import com.android.launcher3.util.DynamicResource
import com.android.launcher3.util.IntArray
import com.android.quickstep.util.GroupTask
import com.android.quickstep.util.isExternalDisplay
import com.android.quickstep.views.RecentsView.RUNNING_TASK_ATTACH_ALPHA
import com.android.systemui.shared.recents.model.ThumbnailData
import java.util.function.BiConsumer
+import kotlin.math.abs
/**
* Helper class for [RecentsView]. This util class contains refactored and extracted functions from
@@ -224,6 +231,9 @@
/** Returns true if there are at least one TaskView has been added to the RecentsView. */
fun hasTaskViews() = taskViews.any()
+ fun getTaskContainerById(taskId: Int) =
+ taskViews.firstNotNullOfOrNull { it.getTaskContainerById(taskId) }
+
private fun getRowRect(firstView: View?, lastView: View?, outRowRect: Rect) {
outRowRect.setEmpty()
firstView?.let {
@@ -291,6 +301,58 @@
}
}
+ /**
+ * Creates the spring animations which run when a dragged task view in overview is released.
+ *
+ * <p>When a task dismiss is cancelled, the task will return to its original position via a
+ * spring animation.
+ */
+ fun createTaskDismissSettlingSpringAnimation(
+ draggedTaskView: TaskView?,
+ velocity: Float,
+ isDismissing: Boolean,
+ detector: SingleAxisSwipeDetector,
+ dismissLength: Int,
+ onEndRunnable: () -> Unit,
+ ): SpringAnimation? {
+ draggedTaskView ?: return null
+ val taskDismissFloatProperty =
+ FloatPropertyCompat.createFloatPropertyCompat(
+ draggedTaskView.secondaryDismissTranslationProperty
+ )
+ val rp = DynamicResource.provider(recentsView.mContainer)
+ return SpringAnimation(draggedTaskView, taskDismissFloatProperty)
+ .setSpring(
+ SpringForce()
+ .setDampingRatio(rp.getFloat(R.dimen.dismiss_task_trans_y_damping_ratio))
+ .setStiffness(rp.getFloat(R.dimen.dismiss_task_trans_y_stiffness))
+ )
+ .setStartVelocity(if (detector.isFling(velocity)) velocity else 0f)
+ .addUpdateListener { animation, value, _ ->
+ if (isDismissing && abs(value) >= abs(dismissLength)) {
+ // TODO(b/393553524): Remove 0 alpha, instead animate task fully off screen.
+ draggedTaskView.alpha = 0f
+ animation.cancel()
+ } else if (draggedTaskView.isRunningTask && recentsView.enableDrawingLiveTile) {
+ recentsView.runActionOnRemoteHandles { remoteTargetHandle ->
+ remoteTargetHandle.taskViewSimulator.taskSecondaryTranslation.value =
+ taskDismissFloatProperty.getValue(draggedTaskView)
+ }
+ recentsView.redrawLiveTile()
+ }
+ }
+ .addEndListener { _, _, _, _ ->
+ if (isDismissing) {
+ recentsView.dismissTask(
+ draggedTaskView,
+ /* animateTaskView = */ false,
+ /* removeTask = */ true,
+ )
+ }
+ onEndRunnable()
+ }
+ }
+
companion object {
val TEMP_RECT = Rect()
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
index a9e84ef..b6f6bed 100644
--- a/quickstep/src/com/android/quickstep/views/TaskContainer.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
@@ -106,7 +106,7 @@
/** Builds proto for logging */
val itemInfo: TaskViewItemInfo
- get() = TaskViewItemInfo(this)
+ get() = TaskViewItemInfo(taskView, this)
fun bind() {
digitalWellBeingToast?.bind(task, taskView, snapshotView, stagePosition)
@@ -153,7 +153,10 @@
}
fun setState(state: TaskData?, liveTile: Boolean, hasHeader: Boolean) {
- thumbnailView.setState(TaskUiStateMapper.toTaskThumbnailUiState(state, liveTile, hasHeader))
+ thumbnailView.setState(
+ TaskUiStateMapper.toTaskThumbnailUiState(state, liveTile, hasHeader),
+ state?.taskId,
+ )
splitAnimationThumbnail =
if (state is TaskData.Data) state.thumbnailData?.thumbnail else null
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 20a385f..4b1b8dc 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -37,7 +37,6 @@
import android.view.ViewGroup
import android.view.ViewStub
import android.view.accessibility.AccessibilityNodeInfo
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
import android.widget.FrameLayout
import android.widget.Toast
import androidx.annotation.IntDef
@@ -56,6 +55,7 @@
import com.android.launcher3.anim.AnimatedFloat
import com.android.launcher3.logging.StatsLogManager.LauncherEvent
import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.TaskViewItemInfo
import com.android.launcher3.testing.TestLogging
import com.android.launcher3.testing.shared.TestProtocol
import com.android.launcher3.util.CancellableTask
@@ -64,9 +64,7 @@
import com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE
import com.android.launcher3.util.MultiValueAlpha
import com.android.launcher3.util.RunnableList
-import com.android.launcher3.util.SplitConfigurationOptions
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
-import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption
import com.android.launcher3.util.SplitConfigurationOptions.StagePosition
import com.android.launcher3.util.TraceHelper
import com.android.launcher3.util.TransformingTouchDelegate
@@ -102,8 +100,6 @@
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
/** A task in the Recents view. */
@@ -163,14 +159,24 @@
val pagedOrientationHandler: RecentsPagedOrientationHandler
get() = orientedState.orientationHandler
- @get:Deprecated("Use [taskContainers] instead.")
- val firstTask: Task
- /** Returns the first task bound to this TaskView. */
- get() = taskContainers[0].task
+ val firstTaskContainer: TaskContainer?
+ get() = taskContainers.firstOrNull()
- @get:Deprecated("Use [taskContainers] instead.")
- val firstItemInfo: ItemInfo
- get() = taskContainers[0].itemInfo
+ val firstTask: Task?
+ /** Returns the first task bound to this TaskView. */
+ get() = firstTaskContainer?.task
+
+ val firstItemInfo: ItemInfo?
+ get() = firstTaskContainer?.itemInfo
+
+ /**
+ * A [TaskViewItemInfo] of this TaskView. The [firstTaskContainer] will be used to get some
+ * specific information like user, title etc of the Task. However, these task specific
+ * information will be skipped if the TaskView has no [taskContainers]. Note, please use
+ * [TaskContainer.itemInfo] for [TaskViewItemInfo] on a specific [TaskContainer].
+ */
+ val itemInfo: TaskViewItemInfo
+ get() = TaskViewItemInfo(this, firstTaskContainer)
protected val container: RecentsViewContainer =
RecentsViewContainer.containerFromContext(context)
@@ -216,7 +222,7 @@
get() =
pagedOrientationHandler.getPrimaryValue(DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y)
- protected val secondaryDismissTranslationProperty: FloatProperty<TaskView>
+ val secondaryDismissTranslationProperty: FloatProperty<TaskView>
get() =
pagedOrientationHandler.getSecondaryValue(DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y)
@@ -671,13 +677,6 @@
val shouldPopulateAccessibilityMenu =
modalness == 0f && recentsView?.isSplitSelectionActive == false
if (shouldPopulateAccessibilityMenu) {
- addAction(
- AccessibilityAction(
- R.id.action_close,
- context.getText(R.string.accessibility_close),
- )
- )
-
taskContainers.forEach {
TraceHelper.allowIpcs("TV.a11yInfo") {
TaskOverlayFactory.getEnabledShortcuts(this@TaskView, it).forEach { shortcut
@@ -708,11 +707,6 @@
override fun performAccessibilityAction(action: Int, arguments: Bundle?): Boolean {
// TODO(b/343708271): Add support for multiple tasks per action.
- if (action == R.id.action_close) {
- recentsView?.dismissTask(this, true /*animateTaskView*/, true /*removeTask*/)
- return true
- }
-
taskContainers.forEach {
if (it.digitalWellBeingToast?.handleAccessibilityAction(action) == true) {
return true
@@ -758,17 +752,10 @@
// onRecycle. So it should be initialized at this point. TaskView Lifecycle:
// `bind` -> `onBind` -> onAttachedToWindow() -> onDetachFromWindow -> onRecycle
coroutineJobs +=
- viewModel!!.tintAmount.onEach(::updateTintAmount).launchIn(coroutineScope)
-
- coroutineJobs +=
coroutineScope.launch { viewModel!!.state.collectLatest(::updateTaskViewState) }
}
}
- private fun updateTintAmount(amount: Float) {
- taskContainers.forEach { it.updateTintAmount(amount) }
- }
-
private fun updateTaskViewState(state: TaskTileUiState) {
sysUiStatusNavFlags = state.sysUiStatusNavFlags
@@ -942,7 +929,7 @@
protected open fun updateThumbnailSize() {
// TODO(b/271468547), we should default to setting translations only on the snapshot instead
// of a hybrid of both margins and translations
- taskContainers[0].snapshotView.updateLayoutParams<LayoutParams> {
+ firstTaskContainer?.snapshotView?.updateLayoutParams<LayoutParams> {
topMargin = container.deviceProfile.overviewTaskThumbnailTopMarginPx
}
taskContainers.forEach { it.digitalWellBeingToast?.setupLayout() }
@@ -1108,7 +1095,7 @@
Log.d("b/310064698", "${taskIds.contentToString()} - onClick - callbackList: $callbackList")
container.statsLogManager
.logger()
- .withItemInfo(firstItemInfo)
+ .withItemInfo(itemInfo)
.log(LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP)
}
@@ -1212,6 +1199,7 @@
* @return CompletionStage to indicate the animation completion or null if the launch failed.
*/
open fun launchAsStaticTile(): RunnableList? {
+ val firstTaskContainer = firstTaskContainer ?: return null
TestLogging.recordEvent(
TestProtocol.SEQUENCE_MAIN,
"startActivityFromRecentsAsync",
@@ -1223,7 +1211,7 @@
}
if (
ActivityManagerWrapper.getInstance()
- .startActivityFromRecents(taskContainers[0].task.key, opts.options)
+ .startActivityFromRecents(firstTaskContainer.task.key, opts.options)
) {
Log.d(
TAG,
@@ -1262,18 +1250,18 @@
isQuickSwitch: Boolean = false,
callback: (launched: Boolean) -> Unit,
) {
+ val firstTaskContainer = firstTaskContainer ?: return
TestLogging.recordEvent(
TestProtocol.SEQUENCE_MAIN,
"startActivityFromRecentsAsync",
taskIds.contentToString(),
)
- val firstContainer = taskContainers[0]
val failureListener = TaskRemovedDuringLaunchListener(context.applicationContext)
if (isQuickSwitch) {
// We only listen for failures to launch in quickswitch because the during this
// gesture launcher is in the background state, vs other launches which are in
// the actual overview state
- failureListener.register(container, firstContainer.task.key.id) {
+ failureListener.register(container, firstTaskContainer.task.key.id) {
notifyTaskLaunchFailed("launchWithoutAnimation")
recentsView?.let {
// Disable animations for now, as it is an edge case and the app usually
@@ -1305,12 +1293,12 @@
if (isQuickSwitch) {
setFreezeRecentTasksReordering()
}
- disableStartingWindow = firstContainer.shouldShowSplashView
+ disableStartingWindow = firstTaskContainer.shouldShowSplashView
}
Executors.UI_HELPER_EXECUTOR.execute {
if (
!ActivityManagerWrapper.getInstance()
- .startActivityFromRecents(firstContainer.task.key, opts)
+ .startActivityFromRecents(firstTaskContainer.task.key, opts)
) {
// If the call to start activity failed, then post the result immediately,
// otherwise, wait for the animation start callback from the activity options
@@ -1337,14 +1325,6 @@
Toast.makeText(context, R.string.activity_not_available, Toast.LENGTH_SHORT).show()
}
- fun initiateSplitSelect(splitPositionOption: SplitPositionOption) {
- recentsView?.initiateSplitSelect(
- this,
- splitPositionOption.stagePosition,
- SplitConfigurationOptions.getLogEventForPosition(splitPositionOption.stagePosition),
- )
- }
-
/**
* Returns `true` if user is already in split select mode and this tap was to choose the second
* app. `false` otherwise
@@ -1550,7 +1530,9 @@
/** Set a color tint on the snapshot and supporting views. */
open fun setColorTint(amount: Float, tintColor: Int) {
taskContainers.forEach {
- if (!enableRefactorTaskThumbnail()) {
+ if (enableRefactorTaskThumbnail()) {
+ it.updateTintAmount(amount)
+ } else {
it.thumbnailViewDeprecated.dimAlpha = amount
}
it.iconView.setIconColorTint(tintColor, amount)
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
index be71640..de0da64 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
@@ -18,6 +18,7 @@
import android.content.ComponentName
import android.content.Intent
+import android.os.Process
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.Flags.enableRefactorTaskThumbnail
@@ -33,7 +34,6 @@
import com.android.launcher3.util.UserIconInfo
import com.android.quickstep.TaskOverlayFactory
import com.android.quickstep.TaskOverlayFactory.TaskOverlay
-import com.android.quickstep.TestComponent
import com.android.quickstep.recents.di.RecentsDependencies
import com.android.quickstep.task.thumbnail.TaskThumbnailView
import com.android.quickstep.views.RecentsView
@@ -84,7 +84,7 @@
whenever(taskView.type).thenReturn(TaskViewType.SINGLE)
whenever(taskView.taskContainers).thenReturn(taskContainers)
- val taskViewItemInfo = TaskViewItemInfo(taskContainers[0])
+ val taskViewItemInfo = TaskViewItemInfo(taskContainers[0].taskView, taskContainers[0])
assertThat(taskViewItemInfo.taskViewAtom)
.isEqualTo(
@@ -105,7 +105,7 @@
whenever(taskView.type).thenReturn(TaskViewType.GROUPED)
whenever(taskView.taskContainers).thenReturn(taskContainers)
- val taskViewItemInfo = TaskViewItemInfo(taskContainers[0])
+ val taskViewItemInfo = TaskViewItemInfo(taskContainers[0].taskView, taskContainers[0])
assertThat(taskViewItemInfo.taskViewAtom)
.isEqualTo(
@@ -130,7 +130,7 @@
whenever(taskView.type).thenReturn(TaskViewType.DESKTOP)
whenever(taskView.taskContainers).thenReturn(taskContainers)
- val taskViewItemInfo = TaskViewItemInfo(taskContainers[0])
+ val taskViewItemInfo = TaskViewItemInfo(taskContainers[0].taskView, taskContainers[0])
assertThat(taskViewItemInfo.taskViewAtom)
.isEqualTo(
@@ -151,7 +151,7 @@
whenever(taskView.taskContainers).thenReturn(taskContainers)
whenever(userInfo.isPrivate).thenReturn(true)
- val taskViewItemInfo = TaskViewItemInfo(taskContainers[0])
+ val taskViewItemInfo = TaskViewItemInfo(taskContainers[0].taskView, taskContainers[0])
assertThat(taskViewItemInfo.taskViewAtom)
.isEqualTo(
@@ -166,6 +166,25 @@
.isEqualTo(FLAG_NOT_PINNABLE)
}
+ @Test
+ fun emptyDesktopTask() {
+ whenever(taskView.type).thenReturn(TaskViewType.DESKTOP)
+
+ val taskViewItemInfo = TaskViewItemInfo(taskView = taskView, taskContainer = null)
+
+ assertThat(taskViewItemInfo.taskViewAtom)
+ .isEqualTo(
+ createTaskViewAtom(
+ type = 2,
+ index = TASK_VIEW_INDEX,
+ componentName = "",
+ cardinality = 0,
+ )
+ )
+ assertThat(taskViewItemInfo.user).isEqualTo(Process.myUserHandle())
+ assertThat(taskViewItemInfo.intent).isNotNull()
+ }
+
private fun createTask(id: Int) =
Task(TaskKey(id, 0, Intent(), ComponentName(PACKAGE, CLASS), 0, 2000))
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt
index e150568..90c9553 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt
@@ -125,7 +125,8 @@
// Needs to be set on window context instead of sandbox context, because it does
// does not propagate between them. However, this change will impact created
// TaskbarActivityContext instances, since they wrap the window context.
- taskbarManager.windowContext.resources.configuration.setLayoutDirection(
+ // TODO: iterate through all window contexts and do this.
+ taskbarManager.primaryWindowContext.resources.configuration.setLayoutDirection(
RTL_LOCALE
)
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetCategoryFilterTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetCategoryFilterTest.kt
new file mode 100644
index 0000000..9b0a95a
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetCategoryFilterTest.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.picker
+
+import android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN
+import android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD
+import android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_NOT_KEYGUARD
+import android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class WidgetCategoryFilterTest {
+
+ @Test
+ fun filterValueZero_everythingMatches() {
+ val noFilter = WidgetCategoryFilter(categoryMask = 0)
+
+ noFilter.assertMatches(WIDGET_CATEGORY_HOME_SCREEN)
+ noFilter.assertMatches(WIDGET_CATEGORY_KEYGUARD)
+ noFilter.assertMatches(WIDGET_CATEGORY_NOT_KEYGUARD)
+ noFilter.assertMatches(WIDGET_CATEGORY_SEARCHBOX)
+ noFilter.assertMatches(WIDGET_CATEGORY_HOME_SCREEN or WIDGET_CATEGORY_KEYGUARD)
+ noFilter.assertMatches(WIDGET_CATEGORY_HOME_SCREEN or WIDGET_CATEGORY_NOT_KEYGUARD)
+ noFilter.assertMatches(
+ WIDGET_CATEGORY_HOME_SCREEN or WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_NOT_KEYGUARD
+ )
+ noFilter.assertMatches(
+ WIDGET_CATEGORY_HOME_SCREEN or WIDGET_CATEGORY_KEYGUARD or WIDGET_CATEGORY_NOT_KEYGUARD
+ )
+ noFilter.assertMatches(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_KEYGUARD)
+ }
+
+ @Test
+ fun includeHomeScreen_matchesOnlyIfHomeScreenExists() {
+ val filter = WidgetCategoryFilter(WIDGET_CATEGORY_HOME_SCREEN)
+
+ filter.assertMatches(WIDGET_CATEGORY_HOME_SCREEN)
+ filter.assertMatches(WIDGET_CATEGORY_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN)
+ filter.assertMatches(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_HOME_SCREEN)
+ filter.assertMatches(WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN)
+
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_KEYGUARD)
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_NOT_KEYGUARD)
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_SEARCHBOX)
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_KEYGUARD or WIDGET_CATEGORY_SEARCHBOX)
+ }
+
+ @Test
+ fun includeHomeScreenOrKeyguard_matchesIfEitherHomeScreenOrKeyguardExists() {
+ val filter = WidgetCategoryFilter(WIDGET_CATEGORY_HOME_SCREEN or WIDGET_CATEGORY_KEYGUARD)
+
+ filter.assertMatches(WIDGET_CATEGORY_HOME_SCREEN)
+ filter.assertMatches(WIDGET_CATEGORY_KEYGUARD)
+ filter.assertMatches(WIDGET_CATEGORY_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN)
+ filter.assertMatches(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_HOME_SCREEN)
+ filter.assertMatches(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_KEYGUARD)
+ filter.assertMatches(WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN)
+ filter.assertMatches(WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_KEYGUARD)
+
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_NOT_KEYGUARD)
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_SEARCHBOX)
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_NOT_KEYGUARD)
+ }
+
+ @Test
+ fun excludeNotKeyguard_doesNotMatchIfNotKeyguardExists() {
+ val filter = WidgetCategoryFilter(WIDGET_CATEGORY_NOT_KEYGUARD.inv())
+
+ filter.assertMatches(WIDGET_CATEGORY_HOME_SCREEN)
+ filter.assertMatches(WIDGET_CATEGORY_KEYGUARD)
+ filter.assertMatches(WIDGET_CATEGORY_SEARCHBOX)
+ filter.assertMatches(WIDGET_CATEGORY_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN)
+ filter.assertMatches(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_HOME_SCREEN)
+ filter.assertMatches(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_KEYGUARD)
+
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_NOT_KEYGUARD)
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN)
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_KEYGUARD)
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_NOT_KEYGUARD)
+ filter.assertDoesNotMatch(
+ WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN
+ )
+ }
+
+ @Test
+ fun multipleExclusions_doesNotMatchIfExcludedCategoriesExist() {
+ val filter =
+ WidgetCategoryFilter(
+ WIDGET_CATEGORY_HOME_SCREEN.inv() and WIDGET_CATEGORY_NOT_KEYGUARD.inv()
+ )
+
+ filter.assertMatches(WIDGET_CATEGORY_SEARCHBOX)
+ filter.assertMatches(WIDGET_CATEGORY_KEYGUARD)
+ filter.assertMatches(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_KEYGUARD)
+
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_HOME_SCREEN)
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN)
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_HOME_SCREEN)
+
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_NOT_KEYGUARD)
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN)
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_KEYGUARD)
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_NOT_KEYGUARD)
+ filter.assertDoesNotMatch(
+ WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN
+ )
+ }
+
+ private fun WidgetCategoryFilter.assertMatches(category: Int) {
+ assertThat(matches(category)).isTrue()
+ }
+
+ private fun WidgetCategoryFilter.assertDoesNotMatch(category: Int) {
+ assertThat(matches(category)).isFalse()
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsModelTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsModelTest.java
index 99a1c59..722e1da 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsModelTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsModelTest.java
@@ -43,6 +43,7 @@
import com.android.launcher3.R;
import com.android.launcher3.graphics.ThemeManager;
import com.android.launcher3.icons.IconProvider;
+import com.android.launcher3.util.LockedUserState;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.SplitTask;
@@ -76,6 +77,12 @@
@Mock
private HighResLoadingState mHighResLoadingState;
+ @Mock
+ private LockedUserState mLockedUserState;
+
+ @Mock
+ private ThemeManager mThemeManager;
+
private RecentsModel mRecentsModel;
private RecentTasksList.TaskLoadResult mTaskResult;
@@ -102,7 +109,7 @@
mRecentsModel = new RecentsModel(mContext, mTasksList, mock(TaskIconCache.class),
mThumbnailCache, mock(IconProvider.class), mock(TaskStackChangeListeners.class),
- mock(ThemeManager.class));
+ mLockedUserState, () -> mThemeManager);
mResource = mock(Resources.class);
when(mResource.getInteger((R.integer.recentsThumbnailCacheSize))).thenReturn(3);
@@ -167,6 +174,17 @@
.updateThumbnailInCache(any(), anyBoolean());
}
+ @Test
+ public void themeCallbackAttachedOnUnlock() {
+ verify(mThemeManager, never()).addChangeListener(any());
+
+ ArgumentCaptor<Runnable> callbackCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(mLockedUserState).runOnUserUnlocked(callbackCaptor.capture());
+
+ callbackCaptor.getAllValues().forEach(Runnable::run);
+ verify(mThemeManager, times(1)).addChangeListener(any());
+ }
+
private RecentTasksList.TaskLoadResult getTaskResult() {
RecentTasksList.TaskLoadResult allTasks = new RecentTasksList.TaskLoadResult(0, false, 1);
ActivityManager.RecentTaskInfo taskInfo1 = new ActivityManager.RecentTaskInfo();
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/ActiveTrackpadListTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/ActiveTrackpadListTest.kt
new file mode 100644
index 0000000..b4c236e
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/ActiveTrackpadListTest.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util
+
+import android.hardware.input.InputManager
+import android.view.InputDevice
+import android.view.InputDevice.SOURCE_MOUSE
+import android.view.InputDevice.SOURCE_TOUCHPAD
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.IntArray
+import com.android.launcher3.util.SandboxApplication
+import com.android.launcher3.util.TestUtil
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.whenever
+
+@RunWith(AndroidJUnit4::class)
+class ActiveTrackpadListTest {
+
+ @get:Rule val context = SandboxApplication()
+
+ private val inputDeviceIds = IntArray()
+ private lateinit var inputManager: InputManager
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ inputManager = context.spyService(InputManager::class.java)
+ doAnswer { inputDeviceIds.toArray() }.whenever(inputManager).inputDeviceIds
+
+ doReturn(null).whenever(inputManager).getInputDevice(eq(1))
+ doReturn(mockDevice(SOURCE_MOUSE or SOURCE_TOUCHPAD))
+ .whenever(inputManager)
+ .getInputDevice(eq(2))
+ doReturn(mockDevice(SOURCE_MOUSE or SOURCE_TOUCHPAD))
+ .whenever(inputManager)
+ .getInputDevice(eq(3))
+ doReturn(mockDevice(SOURCE_MOUSE)).whenever(inputManager).getInputDevice(eq(4))
+ }
+
+ @Test
+ fun `initialize correct devices`() {
+ inputDeviceIds.addAll(IntArray.wrap(1, 2, 3, 4))
+
+ val list = ActiveTrackpadList(context) {}
+ assertEquals(2, list.size())
+ assertTrue(list.contains(2))
+ assertTrue(list.contains(3))
+ }
+
+ @Test
+ fun `update callback not called in constructor`() {
+ inputDeviceIds.addAll(IntArray.wrap(2, 3))
+
+ var updateCalled = false
+ val list = ActiveTrackpadList(context) { updateCalled = true }
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+
+ assertEquals(2, list.size())
+ assertFalse(updateCalled)
+ }
+
+ @Test
+ fun `update called on add only once`() {
+ var updateCalled = false
+ val list = ActiveTrackpadList(context) { updateCalled = true }
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+
+ assertFalse(updateCalled)
+ assertEquals(0, list.size())
+
+ list.onInputDeviceAdded(1)
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+ assertFalse(updateCalled)
+ assertEquals(0, list.size())
+
+ list.onInputDeviceAdded(2)
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+ assertTrue(updateCalled)
+ assertEquals(1, list.size())
+
+ updateCalled = false
+ list.onInputDeviceAdded(3)
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+ assertFalse(updateCalled)
+ assertEquals(2, list.size())
+ }
+
+ @Test
+ fun `update called on remove only once`() {
+ var updateCalled = false
+ inputDeviceIds.addAll(IntArray.wrap(1, 2, 3, 4))
+ val list = ActiveTrackpadList(context) { updateCalled = true }
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+ assertEquals(2, list.size())
+
+ list.onInputDeviceRemoved(2)
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+ assertEquals(1, list.size())
+ assertFalse(updateCalled)
+
+ list.onInputDeviceRemoved(3)
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+ assertEquals(0, list.size())
+ assertTrue(updateCalled)
+ }
+
+ private fun mockDevice(sources: Int) =
+ mock(InputDevice::class.java).apply { doReturn(sources).whenever(this).sources }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
index 0491c07..e4bdba5 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
@@ -102,12 +102,12 @@
fun activeTasks_noMatchingTasks() {
val nonMatchingComponent = ComponentKey(ComponentName("no", "match"), primaryUserHandle)
val groupTask1 =
- generateGroupTask(
+ generateSplitTask(
ComponentName("pomegranate", "juice"),
ComponentName("pumpkin", "pie"),
)
val groupTask2 =
- generateGroupTask(
+ generateSplitTask(
ComponentName("hotdog", "juice"),
ComponentName("personal", "computer"),
)
@@ -143,12 +143,12 @@
val matchingComponent =
ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
val groupTask1 =
- generateGroupTask(
+ generateSplitTask(
ComponentName(matchingPackage, matchingClass),
ComponentName("pomegranate", "juice"),
)
val groupTask2 =
- generateGroupTask(
+ generateSplitTask(
ComponentName("pumpkin", "pie"),
ComponentName("personal", "computer"),
)
@@ -170,7 +170,7 @@
it[0].key.baseIntent.component?.className,
matchingClass,
)
- assertEquals(it[0], groupTask1.task1)
+ assertEquals(it[0], groupTask1.topLeftTask)
}
// Capture callback from recentsModel#getTasks()
@@ -196,12 +196,12 @@
val nonPrimaryUserComponent =
ComponentKey(ComponentName(matchingPackage, matchingClass), nonPrimaryUserHandle)
val groupTask1 =
- generateGroupTask(
+ generateSplitTask(
ComponentName(matchingPackage, matchingClass),
ComponentName("pomegranate", "juice"),
)
val groupTask2 =
- generateGroupTask(
+ generateSplitTask(
ComponentName("pumpkin", "pie"),
ComponentName("personal", "computer"),
)
@@ -237,14 +237,14 @@
val nonPrimaryUserComponent =
ComponentKey(ComponentName(matchingPackage, matchingClass), nonPrimaryUserHandle)
val groupTask1 =
- generateGroupTask(
+ generateSplitTask(
ComponentName(matchingPackage, matchingClass),
nonPrimaryUserHandle,
ComponentName("pomegranate", "juice"),
nonPrimaryUserHandle,
)
val groupTask2 =
- generateGroupTask(
+ generateSplitTask(
ComponentName("pumpkin", "pie"),
ComponentName("personal", "computer"),
)
@@ -267,7 +267,7 @@
matchingClass,
)
assertEquals("userId mismatched", it[0].key.userId, nonPrimaryUserHandle.identifier)
- assertEquals(it[0], groupTask1.task1)
+ assertEquals(it[0], groupTask1.topLeftTask)
}
// Capture callback from recentsModel#getTasks()
@@ -293,12 +293,12 @@
val matchingComponent =
ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
val groupTask1 =
- generateGroupTask(
+ generateSplitTask(
ComponentName(matchingPackage, matchingClass),
ComponentName("pumpkin", "pie"),
)
val groupTask2 =
- generateGroupTask(
+ generateSplitTask(
ComponentName("pomegranate", "juice"),
ComponentName(matchingPackage, matchingClass),
)
@@ -320,7 +320,7 @@
it[0].key.baseIntent.component?.className,
matchingClass,
)
- assertEquals(it[0], groupTask1.task1)
+ assertEquals(it[0], groupTask1.topLeftTask)
}
// Capture callback from recentsModel#getTasks()
@@ -348,9 +348,9 @@
ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
val groupTask1 =
- generateGroupTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
+ generateSplitTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
val groupTask2 =
- generateGroupTask(
+ generateSplitTask(
ComponentName("pomegranate", "juice"),
ComponentName(matchingPackage, matchingClass),
)
@@ -374,7 +374,7 @@
it[1].key.baseIntent.component?.className,
matchingClass,
)
- assertEquals(it[1], groupTask2.task2)
+ assertEquals(it[1], groupTask2.bottomRightTask)
}
// Capture callback from recentsModel#getTasks()
@@ -401,9 +401,9 @@
ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
val groupTask1 =
- generateGroupTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
+ generateSplitTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
val groupTask2 =
- generateGroupTask(
+ generateSplitTask(
ComponentName("pomegranate", "juice"),
ComponentName(matchingPackage, matchingClass),
)
@@ -426,7 +426,7 @@
it[0].key.baseIntent.component?.className,
matchingClass,
)
- assertEquals(it[0], groupTask2.task2)
+ assertEquals(it[0], groupTask2.bottomRightTask)
assertNull("No tasks should have matched", it[1] /*task*/)
}
@@ -454,12 +454,12 @@
ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
val groupTask1 =
- generateGroupTask(
+ generateSplitTask(
ComponentName(matchingPackage, matchingClass),
ComponentName("pumpkin", "pie"),
)
val groupTask2 =
- generateGroupTask(
+ generateSplitTask(
ComponentName("pomegranate", "juice"),
ComponentName(matchingPackage, matchingClass),
)
@@ -482,7 +482,7 @@
it[0].key.baseIntent.component?.className,
matchingClass,
)
- assertEquals(it[0], groupTask1.task1)
+ assertEquals(it[0], groupTask1.topLeftTask)
assertEquals(
"ComponentName package mismatched",
it[1].key.baseIntent.component?.packageName,
@@ -493,7 +493,7 @@
it[1].key.baseIntent.component?.className,
matchingClass,
)
- assertEquals(it[1], groupTask2.task2)
+ assertEquals(it[1], groupTask2.bottomRightTask)
}
// Capture callback from recentsModel#getTasks()
@@ -524,14 +524,14 @@
ComponentKey(ComponentName(matchingPackage2, matchingClass2), primaryUserHandle)
val groupTask1 =
- generateGroupTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
+ generateSplitTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
val groupTask2 =
- generateGroupTask(
+ generateSplitTask(
ComponentName(matchingPackage2, matchingClass2),
ComponentName(matchingPackage, matchingClass),
)
val groupTask3 =
- generateGroupTask(
+ generateSplitTask(
ComponentName("hotdog", "pie"),
ComponentName(matchingPackage, matchingClass),
)
@@ -545,7 +545,7 @@
val taskConsumer =
Consumer<Array<Task>> {
assertEquals("Expected array length 2", 2, it.size)
- assertEquals("Found wrong task", it[0], groupTask2.task1)
+ assertEquals("Found wrong task", it[0], groupTask2.topLeftTask)
}
// Capture callback from recentsModel#getTasks()
@@ -640,11 +640,11 @@
verify(recentsView, times(0)).resetDesktopTaskFromSplitSelectState()
}
- // Generate GroupTask with default userId.
- private fun generateGroupTask(
+ /** Generates a [SplitTask] with default userId. */
+ private fun generateSplitTask(
task1ComponentName: ComponentName,
task2ComponentName: ComponentName,
- ): GroupTask {
+ ): SplitTask {
val task1 = Task()
var taskInfo = ActivityManager.RunningTaskInfo()
taskInfo.taskId = getUniqueId()
@@ -666,20 +666,20 @@
SplitConfigurationOptions.SplitBounds(
/* leftTopBounds = */ Rect(),
/* rightBottomBounds = */ Rect(),
- /* leftTopTaskId = */ -1,
- /* rightBottomTaskId = */ -1,
+ /* leftTopTaskId = */ task1.key.id,
+ /* rightBottomTaskId = */ task2.key.id,
/* snapPosition = */ SNAP_TO_2_50_50,
),
)
}
- // Generate GroupTask with custom user handles.
- private fun generateGroupTask(
+ /** Generates a [SplitTask] with custom user handles. */
+ private fun generateSplitTask(
task1ComponentName: ComponentName,
userHandle1: UserHandle,
task2ComponentName: ComponentName,
userHandle2: UserHandle,
- ): GroupTask {
+ ): SplitTask {
val task1 = Task()
var taskInfo = ActivityManager.RunningTaskInfo()
taskInfo.taskId = getUniqueId()
@@ -704,8 +704,8 @@
SplitConfigurationOptions.SplitBounds(
/* leftTopBounds = */ Rect(),
/* rightBottomBounds = */ Rect(),
- /* leftTopTaskId = */ -1,
- /* rightBottomTaskId = */ -1,
+ /* leftTopTaskId = */ task1.key.id,
+ /* rightBottomTaskId = */ task2.key.id,
/* snapPosition = */ SNAP_TO_2_50_50,
),
)
diff --git a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
index f923142..c78fe1c 100644
--- a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
+++ b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
@@ -21,6 +21,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import android.app.PendingIntent;
@@ -93,7 +94,8 @@
final TaskView task = getOnceNotNull("No latest task", launcher -> getLatestTask(launcher));
return getFromLauncher(launcher -> {
- TaskContainer taskContainer = task.getTaskContainers().get(0);
+ TaskContainer taskContainer = task.getFirstTaskContainer();
+ assertNotNull(taskContainer);
assertTrue("Latest task is not Calculator", calculatorPackage.equals(
taskContainer.getTask().getTopComponent().getPackageName()));
return taskContainer.getDigitalWellBeingToast();
diff --git a/res/layout/work_mode_utility_view.xml b/res/layout/work_mode_utility_view.xml
index fc112ce..b68ff3e 100644
--- a/res/layout/work_mode_utility_view.xml
+++ b/res/layout/work_mode_utility_view.xml
@@ -14,6 +14,7 @@
~ limitations under the License.
-->
<com.android.launcher3.allapps.WorkUtilityView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/work_utility_view"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:orientation="vertical"
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index a925866..2b7ec21 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -24,7 +24,7 @@
<string name="activity_not_found" msgid="8071924732094499514">"لم يتم تثبيت التطبيق."</string>
<string name="activity_not_available" msgid="7456344436509528827">"التطبيق ليس متاحًا"</string>
<string name="safemode_shortcut_error" msgid="9160126848219158407">"تم إيقاف التطبيق الذي تم تنزيله في الوضع الآمن"</string>
- <string name="safemode_widget_error" msgid="4863470563535682004">"الأدوات غير مفعّلة في الوضع الآمن"</string>
+ <string name="safemode_widget_error" msgid="4863470563535682004">"التطبيقات المصغَّرة غير مفعّلة في الوضع الآمن"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"الاختصار غير متاح"</string>
<string name="home_screen" msgid="5629429142036709174">"الشاشة الرئيسية"</string>
<string name="set_default_home_app" msgid="5808906607627586381">"يمكن ضبط \"<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>\" كتطبيق الشاشة الرئيسية التلقائي من خلال \"الإعدادات\""</string>
@@ -38,13 +38,13 @@
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"لا يمكن استخدام هذين التطبيقَين في الوقت نفسه على هذا الجهاز"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"افتح الجهاز لاستخدام هذين التطبيقَين في الوقت نفسه"</string>
<string name="app_pair_not_available" msgid="3556767440808032031">"ميزة \"استخدام تطبيقين في الوقت نفسه\" غير متوفّرة"</string>
- <string name="long_press_widget_to_add" msgid="3587712543577675817">"انقر مع الاستمرار لنقل أداة."</string>
- <string name="long_accessible_way_to_add" msgid="2733588281439571974">"انقر مرتين مع تثبيت إصبعك لنقل أداة أو استخدام الإجراءات المخصّصة."</string>
+ <string name="long_press_widget_to_add" msgid="3587712543577675817">"انقر مع الاستمرار لنقل تطبيق مصغَّر."</string>
+ <string name="long_accessible_way_to_add" msgid="2733588281439571974">"انقر مرتين مع تثبيت إصبعك لنقل تطبيق مصغَّر أو استخدام الإجراءات المخصّصة."</string>
<string name="widget_picker_widget_options_button_description" msgid="4770099264476852363">"خيارات إضافية"</string>
<string name="widget_picker_show_all_widgets_menu_item_title" msgid="9023638224586908119">"عرض كل التطبيقات المصغّرة"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"العرض %1$d الطول %2$d"</string>
- <string name="widget_preview_context_description" msgid="9045841361655787574">"أداة <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
+ <string name="widget_preview_context_description" msgid="9045841361655787574">"التطبيق المصغَّر <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"التطبيق المصغّرة \"<xliff:g id="WIDGET_NAME">%1$s</xliff:g>\"، بعرض %2$d وارتفاع %3$d"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"يُرجى النقر مع الاستمرار على التطبيق المصغّر لنقله إلى الشاشة الرئيسية"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"إضافة إلى الشاشة الرئيسية"</string>
@@ -62,7 +62,7 @@
<string name="widget_button_text" msgid="2880537293434387943">"التطبيقات المصغّرة"</string>
<string name="widgets_full_sheet_search_bar_hint" msgid="8484659090860596457">"بحث"</string>
<string name="widgets_full_sheet_cancel_button_description" msgid="5766167035728653605">"محو النص من مربّع البحث"</string>
- <string name="no_widgets_available" msgid="4337693382501046170">"الأدوات والاختصارات غير متاحة."</string>
+ <string name="no_widgets_available" msgid="4337693382501046170">"التطبيقات المصغَّرة والاختصارات غير متاحة."</string>
<string name="no_search_results" msgid="3787956167293097509">"لم يتم العثور على تطبيقات مصغّرة أو اختصارات."</string>
<string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"التطبيقات الشخصية"</string>
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"تطبيقات العمل"</string>
@@ -75,8 +75,8 @@
<string name="widgets_list_expand_button_label" msgid="7912016136574932622">"عرض الكل"</string>
<string name="widgets_list_expand_button_content_description" msgid="4600513860973450888">"عرض كل التطبيقات المصغّرة"</string>
<string name="widgets_list_expanded" msgid="7374857868788557730">"جارٍ عرض كل التطبيقات المصغّرة"</string>
- <string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"انقر لتغيير إعدادات الأداة"</string>
- <string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"تغيير إعدادات الأداة"</string>
+ <string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"انقر لتغيير إعدادات التطبيق المصغَّر"</string>
+ <string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"تغيير إعدادات التطبيق المصغَّر"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"بحث في التطبيقات"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"جارٍ تحميل التطبيقات…"</string>
<string name="all_apps_no_search_results" msgid="3200346862396363786">"لم يتم العثور على أي تطبيقات تتطابق مع \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
@@ -108,7 +108,7 @@
<string name="permdesc_read_settings" msgid="4208061150510996676">"يسمح هذا الإذن للتطبيق بالاطلاع على الإعدادات والاختصارات على الشاشة الرئيسية."</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_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>
@@ -159,8 +159,8 @@
<string name="dialog_update_message" msgid="4176784553982226114">"لم يتمّ تحديث التطبيق الخاص بهذا الرمز. يمكنك تحديث التطبيق يدويًا لإعادة تفعيل هذا الاختصار أو إزالة الرمز."</string>
<string name="dialog_update" msgid="2178028071796141234">"تحديث"</string>
<string name="dialog_remove" msgid="6510806469849709407">"إزالة"</string>
- <string name="widgets_list" msgid="796804551140113767">"قائمة الأدوات"</string>
- <string name="widgets_list_closed" msgid="6141506579418771922">"تم إغلاق قائمة الأدوات."</string>
+ <string name="widgets_list" msgid="796804551140113767">"قائمة التطبيقات المصغَّرة"</string>
+ <string name="widgets_list_closed" msgid="6141506579418771922">"تم إغلاق قائمة التطبيقات المصغَّرة."</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"إضافة تطبيق للشاشة الرئيسية"</string>
<string name="action_move_here" msgid="2170188780612570250">"نقل العنصر إلى هنا"</string>
<string name="item_removed" msgid="851119963877842327">"تمّت إزالة العنصر."</string>
@@ -181,7 +181,7 @@
<string name="action_increase_height" msgid="459390020612501122">"زيادة الارتفاع"</string>
<string name="action_decrease_width" msgid="1374549771083094654">"تقليل العرض"</string>
<string name="action_decrease_height" msgid="282377193880900022">"تقليل الارتفاع"</string>
- <string name="widget_resized" msgid="9130327887929620">"تم تغيير حجم الأداة إلى العرض <xliff:g id="NUMBER_0">%1$s</xliff:g> والارتفاع <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+ <string name="widget_resized" msgid="9130327887929620">"تم تغيير حجم التطبيق المصغَّر إلى العرض <xliff:g id="NUMBER_0">%1$s</xliff:g> والارتفاع <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
<string name="action_deep_shortcut" msgid="4766835855579976045">"قائمة الاختصارات"</string>
<string name="action_dismiss_notification" msgid="5909461085055959187">"تجاهل"</string>
<string name="accessibility_close" msgid="2277148124685870734">"إغلاق"</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 78b566a..e3bde3a 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -83,7 +83,7 @@
<string name="label_application" msgid="8531721983832654978">"एप"</string>
<string name="all_apps_label" msgid="5015784846527570951">"सबै एप"</string>
<string name="all_apps_list_label" msgid="5106226764073070906">"एपहरूको सूची"</string>
- <string name="notifications_header" msgid="1404149926117359025">"सूचनाहरू"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"नोटिफिकेसनहरू"</string>
<string name="long_press_shortcut_to_add" msgid="5405328730817637737">"कुनै सर्टकट सार्न डबल ट्याप गरेर छोइराख्नुहोस्।"</string>
<string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"कुनै सर्टकट सार्न वा आफ्नो रोजाइका कारबाही प्रयोग गर्न डबल ट्याप गरेर छोइराख्नुहोस्।"</string>
<string name="out_of_space" msgid="6455557115204099579">"यो होम स्क्रिनमा ठाउँ छैन"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 754bd76..1dad001 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -46,7 +46,7 @@
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"Szerokość %1$d, wysokość %2$d"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"Widżet <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Widżet <xliff:g id="WIDGET_NAME">%1$s</xliff:g>, %2$d (szerokość), %3$d (wysokość)"</string>
- <string name="add_item_request_drag_hint" msgid="8730547755622776606">"Aby poruszać widżetem po ekranie głównym, kliknij go i przytrzymaj"</string>
+ <string name="add_item_request_drag_hint" msgid="8730547755622776606">"Aby przesunąć widżet na ekranie głównym, kliknij go i przytrzymaj"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Dodaj do ekranu głównego"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Widżet <xliff:g id="WIDGET_NAME">%1$s</xliff:g> został dodany do ekranu głównego"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Sugestie"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 7016df5..9e2307a 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -129,7 +129,7 @@
<string name="app_pair_name_format" msgid="8134106404716224054">"Par aplikacij: <xliff:g id="APP1">%1$s</xliff:g> in <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Zaslonsko ozadje in slog"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Urejanje začetnega zaslona"</string>
- <string name="settings_button_text" msgid="8873672322605444408">"Domače nastavitve"</string>
+ <string name="settings_button_text" msgid="8873672322605444408">"Začetni zaslon"</string>
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"Onemogočil skrbnik."</string>
<string name="allow_rotation_title" msgid="7222049633713050106">"Dovoli sukanje začetnega zaslona"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Ko se telefon zasuka"</string>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index e06895c..f740489 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -174,7 +174,8 @@
<declare-styleable name="GridDisplayOption">
<attr name="name" format="string" />
- <attr name="title" />
+ <attr name="gridTitle" format="string" />
+ <attr name="gridIconId" format="reference"/>
<attr name="numRows" format="integer" />
<attr name="numColumns" format="integer" />
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 212534b..c48f140 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -191,7 +191,7 @@
<!-- Widget tray -->
<dimen name="widget_cell_vertical_padding">8dp</dimen>
- <dimen name="widget_cell_horizontal_padding">8dp</dimen>
+ <dimen name="widget_cell_horizontal_padding">4dp</dimen>
<dimen name="widget_cell_title_font_size">14sp</dimen>
<integer name="widget_cell_title_font_weight">500</integer>
<dimen name="widget_cell_title_line_height">20sp</dimen>
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 2e75261..6277e41 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -16,6 +16,7 @@
package com.android.launcher3;
+import static com.android.launcher3.util.DisplayController.CHANGE_ROTATION;
import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
@@ -29,17 +30,27 @@
import android.content.res.Configuration;
import android.os.Bundle;
import android.util.Log;
+import android.view.ActionMode;
+import android.view.View;
import android.window.OnBackInvokedDispatcher;
import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.util.ActivityOptionsWrapper;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
+import com.android.launcher3.util.DisplayController.Info;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.ViewCache;
+import com.android.launcher3.util.WindowBounds;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.ScrimView;
@@ -52,7 +63,8 @@
/**
* Launcher BaseActivity
*/
-public abstract class BaseActivity extends Activity implements ActivityContext {
+public abstract class BaseActivity extends Activity implements ActivityContext,
+ DisplayInfoChangeListener {
private static final String TAG = "BaseActivity";
static final boolean DEBUG = false;
@@ -126,6 +138,10 @@
public @interface ActivityFlags {
}
+ // When starting an action mode, setting this tag will cause the action mode to be cancelled
+ // automatically when user interacts with the launcher.
+ public static final Object AUTO_CANCEL_ACTION_MODE = new Object();
+
/** Returns a human-readable string for the specified {@link ActivityFlags}. */
public static String getActivityStateString(@ActivityFlags int flags) {
StringJoiner result = new StringJoiner("|");
@@ -160,6 +176,8 @@
private final RunnableList[] mEventCallbacks =
{new RunnableList(), new RunnableList(), new RunnableList(), new RunnableList()};
+ private ActionMode mCurrentActionMode;
+
@Override
public ViewCache getViewCache() {
return mViewCache;
@@ -206,6 +224,7 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
registerBackDispatcher();
+ DisplayController.INSTANCE.get(this).addChangeListener(this);
}
@Override
@@ -253,6 +272,7 @@
protected void onDestroy() {
super.onDestroy();
mEventCallbacks[EVENT_DESTROYED].executeAllAndClear();
+ DisplayController.INSTANCE.get(this).removeChangeListener(this);
}
@Override
@@ -403,6 +423,61 @@
writer.println(prefix + "mForceInvisible: " + mForceInvisible);
}
+
+ @Override
+ public void onActionModeStarted(ActionMode mode) {
+ super.onActionModeStarted(mode);
+ mCurrentActionMode = mode;
+ }
+
+ @Override
+ public void onActionModeFinished(ActionMode mode) {
+ super.onActionModeFinished(mode);
+ mCurrentActionMode = null;
+ }
+
+ protected boolean isInAutoCancelActionMode() {
+ return mCurrentActionMode != null && AUTO_CANCEL_ACTION_MODE == mCurrentActionMode.getTag();
+ }
+
+ @Override
+ public boolean finishAutoCancelActionMode() {
+ if (isInAutoCancelActionMode()) {
+ mCurrentActionMode.finish();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ @NonNull
+ public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) {
+ ActivityOptionsWrapper wrapper = ActivityContext.super.getActivityLaunchOptions(v, item);
+ addEventCallback(EVENT_RESUMED, wrapper.onEndCallback::executeAllAndDestroy);
+ return wrapper;
+ }
+
+ @Override
+ public ActivityOptionsWrapper makeDefaultActivityOptions(int splashScreenStyle) {
+ ActivityOptionsWrapper wrapper =
+ ActivityContext.super.makeDefaultActivityOptions(splashScreenStyle);
+ addEventCallback(EVENT_RESUMED, wrapper.onEndCallback::executeAllAndDestroy);
+ return wrapper;
+ }
+
+ protected WindowBounds getMultiWindowDisplaySize() {
+ return WindowBounds.fromWindowMetrics(getWindowManager().getCurrentWindowMetrics());
+ }
+
+ @Override
+ public void onDisplayInfoChanged(Context context, Info info, int flags) {
+ if ((flags & CHANGE_ROTATION) != 0 && mDeviceProfile.isVerticalBarLayout()) {
+ reapplyUi();
+ }
+ }
+
+ protected void reapplyUi() {}
+
public static <T extends BaseActivity> T fromContext(Context context) {
if (context instanceof BaseActivity) {
return (T) context;
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
deleted file mode 100644
index 3b93cf4..0000000
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import static com.android.launcher3.util.DisplayController.CHANGE_ROTATION;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.view.ActionMode;
-import android.view.View;
-
-import androidx.annotation.MainThread;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.touch.ItemClickHandler;
-import com.android.launcher3.util.ActivityOptionsWrapper;
-import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
-import com.android.launcher3.util.DisplayController.Info;
-import com.android.launcher3.util.OnColorHintListener;
-import com.android.launcher3.util.Themes;
-import com.android.launcher3.util.WallpaperColorHints;
-import com.android.launcher3.util.WindowBounds;
-
-/**
- * Extension of BaseActivity allowing support for drag-n-drop
- */
-@SuppressWarnings("NewApi")
-public abstract class BaseDraggingActivity extends BaseActivity
- implements OnColorHintListener, DisplayInfoChangeListener {
-
- // When starting an action mode, setting this tag will cause the action mode to be cancelled
- // automatically when user interacts with the launcher.
- public static final Object AUTO_CANCEL_ACTION_MODE = new Object();
-
- private ActionMode mCurrentActionMode;
-
- private int mThemeRes = R.style.AppTheme;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- DisplayController.INSTANCE.get(this).addChangeListener(this);
-
- // Update theme
- WallpaperColorHints.get(this).registerOnColorHintsChangedListener(this);
- int themeRes = Themes.getActivityThemeRes(this);
- if (themeRes != mThemeRes) {
- mThemeRes = themeRes;
- setTheme(themeRes);
- }
- }
-
- @MainThread
- @Override
- public void onColorHintsChanged(int colorHints) {
- updateTheme();
- }
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- updateTheme();
- }
-
- private void updateTheme() {
- if (mThemeRes != Themes.getActivityThemeRes(this)) {
- recreateToUpdateTheme();
- }
- }
-
- protected void recreateToUpdateTheme() {
- recreate();
- }
-
- @Override
- public void onActionModeStarted(ActionMode mode) {
- super.onActionModeStarted(mode);
- mCurrentActionMode = mode;
- }
-
- @Override
- public void onActionModeFinished(ActionMode mode) {
- super.onActionModeFinished(mode);
- mCurrentActionMode = null;
- }
-
- protected boolean isInAutoCancelActionMode() {
- return mCurrentActionMode != null && AUTO_CANCEL_ACTION_MODE == mCurrentActionMode.getTag();
- }
-
- @Override
- public boolean finishAutoCancelActionMode() {
- if (isInAutoCancelActionMode()) {
- mCurrentActionMode.finish();
- return true;
- }
- return false;
- }
-
- public abstract View getRootView();
-
- public void returnToHomescreen() {
- // no-op
- }
-
- @Override
- @NonNull
- public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) {
- ActivityOptionsWrapper wrapper = super.getActivityLaunchOptions(v, item);
- addEventCallback(EVENT_RESUMED, wrapper.onEndCallback::executeAllAndDestroy);
- return wrapper;
- }
-
- @Override
- public ActivityOptionsWrapper makeDefaultActivityOptions(int splashScreenStyle) {
- ActivityOptionsWrapper wrapper = super.makeDefaultActivityOptions(splashScreenStyle);
- addEventCallback(EVENT_RESUMED, wrapper.onEndCallback::executeAllAndDestroy);
- return wrapper;
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- DisplayController.INSTANCE.get(this).removeChangeListener(this);
- WallpaperColorHints.get(this).unregisterOnColorsChangedListener(this);
- }
-
- protected void onDeviceProfileInitiated() {
- }
-
- @Override
- public void onDisplayInfoChanged(Context context, Info info, int flags) {
- if ((flags & CHANGE_ROTATION) != 0 && mDeviceProfile.isVerticalBarLayout()) {
- reapplyUi();
- }
- }
-
- @Override
- public View.OnClickListener getItemOnClickListener() {
- return ItemClickHandler.INSTANCE;
- }
-
- protected abstract void reapplyUi();
-
- protected WindowBounds getMultiWindowDisplaySize() {
- return WindowBounds.fromWindowMetrics(getWindowManager().getCurrentWindowMetrics());
- }
-
- @Override
- public boolean isAppBlockedForSafeMode() {
- return LauncherAppState.getInstance(this).isSafeModeEnabled();
- }
-}
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index b4a24f1..813d8f1 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -25,7 +25,6 @@
import static com.android.launcher3.InvariantDeviceProfile.INDEX_TWO_PANEL_PORTRAIT;
import static com.android.launcher3.Utilities.dpiFromPx;
import static com.android.launcher3.Utilities.pxFromSp;
-import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
import static com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURCE_HANDLE;
import static com.android.launcher3.testing.shared.ResourceUtils.pxFromDp;
@@ -52,6 +51,7 @@
import com.android.launcher3.CellLayout.ContainerType;
import com.android.launcher3.DevicePaddings.DevicePadding;
+import com.android.launcher3.folder.ClippedFolderIconLayoutRule;
import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.icons.IconNormalizer;
@@ -1228,7 +1228,7 @@
}
private int getIconSizeWithOverlap(int iconSize) {
- return (int) Math.ceil(iconSize * ICON_OVERLAP_FACTOR);
+ return (int) Math.ceil(iconSize * ClippedFolderIconLayoutRule.getIconOverlapFactor());
}
/**
diff --git a/src/com/android/launcher3/DropTargetHandler.kt b/src/com/android/launcher3/DropTargetHandler.kt
index 66c948a..0cc7fc7 100644
--- a/src/com/android/launcher3/DropTargetHandler.kt
+++ b/src/com/android/launcher3/DropTargetHandler.kt
@@ -2,7 +2,7 @@
import android.content.ComponentName
import android.view.View
-import com.android.launcher3.BaseDraggingActivity.EVENT_RESUMED
+import com.android.launcher3.BaseActivity.EVENT_RESUMED
import com.android.launcher3.DropTarget.DragObject
import com.android.launcher3.LauncherConstants.ActivityCodes
import com.android.launcher3.SecondaryDropTarget.DeferredOnComplete
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 753e017..e47a44a 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -993,7 +993,8 @@
private static final int DONT_INLINE_QSB = 0;
public final String name;
- public final String title;
+ public final String gridTitle;
+ public final int gridIconId;
public final int numRows;
public final int numColumns;
public final int numSearchContainerColumns;
@@ -1042,7 +1043,9 @@
TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.GridDisplayOption);
name = a.getString(R.styleable.GridDisplayOption_name);
- title = a.getString(R.styleable.GridDisplayOption_title);
+ gridTitle = a.getString(R.styleable.GridDisplayOption_gridTitle);
+ gridIconId = a.getResourceId(
+ R.styleable.GridDisplayOption_gridIconId, INVALID_RESOURCE_HANDLE);
deviceCategory = a.getInt(R.styleable.GridDisplayOption_deviceCategory,
DEVICE_CATEGORY_ALL);
mGridSizeSpecsId = a.getResourceId(
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 647d2ad..30ef24b 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -102,6 +102,7 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.ItemInfoMatcher.forFolderMatch;
import static com.android.launcher3.util.SettingsCache.TOUCHPAD_NATURAL_SCROLLING;
+import static com.android.launcher3.util.WallpaperThemeManager.setWallpaperDependentTheme;
import android.animation.Animator;
import android.animation.AnimatorSet;
@@ -144,6 +145,7 @@
import android.view.Menu;
import android.view.MotionEvent;
import android.view.View;
+import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewTreeObserver.OnPreDrawListener;
import android.view.WindowInsets;
@@ -222,6 +224,7 @@
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.touch.AllAppsSwipeController;
+import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.util.ActivityResultInfo;
import com.android.launcher3.util.BackPressHandler;
@@ -466,6 +469,7 @@
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
+ .detectActivityLeaks()
.penaltyLog()
.penaltyDeath()
.build());
@@ -509,6 +513,7 @@
}
super.onCreate(savedInstanceState);
+ setWallpaperDependentTheme(this);
LauncherAppState app = LauncherAppState.getInstance(this);
mModel = app.getModel();
@@ -819,7 +824,6 @@
this, getMultiWindowDisplaySize());
}
- onDeviceProfileInitiated();
if (FOLDABLE_SINGLE_PAGE.get() && mDeviceProfile.isTwoPanels) {
mCellPosMapper = new TwoPanelCellPosMapper(mDeviceProfile.inv.numColumns);
} else {
@@ -2840,12 +2844,6 @@
// Overridden
}
- @Override
- public void returnToHomescreen() {
- super.returnToHomescreen();
- getStateManager().goToState(LauncherState.NORMAL);
- }
-
public void closeOpenViews() {
closeOpenViews(true);
}
@@ -3170,5 +3168,10 @@
return findViewById(R.id.popup_container);
}
+ @Override
+ public OnClickListener getItemOnClickListener() {
+ return ItemClickHandler.INSTANCE;
+ }
+
// End of Getters and Setters
}
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index 484cef4..1120ec8 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -34,6 +34,7 @@
import com.android.launcher3.states.RotationHelper
import com.android.launcher3.util.DaggerSingletonObject
import com.android.launcher3.util.DisplayController
+import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
/**
@@ -52,17 +53,18 @@
.getSharedPreferences(BOOT_AWARE_PREFS_KEY, MODE_PRIVATE)
}
- open val Item.sharedPrefs: SharedPreferences
- get() =
+ open protected fun getSharedPrefs(item: Item): SharedPreferences =
+ item.run {
if (encryptionType == EncryptionType.DEVICE_PROTECTED) deviceProtectedSharedPrefs
else encryptedContext.getSharedPreferences(sharedPrefFile, MODE_PRIVATE)
+ }
/** Returns the value with type [T] for [item]. */
- open fun <T> get(item: ContextualItem<T>): T =
+ fun <T> get(item: ContextualItem<T>): T =
getInner(item, item.defaultValueFromContext(encryptedContext))
/** Returns the value with type [T] for [item]. */
- open fun <T> get(item: ConstantItem<T>): T = getInner(item, item.defaultValue)
+ fun <T> get(item: ConstantItem<T>): T = getInner(item, item.defaultValue)
/**
* Retrieves the value for an [Item] from [SharedPreferences]. It handles method typing via the
@@ -71,7 +73,7 @@
*/
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
private fun <T> getInner(item: Item, default: T): T {
- val sp = item.sharedPrefs
+ val sp = getSharedPrefs(item)
return when (item.type) {
String::class.java -> sp.getString(item.sharedPrefKey, default as? String)
@@ -127,7 +129,7 @@
private fun prepareToPutValues(
updates: Array<out Pair<Item, Any>>
): List<SharedPreferences.Editor> {
- val updatesPerPrefFile = updates.groupBy { it.first.sharedPrefs }.toMap()
+ val updatesPerPrefFile = updates.groupBy { getSharedPrefs(it.first) }.toMap()
return updatesPerPrefFile.map { (sharedPref, itemList) ->
sharedPref.edit().apply { itemList.forEach { (item, value) -> putValue(item, value) } }
@@ -140,7 +142,7 @@
* types of Item values.
*/
@Suppress("UNCHECKED_CAST")
- private fun SharedPreferences.Editor.putValue(
+ internal fun SharedPreferences.Editor.putValue(
item: Item,
value: Any?,
): SharedPreferences.Editor =
@@ -168,7 +170,7 @@
*/
fun addListener(listener: LauncherPrefChangeListener, vararg items: Item) {
items
- .map { it.sharedPrefs }
+ .map { getSharedPrefs(it) }
.distinct()
.forEach { it.registerOnSharedPreferenceChangeListener(listener) }
}
@@ -180,7 +182,7 @@
fun removeListener(listener: LauncherPrefChangeListener, vararg items: Item) {
// If a listener is not registered to a SharedPreference, unregistering it does nothing
items
- .map { it.sharedPrefs }
+ .map { getSharedPrefs(it) }
.distinct()
.forEach { it.unregisterOnSharedPreferenceChangeListener(listener) }
}
@@ -191,7 +193,7 @@
*/
fun has(vararg items: Item): Boolean {
items
- .groupBy { it.sharedPrefs }
+ .groupBy { getSharedPrefs(it) }
.forEach { (prefs, itemsSublist) ->
if (!itemsSublist.none { !prefs.contains(it.sharedPrefKey) }) return false
}
@@ -215,7 +217,7 @@
* .apply() or .commit()
*/
private fun prepareToRemove(items: Array<out Item>): List<SharedPreferences.Editor> {
- val itemsPerFile = items.groupBy { it.sharedPrefs }.toMap()
+ val itemsPerFile = items.groupBy { getSharedPrefs(it) }.toMap()
return itemsPerFile.map { (prefs, items) ->
prefs.edit().also { editor ->
@@ -412,14 +414,20 @@
*/
class ProxyPrefs(context: Context, private val prefs: SharedPreferences) : LauncherPrefs(context) {
- private val realPrefs = LauncherPrefs(context)
+ private val copiedPrefs = ConcurrentHashMap<SharedPreferences, Boolean>()
- override val Item.sharedPrefs: SharedPreferences
- get() = prefs
-
- override fun <T> get(item: ConstantItem<T>) =
- super.get(backedUpItem(item.sharedPrefKey, realPrefs.get(item)))
-
- override fun <T> get(item: ContextualItem<T>) =
- super.get(backedUpItem(item.sharedPrefKey, realPrefs.get(item)))
+ override fun getSharedPrefs(item: Item): SharedPreferences {
+ val originalPrefs = super.getSharedPrefs(item)
+ // Copy all existing values, when the pref is accessed for the first time
+ copiedPrefs.computeIfAbsent(originalPrefs) { op ->
+ val editor = prefs.edit()
+ op.all.forEach { (key, value) ->
+ if (value != null) {
+ editor.putValue(backedUpItem(key, value), value)
+ }
+ }
+ editor.commit()
+ }
+ return prefs
+ }
}
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index a5b95c7..b8a0abd 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -12,6 +12,7 @@
import com.android.launcher3.graphics.SysUiScrim;
import com.android.launcher3.statemanager.StatefulContainer;
import com.android.launcher3.util.window.WindowManagerProxy;
+import com.android.launcher3.views.ActivityContext;
import java.util.Collections;
import java.util.List;
@@ -36,7 +37,7 @@
public LauncherRootView(Context context, AttributeSet attrs) {
super(context, attrs);
- mStatefulContainer = StatefulContainer.fromContext(context);
+ mStatefulContainer = ActivityContext.lookupContext(context);
mSysUiScrim = new SysUiScrim(this);
}
@@ -54,7 +55,7 @@
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
mStatefulContainer.handleConfigurationChanged(
- mStatefulContainer.getContext().getResources().getConfiguration());
+ mStatefulContainer.asContext().getResources().getConfiguration());
return updateInsets(insets);
}
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 7d5e481..8c6555e 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -118,7 +118,7 @@
LAUNCHER_STATE_HOME,
FLAG_DISABLE_RESTORE | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED | FLAG_HAS_SYS_UI_SCRIM) {
@Override
- public int getTransitionDuration(Context context, boolean isToState) {
+ public int getTransitionDuration(ActivityContext context, boolean isToState) {
// Arbitrary duration, when going to NORMAL we use the state we're coming from instead.
return 0;
}
@@ -345,7 +345,7 @@
public final <DEVICE_PROFILE_CONTEXT extends Context & ActivityContext>
float getDepth(DEVICE_PROFILE_CONTEXT context) {
return getDepth(context,
- BaseDraggingActivity.fromContext(context).getDeviceProfile().isMultiWindowMode);
+ ActivityContext.lookupContext(context).getDeviceProfile().isMultiWindowMode);
}
/**
diff --git a/src/com/android/launcher3/allapps/WorkUtilityView.java b/src/com/android/launcher3/allapps/WorkUtilityView.java
index bccc279..e42a6b9 100644
--- a/src/com/android/launcher3/allapps/WorkUtilityView.java
+++ b/src/com/android/launcher3/allapps/WorkUtilityView.java
@@ -15,6 +15,11 @@
*/
package com.android.launcher3.allapps;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORK_UTILITY_VIEW_EXPAND_ANIMATION_BEGIN;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORK_UTILITY_VIEW_EXPAND_ANIMATION_END;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORK_UTILITY_VIEW_SHRINK_ANIMATION_BEGIN;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORK_UTILITY_VIEW_SHRINK_ANIMATION_END;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
@@ -46,6 +51,7 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatedPropertySetter;
import com.android.launcher3.anim.KeyboardInsetAnimationCallback;
+import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.StringCache;
import com.android.launcher3.views.ActivityContext;
@@ -88,6 +94,8 @@
private TextView mPauseText;
private ImageView mWorkIcon;
private ImageButton mSchedulerButton;
+ private final StatsLogManager mStatsLogManager;
+ private LinearLayout mWorkUtilityView;
public WorkUtilityView(@NonNull Context context) {
this(context, null, 0);
@@ -111,6 +119,7 @@
R.dimen.work_fab_icon_start_margin_expanded);
mWorkSchedulerIntentAction = mContext.getResources().getString(
R.string.work_profile_scheduler_intent);
+ mStatsLogManager = mActivityContext.getStatsLogManager();
}
@Override
@@ -121,6 +130,7 @@
mWorkIcon = findViewById(R.id.work_icon);
mWorkFAB = findViewById(R.id.work_mode_toggle);
mSchedulerButton = findViewById(R.id.work_scheduler);
+ mWorkUtilityView = findViewById(R.id.work_utility_view);
setSelected(true);
KeyboardInsetAnimationCallback keyboardInsetAnimationCallback =
new KeyboardInsetAnimationCallback(this);
@@ -319,6 +329,23 @@
animatorList.add(animateSchedulerScale(isExpanding));
animatorList.add(animateSchedulerAlpha(isExpanding));
}
+ animatorSet.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mStatsLogManager.logger().sendToInteractionJankMonitor(
+ isExpanding ? LAUNCHER_WORK_UTILITY_VIEW_EXPAND_ANIMATION_BEGIN
+ : LAUNCHER_WORK_UTILITY_VIEW_SHRINK_ANIMATION_BEGIN,
+ mWorkUtilityView);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mStatsLogManager.logger().sendToInteractionJankMonitor(
+ isExpanding ? LAUNCHER_WORK_UTILITY_VIEW_EXPAND_ANIMATION_END
+ : LAUNCHER_WORK_UTILITY_VIEW_SHRINK_ANIMATION_END,
+ mWorkUtilityView);
+ }
+ });
animatorSet.playTogether(animatorList);
animatorSet.start();
}
diff --git a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
index 8cd91d3..cf5150a 100644
--- a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
+++ b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
@@ -1,5 +1,7 @@
package com.android.launcher3.folder;
+import com.android.launcher3.Flags;
+
public class ClippedFolderIconLayoutRule {
public static final int MAX_NUM_ITEMS_IN_PREVIEW = 4;
@@ -7,9 +9,12 @@
private static final float MIN_SCALE = 0.44f;
private static final float MAX_SCALE = 0.51f;
+ // TODO: figure out exact radius for different icons
+ private static final float MAX_RADIUS_DILATION_SHAPES = 0.15f;
private static final float MAX_RADIUS_DILATION = 0.25f;
// The max amount of overlap the preview items can go outside of the background bounds.
public static final float ICON_OVERLAP_FACTOR = 1 + (MAX_RADIUS_DILATION / 2f);
+ public static final float ICON_OVERLAP_FACTOR_SHAPES = 1f;
private static final float ITEM_RADIUS_SCALE_FACTOR = 1.15f;
public static final int EXIT_INDEX = -2;
@@ -28,7 +33,7 @@
mRadius = ITEM_RADIUS_SCALE_FACTOR * availableSpace / 2f;
mIconSize = intrinsicIconSize;
mIsRtl = rtl;
- mBaselineIconScale = availableSpace / (intrinsicIconSize * 1f);
+ mBaselineIconScale = availableSpace / intrinsicIconSize;
}
public PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
@@ -84,6 +89,7 @@
result[1] = top + (row * dy);
}
+ // b/392610664 TODO: Change positioning from circular geometry to square / grid-based.
private void getPosition(int index, int curNumItems, float[] result) {
// The case of two items is homomorphic to the case of one.
curNumItems = Math.max(curNumItems, 2);
@@ -113,8 +119,10 @@
}
// We bump the radius up between 0 and MAX_RADIUS_DILATION % as the number of items increase
- float radius = mRadius * (1 + MAX_RADIUS_DILATION * (curNumItems -
- MIN_NUM_ITEMS_IN_PREVIEW) / (MAX_NUM_ITEMS_IN_PREVIEW - MIN_NUM_ITEMS_IN_PREVIEW));
+ float radiusDilation = Flags.enableLauncherIconShapes() ? MAX_RADIUS_DILATION_SHAPES
+ : MAX_RADIUS_DILATION;
+ float radius = mRadius * (1 + radiusDilation * (curNumItems - MIN_NUM_ITEMS_IN_PREVIEW)
+ / (MAX_NUM_ITEMS_IN_PREVIEW - MIN_NUM_ITEMS_IN_PREVIEW));
double theta = theta0 + index * (2 * Math.PI / curNumItems) * direction;
float halfIconSize = (mIconSize * scaleForItem(curNumItems)) / 2;
@@ -130,7 +138,7 @@
public float scaleForItem(int numItems) {
// Scale is determined by the number of items in the preview.
final float scale;
- if (numItems <= 3) {
+ if (numItems <= 3 && !Flags.enableLauncherIconShapes()) {
scale = MAX_SCALE;
} else {
scale = MIN_SCALE;
@@ -141,4 +149,15 @@
public float getIconSize() {
return mIconSize;
}
+
+ /**
+ * Gets correct constant for icon overlap.
+ */
+ public static float getIconOverlapFactor() {
+ if (Flags.enableLauncherIconShapes()) {
+ return ICON_OVERLAP_FACTOR_SHAPES;
+ } else {
+ return ICON_OVERLAP_FACTOR;
+ }
+ }
}
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index b76e098..fb48a4d 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -1708,7 +1708,7 @@
@Override
public boolean canInterceptEventsInSystemGestureRegion() {
- return true;
+ return !mIsEditingName;
}
/**
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index d2ff2cb..2157610 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -35,6 +35,7 @@
import android.util.Property;
import android.view.View;
import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.CellLayout;
@@ -79,9 +80,10 @@
private final int mDuration;
private final int mDelay;
- private final TimeInterpolator mFolderInterpolator;
- private final TimeInterpolator mLargeFolderPreviewItemOpenInterpolator;
- private final TimeInterpolator mLargeFolderPreviewItemCloseInterpolator;
+ private final Interpolator mFolderOpenInterpolator;
+ private final Interpolator mFolderCloseInterpolator;
+ private final Interpolator mLargeFolderPreviewItemOpenInterpolator;
+ private final Interpolator mLargeFolderPreviewItemCloseInterpolator;
private final PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0);
private final FolderGridOrganizer mPreviewVerifier;
@@ -108,7 +110,9 @@
mDuration = res.getInteger(R.integer.config_materialFolderExpandDuration);
mDelay = res.getInteger(R.integer.config_folderDelay);
- mFolderInterpolator = AnimationUtils.loadInterpolator(mContext,
+ mFolderOpenInterpolator = AnimationUtils.loadInterpolator(mContext,
+ R.interpolator.standard_interpolator);
+ mFolderCloseInterpolator = AnimationUtils.loadInterpolator(mContext,
R.interpolator.standard_interpolator);
mLargeFolderPreviewItemOpenInterpolator = AnimationUtils.loadInterpolator(mContext,
R.interpolator.large_folder_preview_item_open_interpolator);
@@ -252,6 +256,7 @@
(int) (left + (startRect.right / initialScale)) + extraRadius,
(int) (startRect.bottom / initialScale) + extraRadius);
Rect contentEnd = new Rect(left, 0, left + lp.width, lp.height);
+ // animated contents of folder with the folder background
play(a, shapeDelegate.createRevealAnimator(
mFolder.getContent(), contentStart, contentEnd, finalRadius, !mIsOpening));
@@ -332,7 +337,11 @@
// We set the interpolator on all current child animators here, because the preview item
// animators may use a different interpolator.
for (Animator animator : a.getChildAnimations()) {
- animator.setInterpolator(mFolderInterpolator);
+ animator.setInterpolator(
+ mIsOpening
+ ? mFolderOpenInterpolator
+ : mFolderCloseInterpolator
+ );
}
int radiusDiff = scaledRadius - mPreviewBackground.getRadius();
@@ -467,7 +476,7 @@
return mFolder.getItemCount() > MAX_NUM_ITEMS_IN_PREVIEW;
}
- private TimeInterpolator getPreviewItemInterpolator() {
+ private Interpolator getPreviewItemInterpolator() {
if (isLargeFolder()) {
// With larger folders, we want the preview items to reach their final positions faster
// (when opening) and later (when closing) so that they appear aligned with the rest of
@@ -476,7 +485,7 @@
? mLargeFolderPreviewItemOpenInterpolator
: mLargeFolderPreviewItemCloseInterpolator;
}
- return mFolderInterpolator;
+ return mIsOpening ? mFolderOpenInterpolator : mFolderCloseInterpolator;
}
private Animator getAnimator(View view, Property property, float v1, float v2) {
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index b7b378e..0ed8787 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -17,7 +17,6 @@
package com.android.launcher3.folder;
import static com.android.launcher3.Flags.enableCursorHoverStates;
-import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
import static com.android.launcher3.folder.FolderGridOrganizer.createFolderGridOrganizer;
import static com.android.launcher3.folder.PreviewItemManager.INITIAL_ITEM_ANIMATION_DURATION;
@@ -248,7 +247,8 @@
mPreviewItemManager.recomputePreviewDrawingParams();
mBackground.getBounds(outBounds);
// The preview items go outside of the bounds of the background.
- Utilities.scaleRectAboutCenter(outBounds, ICON_OVERLAP_FACTOR);
+ Utilities.scaleRectAboutCenter(outBounds,
+ ClippedFolderIconLayoutRule.getIconOverlapFactor());
}
public float getBackgroundStrokeWidth() {
diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java
index d9c60db..77fa355 100644
--- a/src/com/android/launcher3/folder/PreviewBackground.java
+++ b/src/com/android/launcher3/folder/PreviewBackground.java
@@ -18,7 +18,6 @@
import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE;
-import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
import android.animation.Animator;
@@ -373,7 +372,7 @@
public Path getClipPath() {
mPath.reset();
- float radius = getScaledRadius() * ICON_OVERLAP_FACTOR;
+ float radius = getScaledRadius() * ClippedFolderIconLayoutRule.getIconOverlapFactor();
// Find the difference in radius so that the clip path remains centered.
float radiusDifference = radius - getRadius();
float offsetX = basePreviewOffsetX - radiusDifference;
diff --git a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
index 5a2864d..80743af 100644
--- a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
+++ b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
@@ -47,7 +47,7 @@
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.shapes.IconShapeModel;
-import com.android.launcher3.shapes.IconShapesProvider;
+import com.android.launcher3.shapes.ShapesProvider;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.RunnableList;
@@ -55,6 +55,7 @@
import java.lang.ref.WeakReference;
import java.util.Collections;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
@@ -86,10 +87,13 @@
private static final String TAG = "GridCustomizationsProvider";
+ // KEY_NAME is the name of the grid used internally while the KEY_GRID_TITLE is the translated
+ // string title of the grid.
private static final String KEY_NAME = "name";
private static final String KEY_GRID_TITLE = "grid_title";
private static final String KEY_ROWS = "rows";
private static final String KEY_COLS = "cols";
+ private static final String KEY_GRID_ICON_ID = "grid_icon_id";
private static final String KEY_PREVIEW_COUNT = "preview_count";
// is_default means if a certain option is currently set to the system
private static final String KEY_IS_DEFAULT = "is_default";
@@ -145,13 +149,23 @@
KEY_SHAPE_KEY, KEY_SHAPE_TITLE, KEY_PATH, KEY_IS_DEFAULT});
String currentShapePath =
ThemeManager.INSTANCE.get(context).getIconState().getIconMask();
- for (IconShapeModel shape : IconShapesProvider.INSTANCE.getShapes().values()) {
+ Optional<IconShapeModel> selectedShape = ShapesProvider.INSTANCE.getIconShapes()
+ .values()
+ .stream()
+ .filter(shape -> shape.getPathString().equals(currentShapePath))
+ .findFirst();
+ // Handle default for when current shape doesn't match new shapes.
+ if (selectedShape.isEmpty()) {
+ selectedShape = Optional.ofNullable(ShapesProvider.INSTANCE.getIconShapes()
+ .get("circle"));
+ }
+
+ for (IconShapeModel shape : ShapesProvider.INSTANCE.getIconShapes().values()) {
cursor.newRow()
.add(KEY_SHAPE_KEY, shape.getKey())
.add(KEY_SHAPE_TITLE, shape.getTitle())
.add(KEY_PATH, shape.getPathString())
- .add(KEY_IS_DEFAULT,
- shape.getPathString().equals(currentShapePath));
+ .add(KEY_IS_DEFAULT, shape.equals(selectedShape.get()));
}
return cursor;
} else {
@@ -161,17 +175,18 @@
case KEY_LIST_OPTIONS: {
MatrixCursor cursor = new MatrixCursor(new String[]{
KEY_NAME, KEY_GRID_TITLE, KEY_ROWS, KEY_COLS, KEY_PREVIEW_COUNT,
- KEY_IS_DEFAULT});
+ KEY_IS_DEFAULT, KEY_GRID_ICON_ID});
InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(getContext());
for (GridOption gridOption : idp.parseAllGridOptions(getContext())) {
cursor.newRow()
.add(KEY_NAME, gridOption.name)
- .add(KEY_GRID_TITLE, gridOption.title)
+ .add(KEY_GRID_TITLE, gridOption.gridTitle)
.add(KEY_ROWS, gridOption.numRows)
.add(KEY_COLS, gridOption.numColumns)
.add(KEY_PREVIEW_COUNT, 1)
.add(KEY_IS_DEFAULT, idp.numColumns == gridOption.numColumns
- && idp.numRows == gridOption.numRows);
+ && idp.numRows == gridOption.numRows)
+ .add(KEY_GRID_ICON_ID, gridOption.gridIconId);
}
return cursor;
}
diff --git a/src/com/android/launcher3/graphics/IconShape.kt b/src/com/android/launcher3/graphics/IconShape.kt
index e62936c..eac3440 100644
--- a/src/com/android/launcher3/graphics/IconShape.kt
+++ b/src/com/android/launcher3/graphics/IconShape.kt
@@ -38,6 +38,7 @@
import androidx.graphics.shapes.Morph
import androidx.graphics.shapes.RoundedPolygon
import androidx.graphics.shapes.SvgPathParser
+import androidx.graphics.shapes.rectangle
import androidx.graphics.shapes.toPath
import androidx.graphics.shapes.transformed
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider
@@ -362,8 +363,8 @@
}
/**
- * Creates a rounded rect with the start point at the center of the top edge. This ensures a
- * better animation since our shape paths also start at top-center of the bounding box.
+ * Create RoundedRect using RoundedPolygon API. Ensures smoother animation morphing between
+ * generic polygon by using [RoundedPolygon.Companion.rectangle] directly.
*/
fun createRoundedRect(
left: Float,
@@ -372,27 +373,11 @@
bottom: Float,
cornerR: Float,
) =
- RoundedPolygon(
- vertices =
- floatArrayOf(
- // x1, y1
- (left + right) / 2,
- top,
- // x2, y2
- right,
- top,
- // x3, y3
- right,
- bottom,
- // x4, y4
- left,
- bottom,
- // x5, y5
- left,
- top,
- ),
- centerX = (left + right) / 2,
- centerY = (top + bottom) / 2,
+ RoundedPolygon.rectangle(
+ width = right - left,
+ height = bottom - top,
+ centerX = (right - left) / 2,
+ centerY = (bottom - top) / 2,
rounding = CornerRounding(cornerR),
)
}
diff --git a/src/com/android/launcher3/graphics/SysUiScrim.java b/src/com/android/launcher3/graphics/SysUiScrim.java
index d59fc19..6f1d98f 100644
--- a/src/com/android/launcher3/graphics/SysUiScrim.java
+++ b/src/com/android/launcher3/graphics/SysUiScrim.java
@@ -40,6 +40,7 @@
import com.android.launcher3.util.ScreenOnTracker;
import com.android.launcher3.util.ScreenOnTracker.ScreenOnListener;
import com.android.launcher3.util.Themes;
+import com.android.launcher3.views.ActivityContext;
/**
* View scrim which draws behind hotseat and workspace
@@ -94,8 +95,8 @@
public SysUiScrim(View view) {
mRoot = view;
- mContainer = StatefulContainer.fromContext(view.getContext());
- DisplayMetrics dm = mContainer.getContext().getResources().getDisplayMetrics();
+ mContainer = ActivityContext.lookupContext(view.getContext());
+ DisplayMetrics dm = mContainer.asContext().getResources().getDisplayMetrics();
mTopMaskHeight = ResourceUtils.pxFromDp(TOP_MASK_HEIGHT_DP, dm);
mBottomMaskHeight = ResourceUtils.pxFromDp(BOTTOM_MASK_HEIGHT_DP, dm);
@@ -173,12 +174,12 @@
@Override
public void onViewAttachedToWindow(View view) {
- ScreenOnTracker.INSTANCE.get(mContainer.getContext()).addListener(mScreenOnListener);
+ ScreenOnTracker.INSTANCE.get(mContainer.asContext()).addListener(mScreenOnListener);
}
@Override
public void onViewDetachedFromWindow(View view) {
- ScreenOnTracker.INSTANCE.get(mContainer.getContext()).removeListener(mScreenOnListener);
+ ScreenOnTracker.INSTANCE.get(mContainer.asContext()).removeListener(mScreenOnListener);
}
/**
@@ -213,7 +214,7 @@
}
private Bitmap createDitheredAlphaMask(int height, @ColorInt int[] colors, float[] positions) {
- DisplayMetrics dm = mContainer.getContext().getResources().getDisplayMetrics();
+ DisplayMetrics dm = mContainer.asContext().getResources().getDisplayMetrics();
int width = ResourceUtils.pxFromDp(ALPHA_MASK_BITMAP_WIDTH_DP, dm);
Bitmap dst = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8);
Canvas c = new Canvas(dst);
diff --git a/src/com/android/launcher3/graphics/ThemeManager.kt b/src/com/android/launcher3/graphics/ThemeManager.kt
index f24c2ab..9f35e4a 100644
--- a/src/com/android/launcher3/graphics/ThemeManager.kt
+++ b/src/com/android/launcher3/graphics/ThemeManager.kt
@@ -27,7 +27,7 @@
import com.android.launcher3.dagger.LauncherAppSingleton
import com.android.launcher3.icons.IconThemeController
import com.android.launcher3.icons.mono.MonoIconThemeController
-import com.android.launcher3.shapes.IconShapesProvider
+import com.android.launcher3.shapes.ShapesProvider
import com.android.launcher3.util.DaggerSingletonObject
import com.android.launcher3.util.DaggerSingletonTracker
import com.android.launcher3.util.Executors.MAIN_EXECUTOR
@@ -94,7 +94,7 @@
private fun parseIconState(): IconState {
val shapeModel =
prefs.get(PREF_ICON_SHAPE).let { shapeOverride ->
- IconShapesProvider.shapes.values.firstOrNull { it.key == shapeOverride }
+ ShapesProvider.iconShapes.values.firstOrNull { it.key == shapeOverride }
}
val iconMask =
when {
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 6eb02ab..9a1c874 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -225,9 +225,12 @@
@UiEvent(doc = "User tapped on desktop icon on a task menu.")
LAUNCHER_SYSTEM_SHORTCUT_DESKTOP_TAP(1706),
- @UiEvent(doc = "Use tapped on external display icon on a task menu,")
+ @UiEvent(doc = "User tapped on external display icon on a task menu,")
LAUNCHER_SYSTEM_SHORTCUT_EXTERNAL_DISPLAY_TAP(1957),
+ @UiEvent(doc = "User tapped on close app on a task menu,")
+ LAUNCHER_SYSTEM_SHORTCUT_CLOSE_APP_TAP(2081),
+
@UiEvent(doc = "User tapped on pause app system shortcut.")
LAUNCHER_SYSTEM_SHORTCUT_PAUSE_TAP(521),
@@ -856,6 +859,18 @@
@UiEvent(doc = "User sets the device in Fixed Landscape")
FIXED_LANDSCAPE_TOGGLE_DISABLED(2020),
+
+ @UiEvent(doc = "Work utility view expand animation started")
+ LAUNCHER_WORK_UTILITY_VIEW_EXPAND_ANIMATION_BEGIN(2075),
+
+ @UiEvent(doc = "Work utility view expand animation ended")
+ LAUNCHER_WORK_UTILITY_VIEW_EXPAND_ANIMATION_END(2076),
+
+ @UiEvent(doc = "Work utility view shrink animation started")
+ LAUNCHER_WORK_UTILITY_VIEW_SHRINK_ANIMATION_BEGIN(2077),
+
+ @UiEvent(doc = "Work utility view shrink animation ended")
+ LAUNCHER_WORK_UTILITY_VIEW_SHRINK_ANIMATION_END(2078),
// ADD MORE
;
diff --git a/src/com/android/launcher3/model/GridSizeMigrationDBController.java b/src/com/android/launcher3/model/GridSizeMigrationDBController.java
index d256d1b..b291421 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationDBController.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationDBController.java
@@ -17,7 +17,6 @@
package com.android.launcher3.model;
import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
-import static com.android.launcher3.Flags.oneGridSpecs;
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.LauncherSettings.Favorites.TMP_TABLE;
import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
@@ -39,6 +38,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
+import com.android.launcher3.Flags;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherSettings;
@@ -127,7 +127,7 @@
return true;
}
- boolean shouldMigrateToStrictlyTallerGrid = isDestNewDb
+ boolean shouldMigrateToStrictlyTallerGrid = (Flags.oneGridSpecs() || isDestNewDb)
&& srcDeviceState.getColumns().equals(destDeviceState.getColumns())
&& srcDeviceState.getRows() < destDeviceState.getRows();
if (shouldMigrateToStrictlyTallerGrid) {
@@ -142,7 +142,7 @@
if (shouldMigrateToStrictlyTallerGrid) {
// We want to add the extra row(s) to the top of the screen, so we shift the grid
// down.
- if (oneGridSpecs()) {
+ if (Flags.oneGridSpecs()) {
shiftWorkspaceByXCells(
target.getWritableDatabase(),
(destDeviceState.getRows() - srcDeviceState.getRows()),
diff --git a/src/com/android/launcher3/model/GridSizeMigrationLogic.kt b/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
index 876919a..9586bf3 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
+++ b/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
@@ -21,7 +21,6 @@
import android.util.Log
import androidx.annotation.VisibleForTesting
import com.android.launcher3.Flags
-import com.android.launcher3.Flags.oneGridSpecs
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.LauncherPrefs.Companion.get
import com.android.launcher3.LauncherPrefs.Companion.getPrefs
@@ -81,7 +80,7 @@
// down.
if (shouldMigrateToStrtictlyTallerGrid) {
Log.d(TAG, "Migrating to strictly taller grid")
- if (oneGridSpecs()) {
+ if (Flags.oneGridSpecs()) {
shiftWorkspaceByXCells(
target.writableDatabase,
(destDeviceState.rows - srcDeviceState.rows),
@@ -374,7 +373,7 @@
srcDeviceState: DeviceGridState,
destDeviceState: DeviceGridState,
): Boolean {
- return isDestNewDb &&
+ return (Flags.oneGridSpecs() || isDestNewDb) &&
srcDeviceState.columns == destDeviceState.columns &&
srcDeviceState.rows < destDeviceState.rows
}
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 1623881..6a8d86b 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -42,6 +42,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import com.android.launcher3.Flags;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings.Favorites;
@@ -201,7 +202,7 @@
info.itemType = itemType;
info.title = getTitle();
// the fallback icon
- if (!loadIcon(info)) {
+ if (!loadIconFromDb(info)) {
info.bitmap = mIconCache.getDefaultIcon(info.user);
}
@@ -213,15 +214,15 @@
/**
* Loads the icon from the cursor and updates the {@param info} if the icon is an app resource.
*/
- protected boolean loadIcon(WorkspaceItemInfo info) {
- return createIconRequestInfo(info, false).loadWorkspaceIcon(mContext);
+ protected boolean loadIconFromDb(WorkspaceItemInfo info) {
+ return createIconRequestInfo(info, false).loadIconFromDbBlob(mContext);
}
public IconRequestInfo<WorkspaceItemInfo> createIconRequestInfo(
WorkspaceItemInfo wai, boolean useLowResIcon) {
byte[] iconBlob = itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT || restoreFlag != 0
+ || (wai.isInactiveArchive() && Flags.restoreArchivedAppIconsFromDb())
? getIconBlob() : null;
-
return new IconRequestInfo<>(wai, mActivityInfo, iconBlob, useLowResIcon);
}
@@ -312,7 +313,7 @@
info.intent = intent;
// the fallback icon
- if (!loadIcon(info)) {
+ if (!loadIconFromDb(info)) {
mIconCache.getTitleAndIcon(info, DEFAULT_LOOKUP_FLAG);
}
@@ -375,20 +376,11 @@
info.intent = newIntent;
UserCache userCache = UserCache.getInstance(mContext);
UserIconInfo userIconInfo = userCache.getUserInfo(user);
-
- if (loadIcon) {
- mIconCache.getTitleAndIcon(info, mActivityInfo,
- DEFAULT_LOOKUP_FLAG.withUseLowRes(useLowResIcon));
- if (mIconCache.isDefaultIcon(info.bitmap, user)) {
- loadIcon(info);
- }
- }
-
if (mActivityInfo != null) {
AppInfo.updateRuntimeFlagsForActivityTarget(info, mActivityInfo, userIconInfo,
ApiWrapper.INSTANCE.get(mContext), mPmHelper);
}
-
+ loadWorkspaceTitleAndIcon(useLowResIcon, loadIcon, info);
// from the db
if (TextUtils.isEmpty(info.title)) {
if (loadIcon) {
@@ -407,6 +399,32 @@
return info;
}
+ @VisibleForTesting
+ void loadWorkspaceTitleAndIcon(
+ boolean useLowResIcon,
+ boolean loadIconFromCache,
+ WorkspaceItemInfo info
+ ) {
+ boolean isPreArchived = Flags.enableSupportForArchiving()
+ && Flags.restoreArchivedAppIconsFromDb()
+ && info.isInactiveArchive();
+ boolean preArchivedIconNotFound = isPreArchived && !loadIconFromDb(info);
+ if (preArchivedIconNotFound) {
+ Log.d(TAG, "loadIconFromDb failed for pre-archived icon, loading from cache."
+ + " Component=" + info.getTargetComponent());
+ mIconCache.getTitleAndIcon(info, mActivityInfo,
+ DEFAULT_LOOKUP_FLAG.withUseLowRes(useLowResIcon));
+ } else if (loadIconFromCache && !info.isInactiveArchive()) {
+ mIconCache.getTitleAndIcon(info, mActivityInfo,
+ DEFAULT_LOOKUP_FLAG.withUseLowRes(useLowResIcon));
+ if (mIconCache.isDefaultIcon(info.bitmap, user)) {
+ Log.d(TAG, "Default Icon found in cache, trying DB instead. "
+ + " Component=" + info.getTargetComponent());
+ loadIconFromDb(info);
+ }
+ }
+ }
+
/**
* Returns a {@link ContentWriter} which can be used to update the current item.
*/
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index fee9696..c1ee69b 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -26,6 +26,7 @@
import static com.android.launcher3.LauncherSettings.Favorites.DESKTOP_ICON_FLAG;
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.icons.CacheableShortcutInfo.convertShortcutsToCacheableShortcuts;
+import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
@@ -106,11 +107,13 @@
import com.android.launcher3.widget.WidgetInflater;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CancellationException;
@@ -154,6 +157,8 @@
private Map<ShortcutKey, ShortcutInfo> mShortcutKeyToPinnedShortcuts;
private HashMap<PackageUserKey, SessionInfo> mInstallingPkgsCached;
+ private List<IconRequestInfo<WorkspaceItemInfo>> mWorkspaceIconRequestInfos = new ArrayList<>();
+
private boolean mStopped;
private final Set<PackageUserKey> mPendingPackages = new HashSet<>();
@@ -410,7 +415,7 @@
protected void loadWorkspace(
List<CacheableShortcutInfo> allDeepShortcuts,
String selection,
- LoaderMemoryLogger memoryLogger,
+ @Nullable LoaderMemoryLogger memoryLogger,
@Nullable LauncherRestoreEventLogger restoreEventLogger
) {
Trace.beginSection("LoadWorkspace");
@@ -474,13 +479,12 @@
final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>();
queryPinnedShortcutsForUnlockedUsers(context, unlockedUsers);
- List<IconRequestInfo<WorkspaceItemInfo>> iconRequestInfos = new ArrayList<>();
-
+ mWorkspaceIconRequestInfos = new ArrayList<>();
WorkspaceItemProcessor itemProcessor = new WorkspaceItemProcessor(c, memoryLogger,
mUserCache, mUserManagerState, mLauncherApps, mPendingPackages,
mShortcutKeyToPinnedShortcuts, mApp, mBgDataModel,
mWidgetProvidersMap, installingPkgs, isSdCardReady,
- widgetInflater, mPmHelper, iconRequestInfos, unlockedUsers,
+ widgetInflater, mPmHelper, mWorkspaceIconRequestInfos, unlockedUsers,
allDeepShortcuts);
if (mStopped) {
@@ -490,7 +494,7 @@
itemProcessor.processItem();
}
}
- tryLoadWorkspaceIconsInBulk(iconRequestInfos);
+ tryLoadWorkspaceIconsInBulk(mWorkspaceIconRequestInfos);
} finally {
IOUtils.closeSilently(c);
}
@@ -621,7 +625,9 @@
for (IconRequestInfo<WorkspaceItemInfo> iconRequestInfo : iconRequestInfos) {
WorkspaceItemInfo wai = iconRequestInfo.itemInfo;
if (mIconCache.isDefaultIcon(wai.bitmap, wai.user)) {
- iconRequestInfo.loadWorkspaceIcon(mApp.getContext());
+ logASplit("tryLoadWorkspaceIconsInBulk: default icon found for "
+ + wai.getTargetComponent() + ", will attempt to load from iconBlob");
+ iconRequestInfo.loadIconFromDbBlob(mApp.getContext());
}
}
} finally {
@@ -702,7 +708,7 @@
// Clear the list of apps
mBgAllAppsList.clear();
- List<IconRequestInfo<AppInfo>> iconRequestInfos = new ArrayList<>();
+ List<IconRequestInfo<AppInfo>> allAppsItemRequestInfos = new ArrayList<>();
boolean isWorkProfileQuiet = false;
boolean isPrivateProfileQuiet = false;
for (UserHandle user : profiles) {
@@ -742,15 +748,14 @@
}
}
- iconRequestInfos.add(new IconRequestInfo<>(
- appInfo, app, /* useLowResIcon= */ false));
- mBgAllAppsList.add(
- appInfo, app, false);
+ IconRequestInfo<AppInfo> iconRequestInfo = getAppInfoIconRequestInfo(
+ appInfo, app, mWorkspaceIconRequestInfos);
+ allAppsItemRequestInfos.add(iconRequestInfo);
+ mBgAllAppsList.add(appInfo, app, false);
}
allActivityList.addAll(apps);
}
-
if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
// get all active sessions and add them to the all apps list
for (PackageInstaller.SessionInfo info :
@@ -761,7 +766,7 @@
false);
if (promiseAppInfo != null) {
- iconRequestInfos.add(new IconRequestInfo<>(
+ allAppsItemRequestInfos.add(new IconRequestInfo<>(
promiseAppInfo,
/* launcherActivityInfo= */ null,
promiseAppInfo.getMatchingLookupFlag().useLowRes()));
@@ -770,9 +775,22 @@
}
Trace.beginSection("LoadAllAppsIconsInBulk");
+
try {
- mIconCache.getTitlesAndIconsInBulk(iconRequestInfos);
- iconRequestInfos.forEach(iconRequestInfo ->
+ mIconCache.getTitlesAndIconsInBulk(allAppsItemRequestInfos);
+ if (Flags.restoreArchivedAppIconsFromDb()) {
+ for (IconRequestInfo<AppInfo> iconRequestInfo : allAppsItemRequestInfos) {
+ AppInfo appInfo = iconRequestInfo.itemInfo;
+ if (mIconCache.isDefaultIcon(appInfo.bitmap, appInfo.user)) {
+ logASplit("LoadAllAppsIconsInBulk: default icon found for "
+ + appInfo.getTargetComponent()
+ + ", will attempt to load from iconBlob: "
+ + Arrays.toString(iconRequestInfo.iconBlob));
+ iconRequestInfo.loadIconFromDbBlob(mApp.getContext());
+ }
+ }
+ }
+ allAppsItemRequestInfos.forEach(iconRequestInfo ->
mBgAllAppsList.updateSectionName(iconRequestInfo.itemInfo));
} finally {
Trace.endSection();
@@ -795,6 +813,49 @@
return allActivityList;
}
+ @NonNull
+ @VisibleForTesting
+ IconRequestInfo<AppInfo> getAppInfoIconRequestInfo(
+ AppInfo appInfo,
+ LauncherActivityInfo activityInfo,
+ List<IconRequestInfo<WorkspaceItemInfo>> workspaceRequestInfos
+ ) {
+ if (Flags.restoreArchivedAppIconsFromDb()) {
+ Optional<IconRequestInfo<WorkspaceItemInfo>> workspaceIconRequest =
+ workspaceRequestInfos.stream()
+ .filter(request -> appInfo.getTargetComponent().equals(
+ request.itemInfo.getTargetComponent()))
+ .findFirst();
+
+ if (workspaceIconRequest.isPresent() && activityInfo.getApplicationInfo().isArchived) {
+ logASplit("getAppInfoIconRequestInfo:"
+ + " matching archived info found, loading icon blob into icon request."
+ + " Component=" + appInfo.getTargetComponent());
+ IconRequestInfo<AppInfo> iconRequestInfo = new IconRequestInfo<>(
+ appInfo,
+ activityInfo,
+ workspaceIconRequest.get().iconBlob,
+ false /* useLowResIcon= */
+ );
+ if (!iconRequestInfo.loadIconFromDbBlob(mApp.getContext())) {
+ Log.d(TAG, "AppInfo Icon failed to load from blob, using cache.");
+ mIconCache.getTitleAndIcon(
+ appInfo,
+ iconRequestInfo.launcherActivityInfo,
+ DEFAULT_LOOKUP_FLAG
+ );
+ }
+ return iconRequestInfo;
+ } else {
+ Log.d(TAG, "App not archived or workspace info not found"
+ + ", creating IconRequestInfo without icon blob."
+ + " Component:" + appInfo.getTargetComponent()
+ + ", isArchived: " + activityInfo.getApplicationInfo().isArchived);
+ }
+ }
+ return new IconRequestInfo<>(appInfo, activityInfo, false /* useLowResIcon= */);
+ }
+
private List<ShortcutInfo> loadDeepShortcuts() {
List<ShortcutInfo> allShortcuts = new ArrayList<>();
mBgDataModel.deepShortcutMap.clear();
diff --git a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
index de1df2e..90f11a3 100644
--- a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
+++ b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
@@ -286,7 +286,7 @@
// If the pinned deep shortcut is no longer published,
// use the last saved icon instead of the default.
val csi = CacheableShortcutInfo(pinnedShortcut, appInfoWrapper)
- iconCache.getShortcutIcon(info, csi, c::loadIcon)
+ iconCache.getShortcutIcon(info, csi, c::loadIconFromDb)
if (appInfoWrapper.isSuspended()) {
info.runtimeStatusFlags =
info.runtimeStatusFlags or ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED
diff --git a/src/com/android/launcher3/model/data/AppInfo.java b/src/com/android/launcher3/model/data/AppInfo.java
index 97b62b4..fe8fb5f 100644
--- a/src/com/android/launcher3/model/data/AppInfo.java
+++ b/src/com/android/launcher3/model/data/AppInfo.java
@@ -215,8 +215,7 @@
PackageManagerHelper.getLoadingProgress(lai),
PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING);
info.setNonResizeable(apiWrapper.isNonResizeableActivity(lai));
- info.setSupportsMultiInstance(
- pmHelper.supportsMultiInstance(lai.getComponentName()));
+ info.setSupportsMultiInstance(apiWrapper.supportsMultiInstance(lai));
return (oldProgressLevel != info.getProgressLevel())
|| (oldRuntimeStatusFlags != info.runtimeStatusFlags);
}
diff --git a/src/com/android/launcher3/model/data/IconRequestInfo.java b/src/com/android/launcher3/model/data/IconRequestInfo.java
index e77e527..42af018 100644
--- a/src/com/android/launcher3/model/data/IconRequestInfo.java
+++ b/src/com/android/launcher3/model/data/IconRequestInfo.java
@@ -64,23 +64,25 @@
}
/**
- * Loads this request's item info's title. This method should only be used on IconRequestInfos
- * for WorkspaceItemInfos.
+ * Loads this request's item info's title and icon from given iconBlob from Launcher.db.
+ * This method should only be used on {@link IconRequestInfo} for {@link WorkspaceItemInfo}
+ * or {@link AppInfo}.
*/
- public boolean loadWorkspaceIcon(Context context) {
- if (!(itemInfo instanceof WorkspaceItemInfo)) {
+ public boolean loadIconFromDbBlob(Context context) {
+ if (!(itemInfo instanceof WorkspaceItemInfo) && !(itemInfo instanceof AppInfo)) {
throw new IllegalStateException(
- "loadWorkspaceIcon should only be use for a WorkspaceItemInfos: " + itemInfo);
+ "loadIconFromDb should only be used for either WorkspaceItemInfo or AppInfo: "
+ + itemInfo);
}
try (LauncherIcons li = LauncherIcons.obtain(context)) {
- WorkspaceItemInfo info = (WorkspaceItemInfo) itemInfo;
- // Failed to load from resource, try loading from DB.
+ ItemInfoWithIcon info = itemInfo;
if (iconBlob == null) {
+ Log.d(TAG, "loadIconFromDb: icon blob null, returning. Component="
+ + info.getTargetComponent());
return false;
}
- info.bitmap = li.createIconBitmap(decodeByteArray(
- iconBlob, 0, iconBlob.length));
+ info.bitmap = li.createIconBitmap(decodeByteArray(iconBlob, 0, iconBlob.length));
return true;
} catch (Exception e) {
Log.e(TAG, "Failed to decode byte array for info " + itemInfo, e);
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 1c9db17..e52ca6d 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -40,7 +40,6 @@
import androidx.annotation.LayoutRes;
import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
@@ -579,7 +578,7 @@
/**
* Dismisses the popup if it is no longer valid
*/
- public static void dismissInvalidPopup(BaseDraggingActivity activity) {
+ public static <T extends Context & ActivityContext> void dismissInvalidPopup(T activity) {
PopupContainerWithArrow popup = getOpen(activity);
if (popup != null && (!popup.mOriginalIcon.isAttachedToWindow()
|| !ShortcutUtil.supportsShortcuts((ItemInfo) popup.mOriginalIcon.getTag()))) {
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index 9b3292d..df27b54 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.secondarydisplay;
+import static com.android.launcher3.util.WallpaperThemeManager.setWallpaperDependentTheme;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Intent;
@@ -29,7 +31,7 @@
import androidx.annotation.UiThread;
import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.BaseActivity;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
@@ -59,7 +61,6 @@
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.BaseDragLayer;
-import com.android.launcher3.widget.picker.model.WidgetPickerDataProvider;
import java.util.HashMap;
import java.util.Map;
@@ -67,7 +68,7 @@
/**
* Launcher activity for secondary displays
*/
-public class SecondaryDisplayLauncher extends BaseDraggingActivity
+public class SecondaryDisplayLauncher extends BaseActivity
implements BgDataModel.Callbacks, DragController.DragListener {
private LauncherModel mModel;
@@ -77,7 +78,6 @@
private View mAppsButton;
private PopupDataProvider mPopupDataProvider;
- private WidgetPickerDataProvider mWidgetPickerDataProvider;
private boolean mAppDrawerShown = false;
@@ -90,6 +90,7 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ setWallpaperDependentTheme(this);
mModel = LauncherAppState.getInstance(this).getModel();
mDragController = new SecondaryDragController(this);
mSecondaryDisplayPredictions = SecondaryDisplayPredictions.newInstance(this);
@@ -202,14 +203,6 @@
}
@Override
- public View getRootView() {
- return mDragLayer;
- }
-
- @Override
- protected void reapplyUi() { }
-
- @Override
public BaseDragLayer getDragLayer() {
return mDragLayer;
}
@@ -317,11 +310,6 @@
}
@Override
- public WidgetPickerDataProvider getWidgetPickerDataProvider() {
- return mWidgetPickerDataProvider;
- }
-
- @Override
public OnClickListener getItemOnClickListener() {
return this::onIconClicked;
}
diff --git a/src/com/android/launcher3/shapes/IconShapesProvider.kt b/src/com/android/launcher3/shapes/IconShapesProvider.kt
deleted file mode 100644
index 8608437..0000000
--- a/src/com/android/launcher3/shapes/IconShapesProvider.kt
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * 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.shapes
-
-import com.android.launcher3.Flags as LauncherFlags
-import com.android.systemui.shared.Flags
-
-object IconShapesProvider {
- val shapes =
- if (Flags.newCustomizationPickerUi() && LauncherFlags.enableLauncherIconShapes()) {
- mapOf(
- "arch" to
- IconShapeModel(
- key = "arch",
- title = "arch",
- pathString =
- "M50 0C77.614 0 100 22.386 100 50C100 85.471 100 86.476 99.9 87.321 99.116 93.916 93.916 99.116 87.321 99.9 86.476 100 85.471 100 83.46 100H16.54C14.529 100 13.524 100 12.679 99.9 6.084 99.116 .884 93.916 .1 87.321 0 86.476 0 85.471 0 83.46L0 50C0 22.386 22.386 0 50 0Z",
- ),
- "4_sided_cookie" to
- IconShapeModel(
- key = "4_sided_cookie",
- title = "4 sided cookie",
- pathString =
- "M39.888,4.517C46.338 7.319 53.662 7.319 60.112 4.517L63.605 3C84.733 -6.176 106.176 15.268 97 36.395L95.483 39.888C92.681 46.338 92.681 53.662 95.483 60.112L97 63.605C106.176 84.732 84.733 106.176 63.605 97L60.112 95.483C53.662 92.681 46.338 92.681 39.888 95.483L36.395 97C15.267 106.176 -6.176 84.732 3 63.605L4.517 60.112C7.319 53.662 7.319 46.338 4.517 39.888L3 36.395C -6.176 15.268 15.267 -6.176 36.395 3Z",
- ),
- "seven_sided_cookie" to
- IconShapeModel(
- key = "seven_sided_cookie",
- title = "7 sided cookie",
- pathString =
- "M35.209 4.878C36.326 3.895 36.884 3.404 37.397 3.006 44.82 -2.742 55.18 -2.742 62.603 3.006 63.116 3.404 63.674 3.895 64.791 4.878 65.164 5.207 65.351 5.371 65.539 5.529 68.167 7.734 71.303 9.248 74.663 9.932 74.902 9.981 75.147 10.025 75.637 10.113 77.1 10.375 77.831 10.506 78.461 10.66 87.573 12.893 94.032 21.011 94.176 30.412 94.186 31.062 94.151 31.805 94.08 33.293 94.057 33.791 94.045 34.04 94.039 34.285 93.958 37.72 94.732 41.121 96.293 44.18 96.404 44.399 96.522 44.618 96.759 45.056 97.467 46.366 97.821 47.021 98.093 47.611 102.032 56.143 99.727 66.266 92.484 72.24 91.983 72.653 91.381 73.089 90.177 73.961 89.774 74.254 89.572 74.4 89.377 74.548 86.647 76.626 84.477 79.353 83.063 82.483 82.962 82.707 82.865 82.936 82.671 83.395 82.091 84.766 81.8 85.451 81.51 86.033 77.31 94.44 67.977 98.945 58.801 96.994 58.166 96.859 57.451 96.659 56.019 96.259 55.54 96.125 55.3 96.058 55.063 95.998 51.74 95.154 48.26 95.154 44.937 95.998 44.699 96.058 44.46 96.125 43.981 96.259 42.549 96.659 41.834 96.859 41.199 96.994 32.023 98.945 22.69 94.44 18.49 86.033 18.2 85.451 17.909 84.766 17.329 83.395 17.135 82.936 17.038 82.707 16.937 82.483 15.523 79.353 13.353 76.626 10.623 74.548 10.428 74.4 10.226 74.254 9.823 73.961 8.619 73.089 8.017 72.653 7.516 72.24 .273 66.266 -2.032 56.143 1.907 47.611 2.179 47.021 2.533 46.366 3.241 45.056 3.478 44.618 3.596 44.399 3.707 44.18 5.268 41.121 6.042 37.72 5.961 34.285 5.955 34.04 5.943 33.791 5.92 33.293 5.849 31.805 5.814 31.062 5.824 30.412 5.968 21.011 12.427 12.893 21.539 10.66 22.169 10.506 22.9 10.375 24.363 10.113 24.853 10.025 25.098 9.981 25.337 9.932 28.697 9.248 31.833 7.734 34.461 5.529 34.649 5.371 34.836 5.207 35.209 4.878Z",
- ),
- "sunny" to
- IconShapeModel(
- key = "sunny",
- title = "sunny",
- pathString =
- "M42.846 4.873C46.084 -.531 53.916 -.531 57.154 4.873L60.796 10.951C62.685 14.103 66.414 15.647 69.978 14.754L76.851 13.032C82.962 11.5 88.5 17.038 86.968 23.149L85.246 30.022C84.353 33.586 85.897 37.315 89.049 39.204L95.127 42.846C100.531 46.084 100.531 53.916 95.127 57.154L89.049 60.796C85.897 62.685 84.353 66.414 85.246 69.978L86.968 76.851C88.5 82.962 82.962 88.5 76.851 86.968L69.978 85.246C66.414 84.353 62.685 85.898 60.796 89.049L57.154 95.127C53.916 100.531 46.084 100.531 42.846 95.127L39.204 89.049C37.315 85.898 33.586 84.353 30.022 85.246L23.149 86.968C17.038 88.5 11.5 82.962 13.032 76.851L14.754 69.978C15.647 66.414 14.103 62.685 10.951 60.796L4.873 57.154C -.531 53.916 -.531 46.084 4.873 42.846L10.951 39.204C14.103 37.315 15.647 33.586 14.754 30.022L13.032 23.149C11.5 17.038 17.038 11.5 23.149 13.032L30.022 14.754C33.586 15.647 37.315 14.103 39.204 10.951L42.846 4.873Z",
- ),
- "circle" to
- IconShapeModel(
- key = "circle",
- title = "circle",
- pathString = "M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0",
- ),
- "square" to
- IconShapeModel(
- key = "square",
- title = "square",
- pathString =
- "M53.689 0.82 L53.689 .82 C67.434 .82 74.306 .82 79.758 2.978 87.649 6.103 93.897 12.351 97.022 20.242 99.18 25.694 99.18 32.566 99.18 46.311 V53.689 C99.18 67.434 99.18 74.306 97.022 79.758 93.897 87.649 87.649 93.897 79.758 97.022 74.306 99.18 67.434 99.18 53.689 99.18 H46.311 C32.566 99.18 25.694 99.18 20.242 97.022 12.351 93.897 6.103 87.649 2.978 79.758 .82 74.306 .82 67.434 .82 53.689 L.82 46.311 C.82 32.566 .82 25.694 2.978 20.242 6.103 12.351 12.351 6.103 20.242 2.978 25.694 .82 32.566 .82 46.311 .82Z",
- ),
- )
- } else {
- mapOf(
- "circle" to
- IconShapeModel(
- key = "circle",
- title = "circle",
- pathString = "M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0",
- )
- )
- }
-}
diff --git a/src/com/android/launcher3/shapes/ShapesProvider.kt b/src/com/android/launcher3/shapes/ShapesProvider.kt
new file mode 100644
index 0000000..f1ea3a0
--- /dev/null
+++ b/src/com/android/launcher3/shapes/ShapesProvider.kt
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.shapes
+
+import com.android.launcher3.Flags as LauncherFlags
+import com.android.systemui.shared.Flags
+
+object ShapesProvider {
+ val folderShapes =
+ if (LauncherFlags.enableLauncherIconShapes()) {
+ mapOf(
+ "clover" to
+ "M 39.616 4" +
+ "C 46.224 6.87 53.727 6.87 60.335 4" +
+ "L 63.884 2.459" +
+ "C 85.178 -6.789 106.789 14.822 97.541 36.116" +
+ "L 96 39.665" +
+ "C 93.13 46.273 93.13 53.776 96 60.384" +
+ "L 97.541 63.934" +
+ "C 106.789 85.227 85.178 106.839 63.884 97.591" +
+ "L 60.335 96.049" +
+ "C 53.727 93.179 46.224 93.179 39.616 96.049" +
+ "L 36.066 97.591" +
+ "C 14.773 106.839 -6.839 85.227 2.409 63.934" +
+ "L 3.951 60.384" +
+ "C 6.821 53.776 6.821 46.273 3.951 39.665" +
+ "L 2.409 36.116" +
+ "C -6.839 14.822 14.773 -6.789 36.066 2.459" +
+ "Z",
+ "complexClover" to
+ "M 49.85 6.764" +
+ "L 50.013 6.971" +
+ "L 50.175 6.764" +
+ "C 53.422 2.635 58.309 0.207 63.538 0.207" +
+ "C 65.872 0.207 68.175 0.692 70.381 1.648" +
+ "L 71.79 2.264" +
+ "L 71.792 2.265" +
+ "A 3.46 3.46 0 0 0 74.515 2.265" +
+ "L 74.517 2.264" +
+ "L 75.926 1.652" +
+ "A 17.1 17.1 0 0 1 82.769 0.207" +
+ "C 88.495 0.207 93.824 3.117 97.022 7.989" +
+ "C 100.21 12.848 100.697 18.712 98.36 24.087" +
+ "L 97.749 25.496" +
+ "V 25.497" +
+ "A 3.45 3.45 0 0 0 97.749 28.222" +
+ "V 28.223" +
+ "L 98.36 29.632" +
+ "C 100.697 35.007 100.207 40.871 97.022 45.73" +
+ "A 17.5 17.5 0 0 1 93.264 49.838" +
+ "L 93.06 50" +
+ "L 93.264 50.162" +
+ "A 17.5 17.5 0 0 1 97.022 54.27" +
+ "C 100.21 59.129 100.697 64.993 98.36 70.368" +
+ "V 71.778" +
+ "A 3.45 3.45 0 0 0 97.749 74.503" +
+ "V 74.504" +
+ "L 98.36 75.913" +
+ "C 100.697 81.288 100.207 87.152 97.022 92.011" +
+ "C 93.824 96.883 88.495 99.793 82.769 99.793" +
+ "C 80.435 99.793 78.132 99.308 75.926 98.348" +
+ "L 74.517 97.736" +
+ "H 74.515" +
+ "A 3.5 3.5 0 0 0 73.153 97.455" +
+ "C 72.682 97.455 72.225 97.552 71.792 97.736" +
+ "H 71.79" +
+ "L 70.381 98.348" +
+ "A 17.1 17.1 0 0 1 63.538 99.793" +
+ "C 58.309 99.793 53.422 97.365 50.175 93.236" +
+ "L 50.013 93.029" +
+ "L 49.85 93.236" +
+ "C 46.603 97.365 41.717 99.793 36.488 99.793" +
+ "C 34.154 99.793 31.851 99.308 29.645 98.348" +
+ "L 28.236 97.736" +
+ "H 28.234" +
+ "A 3.5 3.5 0 0 0 26.872 97.455" +
+ "C 26.401 97.455 25.944 97.552 25.511 97.736" +
+ "H 25.509" +
+ "L 24.1 98.348" +
+ "A 17.1 17.1 0 0 1 17.257 99.793" +
+ "C 11.53 99.793 6.202 96.883 3.004 92.011" +
+ "C -0.181 87.152 -0.671 81.288 1.661 75.913" +
+ "L 2.277 74.504" +
+ "V 74.503" +
+ "A 3.45 3.45 0 0 0 2.277 71.778" +
+ "V 71.777" +
+ "L 1.665 70.368" +
+ "C -0.671 64.993 -0.181 59.129 3.004 54.274" +
+ "A 17.5 17.5 0 0 1 6.761 50.162" +
+ "L 6.965 50" +
+ "L 6.761 49.838" +
+ "A 17.5 17.5 0 0 1 3.004 45.73" +
+ "C -0.181 40.871 -0.671 35.007 1.665 29.632" +
+ "L 2.277 28.223" +
+ "V 28.222" +
+ "A 3.45 3.45 0 0 0 2.277 25.497" +
+ "V 25.496" +
+ "L 1.665 24.087" +
+ "C -0.671 18.712 -0.181 12.848 3.004 7.994" +
+ "V 7.993" +
+ "C 6.202 3.117 11.53 0.207 17.257 0.207" +
+ "C 19.591 0.207 21.894 0.692 24.1 1.652" +
+ "L 25.509 2.264" +
+ "L 25.511 2.265" +
+ "A 3.46 3.46 0 0 0 28.234 2.265" +
+ "L 28.236 2.264" +
+ "L 29.645 1.652" +
+ "A 17.1 17.1 0 0 1 36.488 0.207" +
+ "C 41.717 0.207 46.603 2.635 49.85 6.764" +
+ "Z",
+ "arch" to
+ "M 50 0" +
+ "L 72.5 0" +
+ "A 27.5 27.5 0 0 1 100 27.5" +
+ "L 100 86.67" +
+ "A 13.33 13.33 0 0 1 86.67 100" +
+ "L 13.33 100" +
+ "A 13.33 13.33 0 0 1 0 86.67" +
+ "L 0 27.5" +
+ "A 27.5 27.5 0 0 1 27.5 0" +
+ "Z",
+ "square" to
+ "M 50 0" +
+ "L 83.4 0" +
+ "A 16.6 16.6 0 0 1 100 16.6" +
+ "L 100 83.4" +
+ "A 16.6 16.6 0 0 1 83.4 100" +
+ "L 16.6 100" +
+ "A 16.6 16.6 0 0 1 0 83.4" +
+ "L 0 16.6" +
+ "A 16.6 16.6 0 0 1 16.6 0" +
+ "Z",
+ )
+ } else {
+ mapOf("circle" to "M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0")
+ }
+
+ val iconShapes =
+ if (Flags.newCustomizationPickerUi() && LauncherFlags.enableLauncherIconShapes()) {
+ mapOf(
+ "arch" to
+ IconShapeModel(
+ key = "arch",
+ title = "arch",
+ pathString =
+ "M50 0C77.614 0 100 22.386 100 50C100 85.471 100 86.476 99.9 87.321 99.116 93.916 93.916 99.116 87.321 99.9 86.476 100 85.471 100 83.46 100H16.54C14.529 100 13.524 100 12.679 99.9 6.084 99.116 .884 93.916 .1 87.321 0 86.476 0 85.471 0 83.46L0 50C0 22.386 22.386 0 50 0Z",
+ folderPathString = folderShapes["arch"]!!,
+ ),
+ "four_sided_cookie" to
+ IconShapeModel(
+ key = "four_sided_cookie",
+ title = "4 sided cookie",
+ pathString =
+ "M39.888,4.517C46.338 7.319 53.662 7.319 60.112 4.517L63.605 3C84.733 -6.176 106.176 15.268 97 36.395L95.483 39.888C92.681 46.338 92.681 53.662 95.483 60.112L97 63.605C106.176 84.732 84.733 106.176 63.605 97L60.112 95.483C53.662 92.681 46.338 92.681 39.888 95.483L36.395 97C15.267 106.176 -6.176 84.732 3 63.605L4.517 60.112C7.319 53.662 7.319 46.338 4.517 39.888L3 36.395C -6.176 15.268 15.267 -6.176 36.395 3Z",
+ folderPathString = folderShapes["complexClover"]!!,
+ ),
+ "seven_sided_cookie" to
+ IconShapeModel(
+ key = "seven_sided_cookie",
+ title = "7 sided cookie",
+ pathString =
+ "M35.209 4.878C36.326 3.895 36.884 3.404 37.397 3.006 44.82 -2.742 55.18 -2.742 62.603 3.006 63.116 3.404 63.674 3.895 64.791 4.878 65.164 5.207 65.351 5.371 65.539 5.529 68.167 7.734 71.303 9.248 74.663 9.932 74.902 9.981 75.147 10.025 75.637 10.113 77.1 10.375 77.831 10.506 78.461 10.66 87.573 12.893 94.032 21.011 94.176 30.412 94.186 31.062 94.151 31.805 94.08 33.293 94.057 33.791 94.045 34.04 94.039 34.285 93.958 37.72 94.732 41.121 96.293 44.18 96.404 44.399 96.522 44.618 96.759 45.056 97.467 46.366 97.821 47.021 98.093 47.611 102.032 56.143 99.727 66.266 92.484 72.24 91.983 72.653 91.381 73.089 90.177 73.961 89.774 74.254 89.572 74.4 89.377 74.548 86.647 76.626 84.477 79.353 83.063 82.483 82.962 82.707 82.865 82.936 82.671 83.395 82.091 84.766 81.8 85.451 81.51 86.033 77.31 94.44 67.977 98.945 58.801 96.994 58.166 96.859 57.451 96.659 56.019 96.259 55.54 96.125 55.3 96.058 55.063 95.998 51.74 95.154 48.26 95.154 44.937 95.998 44.699 96.058 44.46 96.125 43.981 96.259 42.549 96.659 41.834 96.859 41.199 96.994 32.023 98.945 22.69 94.44 18.49 86.033 18.2 85.451 17.909 84.766 17.329 83.395 17.135 82.936 17.038 82.707 16.937 82.483 15.523 79.353 13.353 76.626 10.623 74.548 10.428 74.4 10.226 74.254 9.823 73.961 8.619 73.089 8.017 72.653 7.516 72.24 .273 66.266 -2.032 56.143 1.907 47.611 2.179 47.021 2.533 46.366 3.241 45.056 3.478 44.618 3.596 44.399 3.707 44.18 5.268 41.121 6.042 37.72 5.961 34.285 5.955 34.04 5.943 33.791 5.92 33.293 5.849 31.805 5.814 31.062 5.824 30.412 5.968 21.011 12.427 12.893 21.539 10.66 22.169 10.506 22.9 10.375 24.363 10.113 24.853 10.025 25.098 9.981 25.337 9.932 28.697 9.248 31.833 7.734 34.461 5.529 34.649 5.371 34.836 5.207 35.209 4.878Z",
+ folderPathString = folderShapes["clover"]!!,
+ ),
+ "sunny" to
+ IconShapeModel(
+ key = "sunny",
+ title = "sunny",
+ pathString =
+ "M42.846 4.873C46.084 -.531 53.916 -.531 57.154 4.873L60.796 10.951C62.685 14.103 66.414 15.647 69.978 14.754L76.851 13.032C82.962 11.5 88.5 17.038 86.968 23.149L85.246 30.022C84.353 33.586 85.897 37.315 89.049 39.204L95.127 42.846C100.531 46.084 100.531 53.916 95.127 57.154L89.049 60.796C85.897 62.685 84.353 66.414 85.246 69.978L86.968 76.851C88.5 82.962 82.962 88.5 76.851 86.968L69.978 85.246C66.414 84.353 62.685 85.898 60.796 89.049L57.154 95.127C53.916 100.531 46.084 100.531 42.846 95.127L39.204 89.049C37.315 85.898 33.586 84.353 30.022 85.246L23.149 86.968C17.038 88.5 11.5 82.962 13.032 76.851L14.754 69.978C15.647 66.414 14.103 62.685 10.951 60.796L4.873 57.154C -.531 53.916 -.531 46.084 4.873 42.846L10.951 39.204C14.103 37.315 15.647 33.586 14.754 30.022L13.032 23.149C11.5 17.038 17.038 11.5 23.149 13.032L30.022 14.754C33.586 15.647 37.315 14.103 39.204 10.951L42.846 4.873Z",
+ folderPathString = folderShapes["clover"]!!,
+ ),
+ "circle" to
+ IconShapeModel(
+ key = "circle",
+ title = "circle",
+ pathString = "M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0",
+ folderPathString = folderShapes["clover"]!!,
+ ),
+ "square" to
+ IconShapeModel(
+ key = "square",
+ title = "square",
+ pathString =
+ "M53.689 0.82 L53.689 .82 C67.434 .82 74.306 .82 79.758 2.978 87.649 6.103 93.897 12.351 97.022 20.242 99.18 25.694 99.18 32.566 99.18 46.311 V53.689 C99.18 67.434 99.18 74.306 97.022 79.758 93.897 87.649 87.649 93.897 79.758 97.022 74.306 99.18 67.434 99.18 53.689 99.18 H46.311 C32.566 99.18 25.694 99.18 20.242 97.022 12.351 93.897 6.103 87.649 2.978 79.758 .82 74.306 .82 67.434 .82 53.689 L.82 46.311 C.82 32.566 .82 25.694 2.978 20.242 6.103 12.351 12.351 6.103 20.242 2.978 25.694 .82 32.566 .82 46.311 .82Z",
+ folderShapes["square"]!!,
+ ),
+ )
+ } else {
+ mapOf(
+ "circle" to
+ IconShapeModel(
+ key = "circle",
+ title = "circle",
+ pathString = "M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0",
+ )
+ )
+ }
+}
diff --git a/src/com/android/launcher3/statemanager/BaseState.java b/src/com/android/launcher3/statemanager/BaseState.java
index f6b610c..b7dd2bf 100644
--- a/src/com/android/launcher3/statemanager/BaseState.java
+++ b/src/com/android/launcher3/statemanager/BaseState.java
@@ -15,15 +15,13 @@
*/
package com.android.launcher3.statemanager;
-import android.content.Context;
-
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.views.ActivityContext;
/**
* Interface representing a state of a StatefulContainer
*/
-public interface BaseState<T extends BaseState> {
+public interface BaseState<T> {
// Flag to indicate that Launcher is non-interactive in this state
int FLAG_NON_INTERACTIVE = 1 << 0;
@@ -37,8 +35,7 @@
/**
* @return How long the animation to this state should take (or from this state to NORMAL).
*/
- <DEVICE_PROFILE_CONTEXT extends Context & ActivityContext>
- int getTransitionDuration(DEVICE_PROFILE_CONTEXT context, boolean isToState);
+ int getTransitionDuration(ActivityContext context, boolean isToState);
/**
* Returns the state to go back to from this state
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index 763f3ba..a125331 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -27,7 +27,6 @@
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
-import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
@@ -50,50 +49,49 @@
/**
* Class to manage transitions between different states for a StatefulActivity based on different
* states
- * @param STATE_TYPE Basestate used by the state manager
- * @param STATEFUL_CONTAINER container object used to manage state
+ * @param <S> Basestate used by the state manager
+ * @param <T> container object used to manage state
*/
-public class StateManager<STATE_TYPE extends BaseState<STATE_TYPE>,
- STATEFUL_CONTAINER extends Context & StatefulContainer<STATE_TYPE>> {
+public class StateManager<S extends BaseState<S>, T extends StatefulContainer<S>> {
public static final String TAG = "StateManager";
// b/279059025, b/325463989
private static final boolean DEBUG = true;
- private final AnimationState mConfig = new AnimationState();
+ private final AnimationState<S> mConfig = new AnimationState<>();
private final Handler mUiHandler;
- private final STATEFUL_CONTAINER mStatefulContainer;
- private final ArrayList<StateListener<STATE_TYPE>> mListeners = new ArrayList<>();
- private final STATE_TYPE mBaseState;
+ private final T mContainer;
+ private final ArrayList<StateListener<S>> mListeners = new ArrayList<>();
+ private final S mBaseState;
// Animators which are run on properties also controlled by state animations.
- private final AtomicAnimationFactory mAtomicAnimationFactory;
+ private final AtomicAnimationFactory<S> mAtomicAnimationFactory;
- private StateHandler<STATE_TYPE>[] mStateHandlers;
- private STATE_TYPE mState;
+ private StateHandler<S>[] mStateHandlers;
+ private S mState;
- private STATE_TYPE mLastStableState;
- private STATE_TYPE mCurrentStableState;
+ private S mLastStableState;
+ private S mCurrentStableState;
- private STATE_TYPE mRestState;
+ private S mRestState;
- public StateManager(STATEFUL_CONTAINER container, STATE_TYPE baseState) {
+ public StateManager(T container, S baseState) {
mUiHandler = new Handler(Looper.getMainLooper());
- mStatefulContainer = container;
+ mContainer = container;
mBaseState = baseState;
mState = mLastStableState = mCurrentStableState = baseState;
mAtomicAnimationFactory = container.createAtomicAnimationFactory();
}
- public STATE_TYPE getState() {
+ public S getState() {
return mState;
}
- public STATE_TYPE getTargetState() {
- return (STATE_TYPE) mConfig.targetState;
+ public S getTargetState() {
+ return mConfig.targetState;
}
- public STATE_TYPE getCurrentStableState() {
+ public S getCurrentStableState() {
return mCurrentStableState;
}
@@ -115,20 +113,20 @@
writer.println(prefix + "\tisInTransition:" + isInTransition());
}
- public StateHandler<STATE_TYPE>[] getStateHandlers() {
+ public StateHandler<S>[] getStateHandlers() {
if (mStateHandlers == null) {
- ArrayList<StateHandler<STATE_TYPE>> handlers = new ArrayList<>();
- mStatefulContainer.collectStateHandlers(handlers);
+ ArrayList<StateHandler<S>> handlers = new ArrayList<>();
+ mContainer.collectStateHandlers(handlers);
mStateHandlers = handlers.toArray(new StateHandler[handlers.size()]);
}
return mStateHandlers;
}
- public void addStateListener(StateListener listener) {
+ public void addStateListener(StateListener<S> listener) {
mListeners.add(listener);
}
- public void removeStateListener(StateListener listener) {
+ public void removeStateListener(StateListener<S> listener) {
mListeners.remove(listener);
}
@@ -136,14 +134,14 @@
* Returns true if the state changes should be animated.
*/
public boolean shouldAnimateStateChange() {
- return mStatefulContainer.shouldAnimateStateChange();
+ return mContainer.shouldAnimateStateChange();
}
/**
* @return {@code true} if the state matches the current state and there is no active
* transition to different state.
*/
- public boolean isInStableState(STATE_TYPE state) {
+ public boolean isInStableState(S state) {
return mState == state && mCurrentStableState == state
&& (mConfig.targetState == null || mConfig.targetState == state);
}
@@ -156,23 +154,23 @@
}
/**
- * @see #goToState(STATE_TYPE, boolean, AnimatorListener)
+ * @see #goToState(S, boolean, AnimatorListener)
*/
- public void goToState(STATE_TYPE state) {
+ public void goToState(S state) {
goToState(state, shouldAnimateStateChange());
}
/**
- * @see #goToState(STATE_TYPE, boolean, AnimatorListener)
+ * @see #goToState(S, boolean, AnimatorListener)
*/
- public void goToState(STATE_TYPE state, AnimatorListener listener) {
+ public void goToState(S state, AnimatorListener listener) {
goToState(state, shouldAnimateStateChange(), listener);
}
/**
- * @see #goToState(STATE_TYPE, boolean, AnimatorListener)
+ * @see #goToState(S, boolean, AnimatorListener)
*/
- public void goToState(STATE_TYPE state, boolean animated) {
+ public void goToState(S state, boolean animated) {
goToState(state, animated, 0, null);
}
@@ -183,21 +181,21 @@
* true otherwise
* @param listener any action to perform at the end of the transition, or null.
*/
- public void goToState(STATE_TYPE state, boolean animated, AnimatorListener listener) {
+ public void goToState(S state, boolean animated, AnimatorListener listener) {
goToState(state, animated, 0, listener);
}
/**
* Changes the Launcher state to the provided state after the given delay.
*/
- public void goToState(STATE_TYPE state, long delay, AnimatorListener listener) {
+ public void goToState(S state, long delay, AnimatorListener listener) {
goToState(state, true, delay, listener);
}
/**
* Changes the Launcher state to the provided state after the given delay.
*/
- public void goToState(STATE_TYPE state, long delay) {
+ public void goToState(S state, long delay) {
goToState(state, true, delay, null);
}
@@ -219,7 +217,7 @@
cancelAnimation();
}
if (mConfig.currentAnimation == null) {
- for (StateHandler handler : getStateHandlers()) {
+ for (StateHandler<S> handler : getStateHandlers()) {
handler.setState(mState);
}
if (wasInAnimation) {
@@ -230,21 +228,21 @@
/** Handles backProgress in predictive back gesture by passing it to state handlers. */
public void onBackProgressed(
- STATE_TYPE toState, @FloatRange(from = 0.0, to = 1.0) float backProgress) {
- for (StateHandler handler : getStateHandlers()) {
+ S toState, @FloatRange(from = 0.0, to = 1.0) float backProgress) {
+ for (StateHandler<S> handler : getStateHandlers()) {
handler.onBackProgressed(toState, backProgress);
}
}
/** Handles back cancelled event in predictive back gesture by passing it to state handlers. */
- public void onBackCancelled(STATE_TYPE toState) {
- for (StateHandler handler : getStateHandlers()) {
+ public void onBackCancelled(S toState) {
+ for (StateHandler<S> handler : getStateHandlers()) {
handler.onBackCancelled(toState);
}
}
private void goToState(
- STATE_TYPE state, boolean animated, long delay, AnimatorListener listener) {
+ S state, boolean animated, long delay, AnimatorListener listener) {
if (enableStateManagerProtoLog()) {
StateManagerProtoLogProxy.logGoToState(
mState, state, getTrimmedStackTrace("StateManager.goToState"));
@@ -254,7 +252,7 @@
}
animated &= areAnimatorsEnabled();
- if (mStatefulContainer.isInState(state)) {
+ if (getState() == state) {
if (mConfig.currentAnimation == null) {
// Run any queued runnable
if (listener != null) {
@@ -273,13 +271,13 @@
}
// Cancel the current animation. This will reset mState to mCurrentStableState, so store it.
- STATE_TYPE fromState = mState;
+ S fromState = mState;
cancelAnimation();
if (!animated) {
mAtomicAnimationFactory.cancelAllStateElementAnimation();
onStateTransitionStart(state);
- for (StateHandler handler : getStateHandlers()) {
+ for (StateHandler<S> handler : getStateHandlers()) {
handler.setState(state);
}
@@ -306,13 +304,13 @@
}
}
- private void goToStateAnimated(STATE_TYPE state, STATE_TYPE fromState,
+ private void goToStateAnimated(S state, S fromState,
AnimatorListener listener) {
// Since state mBaseState can be reached from multiple states, just assume that the
// transition plays in reverse and use the same duration as previous state.
mConfig.duration = state == mBaseState
- ? fromState.getTransitionDuration(mStatefulContainer, false /* isToState */)
- : state.getTransitionDuration(mStatefulContainer, true /* isToState */);
+ ? fromState.getTransitionDuration(mContainer, false /* isToState */)
+ : state.getTransitionDuration(mContainer, true /* isToState */);
prepareForAtomicAnimation(fromState, state, mConfig);
AnimatorSet animation = createAnimationToNewWorkspaceInternal(state).buildAnim();
if (listener != null) {
@@ -326,7 +324,7 @@
* - Setting interpolators for various animations included in the state transition.
* - Setting some start values (e.g. scale) for views that are hidden but about to be shown.
*/
- public void prepareForAtomicAnimation(STATE_TYPE fromState, STATE_TYPE toState,
+ public void prepareForAtomicAnimation(S fromState, S toState,
StateAnimationConfig config) {
mAtomicAnimationFactory.prepareForAtomicAnimation(fromState, toState, config);
}
@@ -335,7 +333,7 @@
* Creates an animation representing atomic transitions between the provided states
*/
public AnimatorSet createAtomicAnimation(
- STATE_TYPE fromState, STATE_TYPE toState, StateAnimationConfig config) {
+ S fromState, S toState, StateAnimationConfig config) {
if (enableStateManagerProtoLog()) {
StateManagerProtoLogProxy.logCreateAtomicAnimation(
mState, toState, getTrimmedStackTrace("StateManager.createAtomicAnimation"));
@@ -348,7 +346,7 @@
PendingAnimation builder = new PendingAnimation(config.duration);
prepareForAtomicAnimation(fromState, toState, config);
- for (StateHandler handler : mStatefulContainer.getStateManager().getStateHandlers()) {
+ for (StateHandler<S> handler : getStateHandlers()) {
handler.setStateWithAnimation(toState, config, builder);
}
return builder.buildAnim();
@@ -362,19 +360,19 @@
* accuracy.
*/
public AnimatorPlaybackController createAnimationToNewWorkspace(
- STATE_TYPE state, long duration) {
+ S state, long duration) {
return createAnimationToNewWorkspace(state, duration, 0 /* animFlags */);
}
public AnimatorPlaybackController createAnimationToNewWorkspace(
- STATE_TYPE state, long duration, @AnimationFlags int animFlags) {
+ S state, long duration, @AnimationFlags int animFlags) {
StateAnimationConfig config = new StateAnimationConfig();
config.duration = duration;
config.animFlags = animFlags;
return createAnimationToNewWorkspace(state, config);
}
- public AnimatorPlaybackController createAnimationToNewWorkspace(STATE_TYPE state,
+ public AnimatorPlaybackController createAnimationToNewWorkspace(S state,
StateAnimationConfig config) {
config.animProps |= StateAnimationConfig.USER_CONTROLLED;
cancelAnimation();
@@ -384,10 +382,10 @@
return mConfig.playbackController;
}
- private PendingAnimation createAnimationToNewWorkspaceInternal(final STATE_TYPE state) {
+ private PendingAnimation createAnimationToNewWorkspaceInternal(final S state) {
PendingAnimation builder = new PendingAnimation(mConfig.duration);
if (!mConfig.hasAnimationFlag(SKIP_ALL_ANIMATIONS)) {
- for (StateHandler handler : getStateHandlers()) {
+ for (StateHandler<S> handler : getStateHandlers()) {
handler.setStateWithAnimation(state, mConfig, builder);
}
}
@@ -396,7 +394,7 @@
return builder;
}
- private AnimatorListener createStateAnimationListener(STATE_TYPE state) {
+ private AnimatorListener createStateAnimationListener(S state) {
return new AnimationSuccessListener() {
@Override
@@ -412,9 +410,9 @@
};
}
- private void onStateTransitionStart(STATE_TYPE state) {
+ private void onStateTransitionStart(S state) {
mState = state;
- mStatefulContainer.onStateSetStart(mState);
+ mContainer.onStateSetStart(mState);
if (enableStateManagerProtoLog()) {
StateManagerProtoLogProxy.logOnStateTransitionStart(state);
@@ -426,14 +424,14 @@
}
}
- private void onStateTransitionEnd(STATE_TYPE state) {
+ private void onStateTransitionEnd(S state) {
// Only change the stable states after the transitions have finished
if (state != mCurrentStableState) {
mLastStableState = state.getHistoryForState(mCurrentStableState);
mCurrentStableState = state;
}
- mStatefulContainer.onStateSetEnd(state);
+ mContainer.onStateSetEnd(state);
if (state == mBaseState) {
setRestState(null);
}
@@ -448,7 +446,7 @@
}
}
- public STATE_TYPE getLastState() {
+ public S getLastState() {
return mLastStableState;
}
@@ -468,11 +466,11 @@
}
}
- public STATE_TYPE getRestState() {
+ public S getRestState() {
return mRestState == null ? mBaseState : mRestState;
}
- public void setRestState(STATE_TYPE restState) {
+ public void setRestState(S restState) {
mRestState = restState;
}
@@ -518,7 +516,7 @@
* @param anim The custom animation to the given state.
* @param toState The state we are animating towards.
*/
- public void setCurrentAnimation(AnimatorSet anim, STATE_TYPE toState) {
+ public void setCurrentAnimation(AnimatorSet anim, S toState) {
cancelAnimation();
setCurrentAnimation(anim);
anim.addListener(createStateAnimationListener(toState));
@@ -691,10 +689,10 @@
/** Handles backProgress in predictive back gesture for target state. */
default void onBackProgressed(
- STATE_TYPE toState, @FloatRange(from = 0.0, to = 1.0) float backProgress) {};
+ STATE_TYPE toState, @FloatRange(from = 0.0, to = 1.0) float backProgress) {}
/** Handles back cancelled event in predictive back gesture for target state. */
- default void onBackCancelled(STATE_TYPE toState) {};
+ default void onBackCancelled(STATE_TYPE toState) {}
}
public interface StateListener<STATE_TYPE> {
diff --git a/src/com/android/launcher3/statemanager/StatefulActivity.java b/src/com/android/launcher3/statemanager/StatefulActivity.java
index f21e5da..445701d 100644
--- a/src/com/android/launcher3/statemanager/StatefulActivity.java
+++ b/src/com/android/launcher3/statemanager/StatefulActivity.java
@@ -18,10 +18,8 @@
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
-import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_RECREATE_TO_UPDATE_THEME;
import static com.android.launcher3.LauncherState.FLAG_NON_INTERACTIVE;
-import android.content.Context;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Handler;
@@ -30,9 +28,8 @@
import android.view.View;
import androidx.annotation.CallSuper;
-import androidx.annotation.NonNull;
-import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.BaseActivity;
import com.android.launcher3.LauncherRootView;
import com.android.launcher3.Utilities;
import com.android.launcher3.statemanager.StateManager.StateHandler;
@@ -46,7 +43,7 @@
* @param <STATE_TYPE> Type of state object
*/
public abstract class StatefulActivity<STATE_TYPE extends BaseState<STATE_TYPE>>
- extends BaseDraggingActivity implements StatefulContainer<STATE_TYPE> {
+ extends BaseActivity implements StatefulContainer<STATE_TYPE> {
public final Handler mHandler = new Handler();
private final Runnable mHandleDeferredResume = this::handleDeferredResume;
@@ -56,7 +53,6 @@
protected Configuration mOldConfig;
private int mOldRotation;
- private boolean mRecreateToUpdateTheme = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -66,18 +62,6 @@
mOldRotation = WindowManagerProxy.INSTANCE.get(this).getRotation(this);
}
- @Override
- protected void onSaveInstanceState(@NonNull Bundle outState) {
- outState.putBoolean(RUNTIME_STATE_RECREATE_TO_UPDATE_THEME, mRecreateToUpdateTheme);
- super.onSaveInstanceState(outState);
- }
-
- @Override
- protected void recreateToUpdateTheme() {
- mRecreateToUpdateTheme = true;
- super.recreateToUpdateTheme();
- }
-
/**
* Create handlers to control the property changes for this activity
*/
@@ -214,11 +198,6 @@
mOldRotation = rotation;
}
- @Override
- public Context getContext() {
- return this;
- }
-
/**
* Logic for when device configuration changes (rotation, screen size change, multi-window,
* etc.)
diff --git a/src/com/android/launcher3/statemanager/StatefulContainer.java b/src/com/android/launcher3/statemanager/StatefulContainer.java
index b10af0a..83a2fdc 100644
--- a/src/com/android/launcher3/statemanager/StatefulContainer.java
+++ b/src/com/android/launcher3/statemanager/StatefulContainer.java
@@ -20,8 +20,6 @@
import static com.android.launcher3.LauncherState.FLAG_CLOSE_POPUPS;
import static com.android.launcher3.statemanager.BaseState.FLAG_NON_INTERACTIVE;
-import android.content.Context;
-import android.content.ContextWrapper;
import android.content.res.Configuration;
import androidx.annotation.CallSuper;
@@ -40,23 +38,6 @@
ActivityContext {
/**
- * Returns an instance of an implementation of StatefulContainer
- *
- * @param context will find instance of StatefulContainer from given context.
- */
- static <T extends StatefulContainer> T fromContext(Context context) {
- if (context instanceof StatefulContainer) {
- return (T) context;
- } else if (context instanceof ContextWrapper) {
- return fromContext(((ContextWrapper) context).getBaseContext());
- } else {
- throw new IllegalArgumentException("Cannot find StatefulContainer in parent tree");
- }
- }
-
- Context getContext();
-
- /**
* Creates a factory for atomic state animations
*/
default StateManager.AtomicAnimationFactory<STATE_TYPE> createAtomicAnimationFactory() {
diff --git a/src/com/android/launcher3/states/EditModeState.kt b/src/com/android/launcher3/states/EditModeState.kt
index 6ff47ae..268a373 100644
--- a/src/com/android/launcher3/states/EditModeState.kt
+++ b/src/com/android/launcher3/states/EditModeState.kt
@@ -36,11 +36,7 @@
FLAG_WORKSPACE_HAS_BACKGROUNDS)
}
- override fun <T> getTransitionDuration(context: T, isToState: Boolean): Int where
- T : Context?,
- T : ActivityContext? {
- return 150
- }
+ override fun getTransitionDuration(context: ActivityContext, isToState: Boolean) = 150
override fun <T> getDepthUnchecked(context: T): Float where T : Context?, T : ActivityContext? {
if (enableScalingRevealHomeAnimation()) {
diff --git a/src/com/android/launcher3/states/HintState.java b/src/com/android/launcher3/states/HintState.java
index bf2fb30..ed22d39 100644
--- a/src/com/android/launcher3/states/HintState.java
+++ b/src/com/android/launcher3/states/HintState.java
@@ -26,6 +26,7 @@
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.util.Themes;
+import com.android.launcher3.views.ActivityContext;
/**
* Scale down workspace/hotseat to hint at going to either overview (on pause) or first home screen.
@@ -46,7 +47,7 @@
}
@Override
- public int getTransitionDuration(Context context, boolean isToState) {
+ public int getTransitionDuration(ActivityContext context, boolean isToState) {
return 80;
}
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index 2e57ed8..15e6c61 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -24,6 +24,7 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Workspace;
+import com.android.launcher3.views.ActivityContext;
/**
* Definition for spring loaded state used during drag and drop.
@@ -41,7 +42,7 @@
}
@Override
- public int getTransitionDuration(Context context, boolean isToState) {
+ public int getTransitionDuration(ActivityContext context, boolean isToState) {
return 150;
}
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 3817563..4509bae 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -16,6 +16,7 @@
package com.android.launcher3.touch;
import static com.android.app.animation.Interpolators.scrollInterpolatorForVelocity;
+import static com.android.launcher3.Flags.enableMouseInteractionChanges;
import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
import static com.android.launcher3.LauncherAnimUtils.TABLET_BOTTOM_SHEET_SUCCESS_TRANSITION_PROGRESS;
import static com.android.launcher3.LauncherAnimUtils.newCancelListener;
@@ -33,6 +34,7 @@
import android.animation.Animator.AnimatorListener;
import android.animation.ValueAnimator;
+import android.view.InputDevice;
import android.view.MotionEvent;
import com.android.launcher3.Launcher;
@@ -107,7 +109,9 @@
ignoreSlopWhenSettling = true;
} else {
directionsToDetectScroll = getSwipeDirection();
- if (directionsToDetectScroll == 0) {
+ boolean ignoreMouseScroll = ev.getSource() == InputDevice.SOURCE_MOUSE
+ && enableMouseInteractionChanges();
+ if (directionsToDetectScroll == 0 || ignoreMouseScroll) {
mNoIntercept = true;
return false;
}
diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
index b69bc17..d72e6f9 100644
--- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java
+++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
@@ -21,6 +21,7 @@
import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
+import static com.android.launcher3.Flags.enableMouseInteractionChanges;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_CLOSE_TAP_OUTSIDE;
@@ -31,6 +32,7 @@
import android.graphics.Rect;
import android.view.GestureDetector;
import android.view.HapticFeedbackConstants;
+import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
@@ -41,7 +43,6 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.Workspace;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.testing.TestLogging;
@@ -193,6 +194,10 @@
@Override
public void onLongPress(MotionEvent event) {
+ if (event.getSource() == InputDevice.SOURCE_MOUSE && enableMouseInteractionChanges()) {
+ // Stop mouse long press events from showing the menu.
+ return;
+ }
maybeShowMenu();
}
diff --git a/src/com/android/launcher3/util/ApiWrapper.java b/src/com/android/launcher3/util/ApiWrapper.java
index 48e033a..56337b0 100644
--- a/src/com/android/launcher3/util/ApiWrapper.java
+++ b/src/com/android/launcher3/util/ApiWrapper.java
@@ -59,10 +59,13 @@
LauncherAppComponent::getApiWrapper);
protected final Context mContext;
+ private final String[] mLegacyMultiInstanceSupportedApps;
@Inject
public ApiWrapper(@ApplicationContext Context context) {
mContext = context;
+ mLegacyMultiInstanceSupportedApps = context.getResources().getStringArray(
+ com.android.launcher3.R.array.config_appsSupportMultiInstancesSplit);
}
/**
@@ -157,12 +160,31 @@
/**
* Checks if an activity is flagged as non-resizeable.
*/
- public boolean isNonResizeableActivity(LauncherActivityInfo lai) {
- // Overridden in quickstep
+ public boolean isNonResizeableActivity(@NonNull LauncherActivityInfo lai) {
+ // Overridden in Quickstep
return false;
}
/**
+ * Checks if an activity supports multi-instance.
+ */
+ public boolean supportsMultiInstance(@NonNull LauncherActivityInfo lai) {
+ // Check app multi-instance properties after V
+ if (!Utilities.ATLEAST_V) {
+ return false;
+ }
+
+ // Check the legacy hardcoded allowlist first
+ for (String pkg : mLegacyMultiInstanceSupportedApps) {
+ if (pkg.equals(lai.getComponentName().getPackageName())) {
+ return true;
+ }
+ }
+
+ // Overridden in Quickstep
+ return false;
+ }
+ /**
* Starts an Activity which can be used to set this Launcher as the HOME app, via a consent
* screen. In case the consent screen cannot be shown, or the user does not set current Launcher
* as HOME app, a toast asking the user to do the latter is shown.
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 4b60d98..3d01f24 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -16,8 +16,6 @@
package com.android.launcher3.util;
-import static android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI;
-
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
import android.content.ActivityNotFoundException;
@@ -41,7 +39,6 @@
import com.android.launcher3.PendingAddItemInfo;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
import com.android.launcher3.dagger.ApplicationContext;
import com.android.launcher3.dagger.LauncherAppSingleton;
import com.android.launcher3.dagger.LauncherBaseAppComponent;
@@ -77,15 +74,11 @@
@NonNull
private final LauncherApps mLauncherApps;
- private final String[] mLegacyMultiInstanceSupportedApps;
-
@Inject
public PackageManagerHelper(@ApplicationContext final Context context) {
mContext = context;
mPm = context.getPackageManager();
mLauncherApps = Objects.requireNonNull(context.getSystemService(LauncherApps.class));
- mLegacyMultiInstanceSupportedApps = mContext.getResources().getStringArray(
- R.array.config_appsSupportMultiInstancesSplit);
}
/**
@@ -193,39 +186,6 @@
}
/**
- * Returns whether the given component or its application has the multi-instance property set.
- */
- public boolean supportsMultiInstance(@NonNull ComponentName component) {
- // Check the legacy hardcoded allowlist first
- for (String pkg : mLegacyMultiInstanceSupportedApps) {
- if (pkg.equals(component.getPackageName())) {
- return true;
- }
- }
-
- // Check app multi-instance properties after V
- if (!Utilities.ATLEAST_V) {
- return false;
- }
-
- try {
- // Check if the component has the multi-instance property
- return mPm.getProperty(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, component)
- .getBoolean();
- } catch (PackageManager.NameNotFoundException e1) {
- try {
- // Check if the application has the multi-instance property
- return mPm.getProperty(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI,
- component.getPackageName())
- .getBoolean();
- } catch (PackageManager.NameNotFoundException e2) {
- // Fall through
- }
- }
- return false;
- }
-
- /**
* Returns whether two apps should be considered the same for multi-instance purposes, which
* requires additional checks to ensure they can be started as multiple instances.
*/
diff --git a/src/com/android/launcher3/util/WallpaperThemeManager.kt b/src/com/android/launcher3/util/WallpaperThemeManager.kt
new file mode 100644
index 0000000..c48ef07
--- /dev/null
+++ b/src/com/android/launcher3/util/WallpaperThemeManager.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.app.Activity
+import android.content.ComponentCallbacks
+import android.content.res.Configuration
+import android.os.Bundle
+import com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_RECREATE_TO_UPDATE_THEME
+import com.android.launcher3.R
+
+/** Utility class to manage activity's theme in case it is wallpaper dependent */
+class WallpaperThemeManager private constructor(private val activity: Activity) :
+ OnColorHintListener, ActivityLifecycleCallbacksAdapter, ComponentCallbacks {
+
+ private var themeRes: Int = R.style.AppTheme
+
+ private var recreateToUpdateTheme = false
+
+ init {
+ // Update theme
+ WallpaperColorHints.get(activity).registerOnColorHintsChangedListener(this)
+ val expectedTheme = Themes.getActivityThemeRes(activity)
+ if (expectedTheme != themeRes) {
+ themeRes = expectedTheme
+ activity.setTheme(expectedTheme)
+ }
+
+ activity.registerActivityLifecycleCallbacks(this)
+ activity.registerComponentCallbacks(this)
+ }
+
+ override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) =
+ bundle.putBoolean(RUNTIME_STATE_RECREATE_TO_UPDATE_THEME, recreateToUpdateTheme)
+
+ override fun onActivityDestroyed(unused: Activity) =
+ WallpaperColorHints.get(activity).unregisterOnColorsChangedListener(this)
+
+ override fun onConfigurationChanged(config: Configuration) = updateTheme()
+
+ override fun onLowMemory() {}
+
+ override fun onColorHintsChanged(colorHints: Int) = updateTheme()
+
+ private fun updateTheme() {
+ if (themeRes != Themes.getActivityThemeRes(activity)) {
+ recreateToUpdateTheme = true
+ activity.recreate()
+ }
+ }
+
+ companion object {
+
+ /**
+ * Sets a wallpaper dependent theme on this activity. The activity is automatically
+ * recreated when a wallpaper change can potentially change the theme.
+ */
+ @JvmStatic
+ fun Activity.setWallpaperDependentTheme() {
+ if (!isDestroyed) {
+ WallpaperThemeManager(this)
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index b164b7f..81968fc 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -62,6 +62,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.DropTargetHandler;
+import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -166,6 +167,11 @@
return false;
}
+ /** Returns the RootView */
+ default View getRootView() {
+ return getDragLayer();
+ }
+
/**
* The root view to support drag-and-drop and popup support.
*/
@@ -410,7 +416,8 @@
View v, Intent intent, @Nullable ItemInfo item) {
Preconditions.assertUIThread();
Context context = (Context) this;
- if (isAppBlockedForSafeMode() && !new ApplicationInfoWrapper(context, intent).isSystem()) {
+ if (LauncherAppState.getInstance(context).isSafeModeEnabled()
+ && !new ApplicationInfoWrapper(context, intent).isSystem()) {
Toast.makeText(context, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
return null;
}
@@ -456,11 +463,6 @@
return null;
}
- /** Returns {@code true} if an app launch is blocked due to safe mode. */
- default boolean isAppBlockedForSafeMode() {
- return false;
- }
-
/**
* Creates and logs a new app launch event.
*/
@@ -476,6 +478,7 @@
* @param v View initiating a launch.
* @param item Item associated with the view.
*/
+ @NonNull
default ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) {
int left = 0, top = 0;
int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
@@ -525,6 +528,11 @@
return false;
}
+ /** Returns the current ActivityContext as context */
+ default Context asContext() {
+ return (Context) this;
+ }
+
/**
* Returns the ActivityContext associated with the given Context, or throws an exception if
* the Context is not associated with any ActivityContext.
diff --git a/src/com/android/launcher3/widget/LauncherWidgetHolder.java b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
index f499fca..78197e2 100644
--- a/src/com/android/launcher3/widget/LauncherWidgetHolder.java
+++ b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
@@ -41,7 +41,6 @@
import androidx.annotation.WorkerThread;
import com.android.launcher3.BaseActivity;
-import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.model.data.ItemInfo;
@@ -50,6 +49,7 @@
import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.util.ResourceBasedOverride;
import com.android.launcher3.util.SafeCloseable;
+import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.widget.custom.CustomWidgetManager;
import java.util.ArrayList;
@@ -218,8 +218,7 @@
* @param widgetId The ID of the widget
* @param requestCode The request code
*/
- public void startConfigActivity(@NonNull BaseDraggingActivity activity, int widgetId,
- int requestCode) {
+ public void startConfigActivity(@NonNull BaseActivity activity, int widgetId, int requestCode) {
if (!WIDGETS_ENABLED) {
sendActionCancelled(activity, requestCode);
return;
@@ -245,7 +244,7 @@
* the configuration of the {@code widgetId} app widget, or null of options cannot be produced.
*/
@Nullable
- protected Bundle getConfigurationActivityOptions(@NonNull BaseDraggingActivity activity,
+ protected Bundle getConfigurationActivityOptions(@NonNull ActivityContext activity,
int widgetId) {
LauncherAppWidgetHostView view = mViews.get(widgetId);
if (view == null) {
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 4811a17..7a27bf4 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -153,17 +153,19 @@
mWidgetAddButton = findViewById(R.id.widget_add_button);
if (enableWidgetTapToAdd()) {
-
setAccessibilityDelegate(new AccessibilityDelegate() {
@Override
public void onInitializeAccessibilityNodeInfo(View host,
AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
- String accessibilityLabel = getResources().getString(mWidgetAddButton.isShown()
- ? R.string.widget_cell_tap_to_hide_add_button_label
- : R.string.widget_cell_tap_to_show_add_button_label);
- info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK,
- accessibilityLabel));
+ if (hasOnClickListeners()) {
+ String accessibilityLabel = getResources().getString(
+ mWidgetAddButton.isShown()
+ ? R.string.widget_cell_tap_to_hide_add_button_label
+ : R.string.widget_cell_tap_to_show_add_button_label);
+ info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK,
+ accessibilityLabel));
+ }
}
});
mWidgetAddButton.setVisibility(INVISIBLE);
diff --git a/src_no_quickstep/com/android/launcher3/uioverrides/states/AllAppsState.java b/src_no_quickstep/com/android/launcher3/uioverrides/states/AllAppsState.java
index 9865516..03a5535 100644
--- a/src_no_quickstep/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/src_no_quickstep/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -18,8 +18,6 @@
import static com.android.app.animation.Interpolators.DECELERATE;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_ALLAPPS;
-import android.content.Context;
-
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
@@ -31,8 +29,6 @@
*/
public class AllAppsState extends LauncherState {
- private static final float PARALLAX_COEFFICIENT = .125f;
-
private static final int STATE_FLAGS = FLAG_WORKSPACE_INACCESSIBLE;
public AllAppsState(int id) {
@@ -40,8 +36,7 @@
}
@Override
- public <DEVICE_PROFILE_CONTEXT extends Context & ActivityContext>
- int getTransitionDuration(DEVICE_PROFILE_CONTEXT context, boolean isToState) {
+ public int getTransitionDuration(ActivityContext context, boolean isToState) {
return isToState
? context.getDeviceProfile().allAppsOpenDuration
: context.getDeviceProfile().allAppsCloseDuration;
diff --git a/src_no_quickstep/com/android/launcher3/uioverrides/states/OverviewState.java b/src_no_quickstep/com/android/launcher3/uioverrides/states/OverviewState.java
index 7a228c4..532a338 100644
--- a/src_no_quickstep/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/src_no_quickstep/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -17,12 +17,11 @@
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
-import android.content.Context;
-
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.util.Themes;
+import com.android.launcher3.views.ActivityContext;
/**
* Definition for overview state
@@ -34,7 +33,7 @@
}
@Override
- public int getTransitionDuration(Context context, boolean isToState) {
+ public int getTransitionDuration(ActivityContext context, boolean isToState) {
return 250;
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefs.kt b/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefs.kt
index 7573d2f..5e1e548 100644
--- a/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefs.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefs.kt
@@ -35,6 +35,5 @@
MODE_PRIVATE,
)
- override val Item.sharedPrefs: SharedPreferences
- get() = backingPrefs
+ override fun getSharedPrefs(item: Item): SharedPreferences = backingPrefs
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/ProxyPrefsTest.kt b/tests/multivalentTests/src/com/android/launcher3/ProxyPrefsTest.kt
new file mode 100644
index 0000000..54f6f63
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/ProxyPrefsTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3
+
+import android.content.Context.MODE_PRIVATE
+import android.platform.uiautomatorhelpers.DeviceHelpers.context
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.LauncherPrefs.Companion.backedUpItem
+import com.google.common.truth.Truth.assertThat
+import java.util.UUID
+import org.junit.After
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ProxyPrefsTest {
+
+ private val prefName = "pref-test-" + UUID.randomUUID().toString()
+
+ private val proxyPrefs by lazy {
+ ProxyPrefs(
+ context,
+ context.getSharedPreferences(prefName, MODE_PRIVATE).apply { edit().clear().commit() },
+ )
+ }
+ private val launcherPrefs by lazy { LauncherPrefs(context) }
+
+ @After
+ fun tearDown() {
+ context.deleteSharedPreferences(prefName)
+ }
+
+ @Test
+ fun `returns fallback value if present`() {
+ launcherPrefs.putSync(TEST_ENTRY.to("new_value"))
+ assertThat(proxyPrefs.get(TEST_ENTRY)).isEqualTo("new_value")
+ }
+
+ @Test
+ fun `returns default value if not present`() {
+ launcherPrefs.removeSync(TEST_ENTRY)
+ assertThat(proxyPrefs.get(TEST_ENTRY)).isEqualTo("default_value")
+ }
+
+ @Test
+ fun `returns overridden value if present`() {
+ launcherPrefs.putSync(TEST_ENTRY.to("new_value"))
+ proxyPrefs.putSync(TEST_ENTRY.to("overridden_value"))
+ assertThat(proxyPrefs.get(TEST_ENTRY)).isEqualTo("overridden_value")
+ }
+
+ @Test
+ fun `value not present when removed`() {
+ launcherPrefs.putSync(TEST_ENTRY.to("new_value"))
+ proxyPrefs.removeSync(TEST_ENTRY)
+ assertThat(proxyPrefs.has(TEST_ENTRY)).isFalse()
+ }
+
+ @Test
+ fun `returns default if removed`() {
+ launcherPrefs.putSync(TEST_ENTRY.to("new_value"))
+ proxyPrefs.removeSync(TEST_ENTRY)
+ assertThat(proxyPrefs.get(TEST_ENTRY)).isEqualTo("default_value")
+ }
+
+ @Test
+ fun `value present on init`() {
+ launcherPrefs.putSync(TEST_ENTRY.to("new_value"))
+ assertThat(proxyPrefs.has(TEST_ENTRY)).isTrue()
+ }
+
+ @Test
+ fun `value absent on init`() {
+ launcherPrefs.removeSync(TEST_ENTRY)
+ assertThat(proxyPrefs.has(TEST_ENTRY)).isFalse()
+ }
+
+ companion object {
+
+ val TEST_ENTRY =
+ backedUpItem("test_prefs", "default_value", EncryptionType.DEVICE_PROTECTED)
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/multivalentTests/src/com/android/launcher3/model/LoaderCursorTest.java
index 63359ec..11047fb 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/LoaderCursorTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -16,6 +16,9 @@
package com.android.launcher3.model;
+import static android.graphics.BitmapFactory.decodeByteArray;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+
import static androidx.test.InstrumentationRegistry.getContext;
import static com.android.launcher3.LauncherSettings.Favorites.APPWIDGET_ID;
@@ -40,8 +43,11 @@
import static com.android.launcher3.LauncherSettings.Favorites.SPANY;
import static com.android.launcher3.LauncherSettings.Favorites.TITLE;
import static com.android.launcher3.LauncherSettings.Favorites._ID;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED;
import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
@@ -52,13 +58,19 @@
import android.content.Context;
import android.content.Intent;
import android.database.MatrixCursor;
+import android.graphics.Bitmap;
import android.os.Process;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.launcher3.Flags;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.Executors;
@@ -67,6 +79,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -77,6 +90,9 @@
@RunWith(AndroidJUnit4.class)
public class LoaderCursorTest {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
private LauncherModelHelper mModelHelper;
private LauncherAppState mApp;
private PackageManagerHelper mPmHelper;
@@ -87,6 +103,12 @@
private LoaderCursor mLoaderCursor;
+ private static byte[] sTestBlob = new byte[] {
+ -119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 1, 0, 0, 0, 1,
+ 8, 4, 0, 0, 0, -75, 28, 12, 2, 0, 0, 0, 11, 73, 68, 65, 84, 120, -38, 99, 100, 96, 0,
+ 0, 0, 6, 0, 2, 48, -127, -48, 47, 0, 0, 0, 0, 73, 69, 78, 68, -82, 66, 96, -126
+ };
+
@Before
public void setup() {
mModelHelper = new LauncherModelHelper();
@@ -119,7 +141,8 @@
.add(PROFILE_ID, 0)
.add(ITEM_TYPE, itemType)
.add(TITLE, title)
- .add(CONTAINER, CONTAINER_DESKTOP);
+ .add(CONTAINER, CONTAINER_DESKTOP)
+ .add(ICON, sTestBlob);
}
@Test
@@ -161,7 +184,12 @@
@Test
public void loadSimpleShortcut() {
- initCursor(ITEM_TYPE_DEEP_SHORTCUT, "my-shortcut");
+ mCursor.newRow()
+ .add(_ID, 1)
+ .add(PROFILE_ID, 0)
+ .add(ITEM_TYPE, ITEM_TYPE_DEEP_SHORTCUT)
+ .add(TITLE, "my-shortcut")
+ .add(CONTAINER, CONTAINER_DESKTOP);
assertTrue(mLoaderCursor.moveToNext());
WorkspaceItemInfo info = mLoaderCursor.loadSimpleWorkspaceItem();
@@ -223,6 +251,68 @@
newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 3), true));
}
+ @Test
+ @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB)
+ public void ifArchivedWithFlag_whenloadWorkspaceTitleAndIcon_thenLoadIconFromDb() {
+ // Given
+ initCursor(ITEM_TYPE_APPLICATION, "title");
+ assertTrue(mLoaderCursor.moveToNext());
+ WorkspaceItemInfo itemInfo = new WorkspaceItemInfo();
+ itemInfo.bitmap = null;
+ itemInfo.runtimeStatusFlags |= FLAG_ARCHIVED;
+ Bitmap expectedBitmap = LauncherIcons.obtain(mContext)
+ .createIconBitmap(decodeByteArray(sTestBlob, 0, sTestBlob.length))
+ .icon;
+ // When
+ mLoaderCursor.loadWorkspaceTitleAndIcon(false, true, itemInfo);
+ // Then
+ assertThat(itemInfo.bitmap.icon).isNotNull();
+ assertThat(itemInfo.bitmap.icon.sameAs(expectedBitmap)).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB)
+ public void ifArchivedWithFlag_whenLoadIconFromDb_thenLoadIconFromBlob() {
+ // Given
+ initCursor(ITEM_TYPE_APPLICATION, "title");
+ assertTrue(mLoaderCursor.moveToNext());
+ WorkspaceItemInfo itemInfo = new WorkspaceItemInfo();
+ itemInfo.runtimeStatusFlags |= FLAG_ARCHIVED;
+ // Then
+ assertTrue(mLoaderCursor.loadIconFromDb(itemInfo));
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB)
+ public void ifArchivedWithoutFlag_whenLoadWorkspaceTitleAndIcon_thenDoNotLoadFromDb() {
+ // Given
+ initCursor(ITEM_TYPE_APPLICATION, "title");
+ assertTrue(mLoaderCursor.moveToNext());
+ WorkspaceItemInfo itemInfo = new WorkspaceItemInfo();
+ itemInfo.bitmap = null;
+ itemInfo.runtimeStatusFlags |= FLAG_ARCHIVED;
+ Intent intent = new Intent();
+ intent.setComponent(new ComponentName("package", "class"));
+ itemInfo.intent = intent;
+ // When
+ mLoaderCursor.loadWorkspaceTitleAndIcon(false, false, itemInfo);
+ // Then
+ assertThat(itemInfo.bitmap).isNull();
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB)
+ public void ifArchivedWithoutFlag_whenLoadIconFromDb_thenDoNotLoadFromBlob() {
+ // Given
+ initCursor(ITEM_TYPE_APPLICATION, "title");
+ assertTrue(mLoaderCursor.moveToNext());
+ WorkspaceItemInfo itemInfo = new WorkspaceItemInfo();
+ itemInfo.runtimeStatusFlags |= FLAG_ARCHIVED;
+ // Then
+ assertFalse(mLoaderCursor.loadIconFromDb(itemInfo));
+ }
+
+
private ItemInfo newItemInfo(int cellX, int cellY, int spanX, int spanY,
int container, int screenId) {
ItemInfo info = new ItemInfo();
diff --git a/tests/multivalentTests/src/com/android/launcher3/shapes/IconShapesProviderTest.kt b/tests/multivalentTests/src/com/android/launcher3/shapes/ShapesProviderTest.kt
similarity index 63%
rename from tests/multivalentTests/src/com/android/launcher3/shapes/IconShapesProviderTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/shapes/ShapesProviderTest.kt
index 234e050..2b8896e 100644
--- a/tests/multivalentTests/src/com/android/launcher3/shapes/IconShapesProviderTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/shapes/ShapesProviderTest.kt
@@ -30,14 +30,14 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
-class IconShapesProviderTest {
+class ShapesProviderTest {
@get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule()
@Test
@EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
fun `verify valid path arch`() {
- IconShapesProvider.shapes["arch"]?.apply {
+ ShapesProvider.iconShapes["arch"]?.apply {
GenericPathShape(pathString)
PathParser.createPathFromPathData(pathString)
}
@@ -46,7 +46,7 @@
@Test
@EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
fun `verify valid path 4_sided_cookie`() {
- IconShapesProvider.shapes["4_sided_cookie"]?.apply {
+ ShapesProvider.iconShapes["4_sided_cookie"]?.apply {
GenericPathShape(pathString)
PathParser.createPathFromPathData(pathString)
}
@@ -55,7 +55,7 @@
@Test
@EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
fun `verify valid path seven_sided_cookie`() {
- IconShapesProvider.shapes["seven_sided_cookie"]?.apply {
+ ShapesProvider.iconShapes["seven_sided_cookie"]?.apply {
GenericPathShape(pathString)
PathParser.createPathFromPathData(pathString)
}
@@ -64,7 +64,7 @@
@Test
@EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
fun `verify valid path sunny`() {
- IconShapesProvider.shapes["sunny"]?.apply {
+ ShapesProvider.iconShapes["sunny"]?.apply {
GenericPathShape(pathString)
PathParser.createPathFromPathData(pathString)
}
@@ -73,7 +73,7 @@
@Test
@EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
fun `verify valid path circle`() {
- IconShapesProvider.shapes["circle"]?.apply {
+ ShapesProvider.iconShapes["circle"]?.apply {
GenericPathShape(pathString)
PathParser.createPathFromPathData(pathString)
}
@@ -82,7 +82,43 @@
@Test
@EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
fun `verify valid path square`() {
- IconShapesProvider.shapes["square"]?.apply {
+ ShapesProvider.iconShapes["square"]?.apply {
+ GenericPathShape(pathString)
+ PathParser.createPathFromPathData(pathString)
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+ fun `verify valid folder path clover`() {
+ ShapesProvider.folderShapes["clover"]?.let { pathString ->
+ GenericPathShape(pathString)
+ PathParser.createPathFromPathData(pathString)
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+ fun `verify valid folder path complexClover`() {
+ ShapesProvider.folderShapes["complexClover"]?.let { pathString ->
+ GenericPathShape(pathString)
+ PathParser.createPathFromPathData(pathString)
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+ fun `verify valid folder path arch`() {
+ ShapesProvider.folderShapes["arch"]?.let { pathString ->
+ GenericPathShape(pathString)
+ PathParser.createPathFromPathData(pathString)
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+ fun `verify valid folder path square`() {
+ ShapesProvider.folderShapes["square"]?.let { pathString ->
GenericPathShape(pathString)
PathParser.createPathFromPathData(pathString)
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java b/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java
index faf6b91..5c326f9 100644
--- a/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java
@@ -27,7 +27,6 @@
import static com.android.launcher3.Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING;
import static com.android.launcher3.Flags.FLAG_USE_NEW_ICON_FOR_ARCHIVED_APPS;
import static com.android.launcher3.LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE;
-import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -81,8 +80,6 @@
import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
-import java.util.concurrent.CountDownLatch;
-
/**
* Unit tests for testing modifyTitleToSupportMultiLine() in BubbleTextView.java
* This class tests a couple of strings and uses the getMaxLines() to determine if the test passes.
@@ -518,14 +515,12 @@
// Icon is replaced with a non pending icon when download finishes
mItemInfoWithIcon.setProgressLevel(100, PackageInstallInfo.STATUS_INSTALLED);
- CountDownLatch animWait = new CountDownLatch(1);
TestUtil.runOnExecutorSync(MAIN_EXECUTOR, () -> {
mBubbleTextView.applyIconAndLabel(mItemInfoWithIcon);
assertThat(mBubbleTextView.getIcon()).isSameInstanceAs(oldIcon);
assertThat(oldIcon.getActiveAnimation()).isNotNull();
- oldIcon.getActiveAnimation().addListener(forEndCallback(animWait::countDown));
+ oldIcon.getActiveAnimation().end();
});
- animWait.await();
// Assert that the icon is replaced with a non-pending icon
assertThat(mBubbleTextView.getIcon()).isNotInstanceOf(PreloadIconDrawable.class);
diff --git a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
index dba7603..cdb45fc 100644
--- a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
+++ b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
@@ -1,7 +1,10 @@
package com.android.launcher3.model
import android.appwidget.AppWidgetManager
+import android.content.ComponentName
import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.LauncherActivityInfo
import android.os.Process
import android.os.UserHandle
import android.platform.test.annotations.DisableFlags
@@ -28,6 +31,9 @@
import com.android.launcher3.icons.IconCache
import com.android.launcher3.icons.cache.CachingLogic
import com.android.launcher3.icons.cache.IconCacheUpdateHandler
+import com.android.launcher3.model.data.AppInfo
+import com.android.launcher3.model.data.IconRequestInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.pm.UserCache
import com.android.launcher3.provider.RestoreDbTask
import com.android.launcher3.ui.TestViewHelpers
@@ -38,8 +44,11 @@
import com.android.launcher3.util.TestUtil
import com.android.launcher3.util.UserIconInfo
import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
import dagger.BindsInstance
import dagger.Component
+import java.util.concurrent.CountDownLatch
+import java.util.function.Predicate
import junit.framework.Assert.assertEquals
import org.junit.After
import org.junit.Before
@@ -58,12 +67,11 @@
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
-import java.util.concurrent.CountDownLatch
-import java.util.function.Predicate
private const val INSERTION_STATEMENT_FILE = "databases/workspace_items.sql"
@@ -480,6 +488,157 @@
verify(spyContext, times(0)).sendBroadcast(any())
}
+ @Test
+ @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB)
+ fun `When flag on then archived AllApps icons found on Workspace loaded from db`() {
+ // Given
+ // Given
+ val activityInfo: LauncherActivityInfo = mock()
+ val applicationInfo: ApplicationInfo = mock<ApplicationInfo>().apply { isArchived = true }
+ whenever(activityInfo.applicationInfo).thenReturn(applicationInfo)
+ val expectedIconBlob = byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08)
+ val expectedComponent = ComponentName("package", "class")
+ val workspaceIconRequests =
+ listOf(
+ IconRequestInfo<WorkspaceItemInfo>(
+ WorkspaceItemInfo().apply {
+ intent = Intent().apply { component = expectedComponent }
+ },
+ activityInfo,
+ expectedIconBlob,
+ false, /* useLowResIcon */
+ )
+ )
+ val expectedAppInfo = AppInfo().apply { componentName = expectedComponent }
+ // When
+ val loader =
+ LoaderTask(
+ app,
+ bgAllAppsList,
+ BgDataModel(),
+ modelDelegate,
+ launcherBinder,
+ widgetsFilterDataProvider,
+ )
+ val actualIconRequest =
+ loader.getAppInfoIconRequestInfo(expectedAppInfo, activityInfo, workspaceIconRequests)
+ // Then
+ assertThat(actualIconRequest.iconBlob).isEqualTo(expectedIconBlob)
+ assertThat(actualIconRequest.itemInfo).isEqualTo(expectedAppInfo)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB)
+ fun `When flag on then unarchived AllApps icons not loaded from db`() {
+ // Given
+ val activityInfo: LauncherActivityInfo = mock()
+ val applicationInfo: ApplicationInfo = mock<ApplicationInfo>().apply { isArchived = false }
+ whenever(activityInfo.applicationInfo).thenReturn(applicationInfo)
+ val expectedIconBlob = byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08)
+ val expectedComponent = ComponentName("package", "class")
+ val workspaceIconRequests =
+ listOf(
+ IconRequestInfo<WorkspaceItemInfo>(
+ WorkspaceItemInfo().apply {
+ intent = Intent().apply { component = expectedComponent }
+ },
+ activityInfo,
+ expectedIconBlob,
+ false, /* useLowResIcon */
+ )
+ )
+ val expectedAppInfo = AppInfo().apply { componentName = expectedComponent }
+ // When
+ val loader =
+ LoaderTask(
+ app,
+ bgAllAppsList,
+ BgDataModel(),
+ modelDelegate,
+ launcherBinder,
+ widgetsFilterDataProvider,
+ )
+ val actualIconRequest =
+ loader.getAppInfoIconRequestInfo(expectedAppInfo, activityInfo, workspaceIconRequests)
+ // Then
+ assertThat(actualIconRequest.iconBlob).isNull()
+ assertThat(actualIconRequest.itemInfo).isEqualTo(expectedAppInfo)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB)
+ fun `When flag on then archived AllApps icon not found on Workspace not loaded from db`() {
+ // Given
+ val activityInfo: LauncherActivityInfo = mock()
+ val applicationInfo: ApplicationInfo = mock<ApplicationInfo>().apply { isArchived = true }
+ whenever(activityInfo.applicationInfo).thenReturn(applicationInfo)
+ val expectedIconBlob = byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08)
+ val expectedComponent = ComponentName("package", "class")
+ val workspaceIconRequests =
+ listOf(
+ IconRequestInfo<WorkspaceItemInfo>(
+ WorkspaceItemInfo().apply {
+ intent = Intent().apply { component = expectedComponent }
+ },
+ activityInfo,
+ expectedIconBlob,
+ false, /* useLowResIcon */
+ )
+ )
+ val expectedAppInfo =
+ AppInfo().apply { componentName = ComponentName("differentPkg", "differentClass") }
+ // When
+ val loader =
+ LoaderTask(
+ app,
+ bgAllAppsList,
+ BgDataModel(),
+ modelDelegate,
+ launcherBinder,
+ widgetsFilterDataProvider,
+ )
+ val actualIconRequest =
+ loader.getAppInfoIconRequestInfo(expectedAppInfo, activityInfo, workspaceIconRequests)
+ // Then
+ assertThat(actualIconRequest.iconBlob).isNull()
+ assertThat(actualIconRequest.itemInfo).isEqualTo(expectedAppInfo)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB)
+ fun `When flag off then archived AllApps icons not loaded from db`() {
+ // Given
+ val activityInfo: LauncherActivityInfo = mock()
+ val applicationInfo: ApplicationInfo = mock<ApplicationInfo>().apply { isArchived = true }
+ whenever(activityInfo.applicationInfo).thenReturn(applicationInfo)
+ val expectedIconBlob = byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08)
+ val workspaceIconRequests =
+ listOf(
+ IconRequestInfo<WorkspaceItemInfo>(
+ WorkspaceItemInfo(),
+ activityInfo,
+ expectedIconBlob,
+ false, /* useLowResIcon */
+ )
+ )
+ val expectedAppInfo = AppInfo()
+ // When
+ val loader =
+ LoaderTask(
+ app,
+ bgAllAppsList,
+ BgDataModel(),
+ modelDelegate,
+ launcherBinder,
+ widgetsFilterDataProvider,
+ )
+ val actualIconRequest =
+ loader.getAppInfoIconRequestInfo(expectedAppInfo, activityInfo, workspaceIconRequests)
+ // Then
+ assertThat(actualIconRequest.iconBlob).isNull()
+ assertThat(actualIconRequest.itemInfo).isEqualTo(expectedAppInfo)
+ }
+
@LauncherAppSingleton
@Component(modules = [AllModulesForTest::class])
interface TestComponent : LauncherAppComponent {
diff --git a/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java
index cb04e13..cab1ebe 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java
@@ -116,7 +116,13 @@
*/
@ScreenRecord // b/381918059
@Test
- public void testAddAndDeletePageAndFling() {
+ public void testAddAndDeletePageAndFling() throws Exception {
+ // Set workspace that includes the chrome Activity app icon on the hotseat.
+ LauncherLayoutBuilder builder = new LauncherLayoutBuilder()
+ .atHotseat(0).putApp("com.android.chrome", "com.google.android.apps.chrome.Main");
+ mLauncherLayout = TestUtil.setLauncherDefaultLayout(mTargetContext, builder);
+ reinitializeLauncherData();
+
Workspace workspace = mLauncher.getWorkspace();
// Get the first app from the hotseat
HomeAppIcon hotSeatIcon = workspace.getHotseatAppIcon(0);
diff --git a/tests/src/com/android/launcher3/util/WallpaperThemeManagerTest.kt b/tests/src/com/android/launcher3/util/WallpaperThemeManagerTest.kt
new file mode 100644
index 0000000..4c8fd8a
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/WallpaperThemeManagerTest.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.app.Activity
+import android.content.ComponentCallbacks
+import android.content.res.Configuration
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.any
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
+import com.android.dx.mockito.inline.extended.ExtendedMockito.eq
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.ExtendedMockito.times
+import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.launcher3.util.WallpaperThemeManager.Companion.setWallpaperDependentTheme
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.never
+import org.mockito.kotlin.whenever
+
+/** Tests for WallpaperThemeManager */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WallpaperThemeManagerTest {
+
+ @get:Rule val context = SandboxApplication()
+
+ @Mock lateinit var activity: Activity
+ @Captor lateinit var callbacksCaptor: ArgumentCaptor<ComponentCallbacks>
+
+ private lateinit var mockSession: StaticMockitoSession
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ mockSession = mockitoSession().spyStatic(Themes::class.java).startMocking()
+
+ doReturn(1).`when`<Int> { Themes.getActivityThemeRes(any()) }
+ doReturn(context).whenever(activity).applicationContext
+ }
+
+ @After
+ fun tearDown() {
+ mockSession.finishMocking()
+ }
+
+ @Test
+ fun `correct theme set on activity create`() {
+ activity.setWallpaperDependentTheme()
+ verify(activity, times(1)).setTheme(eq(1))
+ }
+
+ @Test
+ fun `ignores update if theme does not change`() {
+ activity.setWallpaperDependentTheme()
+ verify(activity).registerComponentCallbacks(callbacksCaptor.capture())
+ callbacksCaptor.value.onConfigurationChanged(Configuration())
+ verify(activity, never()).recreate()
+ }
+
+ @Test
+ fun `activity recreated if theme changes`() {
+ activity.setWallpaperDependentTheme()
+ verify(activity).registerComponentCallbacks(callbacksCaptor.capture())
+
+ doReturn(3).`when`<Int> { Themes.getActivityThemeRes(any()) }
+ callbacksCaptor.value.onConfigurationChanged(Configuration())
+ verify(activity, times(1)).recreate()
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index abf46e7..afc0dd5 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -2131,12 +2131,13 @@
downTime, currentTime, action, point.x, point.y, pointerCount,
mTrackpadGestureType)
: getMotionEvent(downTime, currentTime, action, point.x, point.y, source, toolType);
+ int button = isRightClick ? MotionEvent.BUTTON_SECONDARY : MotionEvent.BUTTON_PRIMARY;
+ if (action == MotionEvent.ACTION_BUTTON_PRESS) {
+ event.setButtonState(event.getButtonState() | button);
+ }
if (action == MotionEvent.ACTION_BUTTON_PRESS
|| action == MotionEvent.ACTION_BUTTON_RELEASE) {
- event.setActionButton(MotionEvent.BUTTON_PRIMARY);
- }
- if (isRightClick) {
- event.setButtonState(event.getButtonState() | MotionEvent.BUTTON_SECONDARY);
+ event.setActionButton(button);
}
injectEvent(event);
}
@@ -2247,11 +2248,17 @@
sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetCenter,
GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_TOUCHSCREEN,
/* isRightClick= */ true, MotionEvent.TOOL_TYPE_STYLUS);
+ sendPointer(downTime, downTime, MotionEvent.ACTION_BUTTON_PRESS, targetCenter,
+ GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_TOUCHSCREEN,
+ /* isRightClick= */ true, MotionEvent.TOOL_TYPE_STYLUS);
try {
expectEvent(TestProtocol.SEQUENCE_MAIN, longClickEvent);
final UiObject2 result = waitForLauncherObject(resName);
return result;
} finally {
+ sendPointer(downTime, downTime, MotionEvent.ACTION_BUTTON_RELEASE, targetCenter,
+ GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_TOUCHSCREEN,
+ /* isRightClick= */ true, MotionEvent.TOOL_TYPE_STYLUS);
sendPointer(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, targetCenter,
GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_TOUCHSCREEN,
/* isRightClick= */ true, MotionEvent.TOOL_TYPE_STYLUS);
@@ -2266,11 +2273,17 @@
sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetCenter,
GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_MOUSE,
/* isRightClick= */ true);
+ sendPointer(downTime, downTime, MotionEvent.ACTION_BUTTON_PRESS, targetCenter,
+ GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_MOUSE,
+ /* isRightClick= */ true);
try {
expectEvent(TestProtocol.SEQUENCE_MAIN, rightClickEvent);
final UiObject2 result = waitForLauncherObject(resName);
return result;
} finally {
+ sendPointer(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_BUTTON_RELEASE,
+ targetCenter, GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_MOUSE,
+ /* isRightClick= */ true);
sendPointer(downTime, SystemClock.uptimeMillis(), ACTION_UP, targetCenter,
GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_MOUSE,
/* isRightClick= */ true);
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 4a7caf8..4230643 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -58,7 +58,7 @@
*/
public final class Workspace extends Home {
private static final int FLING_STEPS = 10;
- private static final int DEFAULT_DRAG_STEPS = 10;
+ private static final int DEFAULT_DRAG_STEPS = 15;
private static final String DROP_BAR_RES_ID = "drop_target_bar";
private static final String DELETE_TARGET_TEXT_ID = "delete_target_text";
private static final String UNINSTALL_TARGET_TEXT_ID = "uninstall_target_text";
@@ -682,7 +682,7 @@
launcher.movePointer(dragStart, targetDest,
DEFAULT_DRAG_STEPS, isDecelerating, downTime, SystemClock.uptimeMillis(),
- false, LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
+ true, LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
dropDraggedIcon(launcher, targetDest, downTime, expectDropEvents, startsActivity);
}