Merge "Notify with `onIsInDesktopModeChanged()`" into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index ed370ec..d4cea8d 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -559,6 +559,13 @@
}
flag {
+ name: "enable_launcher_visual_refresh"
+ namespace: "launcher"
+ description: "Adds refresh for font family, app longpress menu icons, and pagination dots"
+ bug: "395145453"
+}
+
+flag {
name: "restore_archived_shortcuts"
namespace: "launcher"
description: "Makes sure pre-archived pinned shortcuts also get restored"
@@ -608,3 +615,10 @@
description: "Enable Strict Mode for the Launcher app"
bug: "394651876"
}
+
+flag {
+ name: "extendible_theme_manager"
+ namespace: "launcher"
+ description: "Enables custom theme manager in Launcher"
+ bug: "381897614"
+}
diff --git a/quickstep/res/values-af/strings.xml b/quickstep/res/values-af/strings.xml
index c71308f..c922238 100644
--- a/quickstep/res/values-af/strings.xml
+++ b/quickstep/res/values-af/strings.xml
@@ -23,8 +23,7 @@
<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_option_close" msgid="942942499021777264">"Maak toe"</string>
<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 a7b9957..97b0ac7 100644
--- a/quickstep/res/values-am/strings.xml
+++ b/quickstep/res/values-am/strings.xml
@@ -23,8 +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>
- <!-- no translation found for recent_task_option_close (942942499021777264) -->
- <skip />
+ <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 ab57177..dab4070 100644
--- a/quickstep/res/values-az/strings.xml
+++ b/quickstep/res/values-az/strings.xml
@@ -23,8 +23,7 @@
<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_option_close" msgid="942942499021777264">"Bağlayın"</string>
<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-be/strings.xml b/quickstep/res/values-be/strings.xml
index 08d855c..5ea5761 100644
--- a/quickstep/res/values-be/strings.xml
+++ b/quickstep/res/values-be/strings.xml
@@ -23,8 +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>
- <!-- no translation found for recent_task_option_close (942942499021777264) -->
- <skip />
+ <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-bg/strings.xml b/quickstep/res/values-bg/strings.xml
index f67e8e4..5c01d29 100644
--- a/quickstep/res/values-bg/strings.xml
+++ b/quickstep/res/values-bg/strings.xml
@@ -23,8 +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>
- <!-- no translation found for recent_task_option_close (942942499021777264) -->
- <skip />
+ <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-bn/strings.xml b/quickstep/res/values-bn/strings.xml
index 8ff5d76..01954cf 100644
--- a/quickstep/res/values-bn/strings.xml
+++ b/quickstep/res/values-bn/strings.xml
@@ -23,8 +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>
- <!-- no translation found for recent_task_option_close (942942499021777264) -->
- <skip />
+ <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-bs/strings.xml b/quickstep/res/values-bs/strings.xml
index 9553e28..18c178b 100644
--- a/quickstep/res/values-bs/strings.xml
+++ b/quickstep/res/values-bs/strings.xml
@@ -23,7 +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_option_close" msgid="942942499021777264">"Zatvaranje"</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 44ef6c3..23cd439 100644
--- a/quickstep/res/values-ca/strings.xml
+++ b/quickstep/res/values-ca/strings.xml
@@ -23,8 +23,7 @@
<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_option_close" msgid="942942499021777264">"Tanca"</string>
<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-da/strings.xml b/quickstep/res/values-da/strings.xml
index 224ac11..4bae930 100644
--- a/quickstep/res/values-da/strings.xml
+++ b/quickstep/res/values-da/strings.xml
@@ -23,8 +23,7 @@
<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_option_close" msgid="942942499021777264">"Luk"</string>
<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 40c1cce..e937410 100644
--- a/quickstep/res/values-de/strings.xml
+++ b/quickstep/res/values-de/strings.xml
@@ -23,8 +23,7 @@
<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_option_close" msgid="942942499021777264">"Schließen"</string>
<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>
diff --git a/quickstep/res/values-el/strings.xml b/quickstep/res/values-el/strings.xml
index 046e047..438dcf8 100644
--- a/quickstep/res/values-el/strings.xml
+++ b/quickstep/res/values-el/strings.xml
@@ -23,8 +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>
- <!-- no translation found for recent_task_option_close (942942499021777264) -->
- <skip />
+ <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-en-rAU/strings.xml b/quickstep/res/values-en-rAU/strings.xml
index 0c8384c..954db96 100644
--- a/quickstep/res/values-en-rAU/strings.xml
+++ b/quickstep/res/values-en-rAU/strings.xml
@@ -23,8 +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>
- <!-- no translation found for recent_task_option_close (942942499021777264) -->
- <skip />
+ <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 0c8384c..954db96 100644
--- a/quickstep/res/values-en-rGB/strings.xml
+++ b/quickstep/res/values-en-rGB/strings.xml
@@ -23,8 +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>
- <!-- no translation found for recent_task_option_close (942942499021777264) -->
- <skip />
+ <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-rIN/strings.xml b/quickstep/res/values-en-rIN/strings.xml
index 0c8384c..954db96 100644
--- a/quickstep/res/values-en-rIN/strings.xml
+++ b/quickstep/res/values-en-rIN/strings.xml
@@ -23,8 +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>
- <!-- no translation found for recent_task_option_close (942942499021777264) -->
- <skip />
+ <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-es/strings.xml b/quickstep/res/values-es/strings.xml
index 6c412f7..7f7b433 100644
--- a/quickstep/res/values-es/strings.xml
+++ b/quickstep/res/values-es/strings.xml
@@ -23,8 +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>
- <!-- no translation found for recent_task_option_close (942942499021777264) -->
- <skip />
+ <string name="recent_task_option_close" msgid="942942499021777264">"Cerrar"</string>
<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-eu/strings.xml b/quickstep/res/values-eu/strings.xml
index 892089d..0a912d8 100644
--- a/quickstep/res/values-eu/strings.xml
+++ b/quickstep/res/values-eu/strings.xml
@@ -23,8 +23,7 @@
<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_option_close" msgid="942942499021777264">"Itxi"</string>
<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-fi/strings.xml b/quickstep/res/values-fi/strings.xml
index 0e360d0..adeaa34 100644
--- a/quickstep/res/values-fi/strings.xml
+++ b/quickstep/res/values-fi/strings.xml
@@ -23,8 +23,7 @@
<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_option_close" msgid="942942499021777264">"Sulje"</string>
<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-gl/strings.xml b/quickstep/res/values-gl/strings.xml
index f4260f3..edd55d5 100644
--- a/quickstep/res/values-gl/strings.xml
+++ b/quickstep/res/values-gl/strings.xml
@@ -23,8 +23,7 @@
<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_option_close" msgid="942942499021777264">"Pechar"</string>
<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 2797f82..7145c6c9 100644
--- a/quickstep/res/values-gu/strings.xml
+++ b/quickstep/res/values-gu/strings.xml
@@ -23,8 +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>
- <!-- no translation found for recent_task_option_close (942942499021777264) -->
- <skip />
+ <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-hi/strings.xml b/quickstep/res/values-hi/strings.xml
index 5ed6ef5..99b7b71 100644
--- a/quickstep/res/values-hi/strings.xml
+++ b/quickstep/res/values-hi/strings.xml
@@ -23,8 +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>
- <!-- no translation found for recent_task_option_close (942942499021777264) -->
- <skip />
+ <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-hu/strings.xml b/quickstep/res/values-hu/strings.xml
index 4f15c7d..f825032 100644
--- a/quickstep/res/values-hu/strings.xml
+++ b/quickstep/res/values-hu/strings.xml
@@ -23,8 +23,7 @@
<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_option_close" msgid="942942499021777264">"Bezárás"</string>
<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 edaa5bd..6a7653f 100644
--- a/quickstep/res/values-hy/strings.xml
+++ b/quickstep/res/values-hy/strings.xml
@@ -23,8 +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>
- <!-- no translation found for recent_task_option_close (942942499021777264) -->
- <skip />
+ <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-in/strings.xml b/quickstep/res/values-in/strings.xml
index 4b2ffd7..5b5796c 100644
--- a/quickstep/res/values-in/strings.xml
+++ b/quickstep/res/values-in/strings.xml
@@ -23,8 +23,7 @@
<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_option_close" msgid="942942499021777264">"Tutup"</string>
<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 3619a2d..12541f7 100644
--- a/quickstep/res/values-is/strings.xml
+++ b/quickstep/res/values-is/strings.xml
@@ -23,8 +23,7 @@
<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_option_close" msgid="942942499021777264">"Loka"</string>
<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 17aefa4..731184b 100644
--- a/quickstep/res/values-it/strings.xml
+++ b/quickstep/res/values-it/strings.xml
@@ -23,8 +23,7 @@
<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_option_close" msgid="942942499021777264">"Chiudi"</string>
<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 1cba30f..0fff001 100644
--- a/quickstep/res/values-iw/strings.xml
+++ b/quickstep/res/values-iw/strings.xml
@@ -23,8 +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>
- <!-- no translation found for recent_task_option_close (942942499021777264) -->
- <skip />
+ <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 b95e708..76a6199 100644
--- a/quickstep/res/values-kk/strings.xml
+++ b/quickstep/res/values-kk/strings.xml
@@ -23,8 +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>
- <!-- no translation found for recent_task_option_close (942942499021777264) -->
- <skip />
+ <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 19d3ec9..c43a3d1 100644
--- a/quickstep/res/values-kn/strings.xml
+++ b/quickstep/res/values-kn/strings.xml
@@ -23,8 +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>
- <!-- no translation found for recent_task_option_close (942942499021777264) -->
- <skip />
+ <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-ko/strings.xml b/quickstep/res/values-ko/strings.xml
index 2e89174..4105f97 100644
--- a/quickstep/res/values-ko/strings.xml
+++ b/quickstep/res/values-ko/strings.xml
@@ -23,8 +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>
- <!-- no translation found for recent_task_option_close (942942499021777264) -->
- <skip />
+ <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-ky/strings.xml b/quickstep/res/values-ky/strings.xml
index ac2b06d..3109069 100644
--- a/quickstep/res/values-ky/strings.xml
+++ b/quickstep/res/values-ky/strings.xml
@@ -23,8 +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>
- <!-- no translation found for recent_task_option_close (942942499021777264) -->
- <skip />
+ <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-lo/strings.xml b/quickstep/res/values-lo/strings.xml
index 2ebaa7a..3df475f 100644
--- a/quickstep/res/values-lo/strings.xml
+++ b/quickstep/res/values-lo/strings.xml
@@ -90,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-lt/strings.xml b/quickstep/res/values-lt/strings.xml
index b57fee4..ec16b11 100644
--- a/quickstep/res/values-lt/strings.xml
+++ b/quickstep/res/values-lt/strings.xml
@@ -23,8 +23,7 @@
<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_option_close" msgid="942942499021777264">"Uždaryti"</string>
<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 07e9e1a..0f15a23 100644
--- a/quickstep/res/values-lv/strings.xml
+++ b/quickstep/res/values-lv/strings.xml
@@ -23,8 +23,7 @@
<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_option_close" msgid="942942499021777264">"Aizvērt"</string>
<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>
diff --git a/quickstep/res/values-mk/strings.xml b/quickstep/res/values-mk/strings.xml
index 0a133a3..8fd0cc5 100644
--- a/quickstep/res/values-mk/strings.xml
+++ b/quickstep/res/values-mk/strings.xml
@@ -23,8 +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>
- <!-- no translation found for recent_task_option_close (942942499021777264) -->
- <skip />
+ <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 c50e87a..f086314 100644
--- a/quickstep/res/values-mn/strings.xml
+++ b/quickstep/res/values-mn/strings.xml
@@ -23,8 +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>
- <!-- no translation found for recent_task_option_close (942942499021777264) -->
- <skip />
+ <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-mr/strings.xml b/quickstep/res/values-mr/strings.xml
index 63f4317..75c42ad 100644
--- a/quickstep/res/values-mr/strings.xml
+++ b/quickstep/res/values-mr/strings.xml
@@ -23,8 +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>
- <!-- no translation found for recent_task_option_close (942942499021777264) -->
- <skip />
+ <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-my/strings.xml b/quickstep/res/values-my/strings.xml
index 9df3d66..81a10b5 100644
--- a/quickstep/res/values-my/strings.xml
+++ b/quickstep/res/values-my/strings.xml
@@ -23,8 +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>
- <!-- no translation found for recent_task_option_close (942942499021777264) -->
- <skip />
+ <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-nb/strings.xml b/quickstep/res/values-nb/strings.xml
index d05884f..4c1aaaa 100644
--- a/quickstep/res/values-nb/strings.xml
+++ b/quickstep/res/values-nb/strings.xml
@@ -23,8 +23,7 @@
<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_option_close" msgid="942942499021777264">"Lukk"</string>
<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 6efdca9..47b2532 100644
--- a/quickstep/res/values-ne/strings.xml
+++ b/quickstep/res/values-ne/strings.xml
@@ -23,8 +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>
- <!-- no translation found for recent_task_option_close (942942499021777264) -->
- <skip />
+ <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-or/strings.xml b/quickstep/res/values-or/strings.xml
index 66cbcdd..248d4cc 100644
--- a/quickstep/res/values-or/strings.xml
+++ b/quickstep/res/values-or/strings.xml
@@ -23,8 +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>
- <!-- no translation found for recent_task_option_close (942942499021777264) -->
- <skip />
+ <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-pl/strings.xml b/quickstep/res/values-pl/strings.xml
index b571ba2..5c76187 100644
--- a/quickstep/res/values-pl/strings.xml
+++ b/quickstep/res/values-pl/strings.xml
@@ -23,8 +23,7 @@
<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_option_close" msgid="942942499021777264">"Zamknij"</string>
<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/strings.xml b/quickstep/res/values-pt/strings.xml
index bb7d97d..ede50cc 100644
--- a/quickstep/res/values-pt/strings.xml
+++ b/quickstep/res/values-pt/strings.xml
@@ -23,8 +23,7 @@
<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_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">"Configurações de uso do app"</string>
@@ -91,7 +90,7 @@
<string name="allset_title" msgid="5021126669778966707">"Tudo pronto!"</string>
<string name="allset_hint" msgid="459504134589971527">"Deslize para cima para acessar a tela inicial"</string>
<string name="allset_button_hint" msgid="2395219947744706291">"Toque no botão home para acessar a tela inicial"</string>
- <string name="allset_description_generic" msgid="5385500062202019855">"Você já pode começar a usar seu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
+ <string name="allset_description_generic" msgid="5385500062202019855">"O <xliff:g id="DEVICE">%1$s</xliff:g> já pode ser usado"</string>
<string name="default_device_name" msgid="6660656727127422487">"dispositivo"</string>
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Configurações de navegação do sistema"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Compartilhar"</string>
diff --git a/quickstep/res/values-ro/strings.xml b/quickstep/res/values-ro/strings.xml
index 856bef7..a89ccdc 100644
--- a/quickstep/res/values-ro/strings.xml
+++ b/quickstep/res/values-ro/strings.xml
@@ -23,8 +23,7 @@
<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_option_close" msgid="942942499021777264">"Închide"</string>
<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-si/strings.xml b/quickstep/res/values-si/strings.xml
index 3b1cada..de23b6e 100644
--- a/quickstep/res/values-si/strings.xml
+++ b/quickstep/res/values-si/strings.xml
@@ -23,8 +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>
- <!-- no translation found for recent_task_option_close (942942499021777264) -->
- <skip />
+ <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-sq/strings.xml b/quickstep/res/values-sq/strings.xml
index 8f39f65..9aedee5 100644
--- a/quickstep/res/values-sq/strings.xml
+++ b/quickstep/res/values-sq/strings.xml
@@ -23,8 +23,7 @@
<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_option_close" msgid="942942499021777264">"Mbyll"</string>
<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-sv/strings.xml b/quickstep/res/values-sv/strings.xml
index c33de8a..ab9f10e 100644
--- a/quickstep/res/values-sv/strings.xml
+++ b/quickstep/res/values-sv/strings.xml
@@ -23,8 +23,7 @@
<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_option_close" msgid="942942499021777264">"Stäng"</string>
<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 543663f..5f696c5 100644
--- a/quickstep/res/values-sw/strings.xml
+++ b/quickstep/res/values-sw/strings.xml
@@ -23,8 +23,7 @@
<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_option_close" msgid="942942499021777264">"Funga"</string>
<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>
diff --git a/quickstep/res/values-ta/strings.xml b/quickstep/res/values-ta/strings.xml
index 1e6cb9b..4f22a04 100644
--- a/quickstep/res/values-ta/strings.xml
+++ b/quickstep/res/values-ta/strings.xml
@@ -23,8 +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>
- <!-- no translation found for recent_task_option_close (942942499021777264) -->
- <skip />
+ <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-te/strings.xml b/quickstep/res/values-te/strings.xml
index 74e59ba..429d2d4 100644
--- a/quickstep/res/values-te/strings.xml
+++ b/quickstep/res/values-te/strings.xml
@@ -23,8 +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>
- <!-- no translation found for recent_task_option_close (942942499021777264) -->
- <skip />
+ <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-tr/strings.xml b/quickstep/res/values-tr/strings.xml
index ac06369..e213b40 100644
--- a/quickstep/res/values-tr/strings.xml
+++ b/quickstep/res/values-tr/strings.xml
@@ -23,8 +23,7 @@
<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_option_close" msgid="942942499021777264">"Kapat"</string>
<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>
@@ -91,7 +90,7 @@
<string name="allset_title" msgid="5021126669778966707">"Kurulum tamamlandı"</string>
<string name="allset_hint" msgid="459504134589971527">"Ana ekrana gitmek için yukarı kaydırın"</string>
<string name="allset_button_hint" msgid="2395219947744706291">"Ana ekranınıza gitmek için ana sayfa düğmesine dokunun"</string>
- <string name="allset_description_generic" msgid="5385500062202019855">"<xliff:g id="DEVICE">%1$s</xliff:g> adlı cihazınızı kullanmaya hazırsınız"</string>
+ <string name="allset_description_generic" msgid="5385500062202019855">"Artık <xliff:g id="DEVICE">%1$s</xliff:g> kullanılmak için hazır"</string>
<string name="default_device_name" msgid="6660656727127422487">"cihaz"</string>
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Sistem gezinme ayarları"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Paylaş"</string>
diff --git a/quickstep/res/values-uk/strings.xml b/quickstep/res/values-uk/strings.xml
index 2d27bb9..dab58d7 100644
--- a/quickstep/res/values-uk/strings.xml
+++ b/quickstep/res/values-uk/strings.xml
@@ -23,8 +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>
- <!-- no translation found for recent_task_option_close (942942499021777264) -->
- <skip />
+ <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-vi/strings.xml b/quickstep/res/values-vi/strings.xml
index e49f7e3..bf50e05 100644
--- a/quickstep/res/values-vi/strings.xml
+++ b/quickstep/res/values-vi/strings.xml
@@ -23,8 +23,7 @@
<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_option_close" msgid="942942499021777264">"Đóng"</string>
<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 c1cf2fe..374a4b6 100644
--- a/quickstep/res/values-zh-rCN/strings.xml
+++ b/quickstep/res/values-zh-rCN/strings.xml
@@ -23,8 +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>
- <!-- no translation found for recent_task_option_close (942942499021777264) -->
- <skip />
+ <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-zh-rHK/strings.xml b/quickstep/res/values-zh-rHK/strings.xml
index 393565a..42e2e37 100644
--- a/quickstep/res/values-zh-rHK/strings.xml
+++ b/quickstep/res/values-zh-rHK/strings.xml
@@ -23,8 +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>
- <!-- no translation found for recent_task_option_close (942942499021777264) -->
- <skip />
+ <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-zh-rTW/strings.xml b/quickstep/res/values-zh-rTW/strings.xml
index fbd465d..d9d1fb5 100644
--- a/quickstep/res/values-zh-rTW/strings.xml
+++ b/quickstep/res/values-zh-rTW/strings.xml
@@ -23,8 +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>
- <!-- no translation found for recent_task_option_close (942942499021777264) -->
- <skip />
+ <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-zu/strings.xml b/quickstep/res/values-zu/strings.xml
index 39b4873..660ef3e 100644
--- a/quickstep/res/values-zu/strings.xml
+++ b/quickstep/res/values-zu/strings.xml
@@ -23,8 +23,7 @@
<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_option_close" msgid="942942499021777264">"Vala"</string>
<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/dimens.xml b/quickstep/res/values/dimens.xml
index f2f1ebd..05f0695 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -88,6 +88,9 @@
<dimen name="task_thumbnail_header_icon_size">18dp</dimen>
<dimen name="task_thumbnail_header_round_corner_radius">16dp</dimen>
+ <!-- How much a task being dragged for dismissal can undershoot the origin when dragged back to its start position. -->
+ <dimen name="task_dismiss_max_undershoot">25dp</dimen>
+
<dimen name="task_icon_cache_default_icon_size">72dp</dimen>
<item name="overview_modal_max_scale" format="float" type="dimen">1.1</item>
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 4ba4e2b..c748385 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -411,7 +411,8 @@
@NonNull RemoteAnimationTarget[] nonAppTargets, boolean launcherClosing) {
TaskViewUtils.composeRecentsLaunchAnimator(anim, v, appTargets, wallpaperTargets,
nonAppTargets, launcherClosing, mLauncher.getStateManager(),
- mLauncher.getOverviewPanel(), mLauncher.getDepthController());
+ mLauncher.getOverviewPanel(), mLauncher.getDepthController(),
+ /* transitionInfo= */ null);
}
private boolean areAllTargetsTranslucent(@NonNull RemoteAnimationTarget[] targets) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
index 23065b5..5afc5ed 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.taskbar;
+import static com.android.launcher3.Flags.enableAltTabKqsOnConnectedDisplays;
+
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.view.MotionEvent;
@@ -272,11 +274,26 @@
}
private void processLoadedTasksOnDesktop(List<GroupTask> tasks, Set<Integer> taskIdsToExclude) {
- // Find the single desktop task that contains a grouping of desktop tasks
- DesktopTask desktopTask = findDesktopTask(tasks);
+ // Find all desktop tasks.
+ List<DesktopTask> desktopTasks = tasks.stream()
+ .filter(t -> t instanceof DesktopTask)
+ .map(t -> (DesktopTask) t)
+ .toList();
- if (desktopTask != null) {
- mTasks = desktopTask.getTasks().stream()
+ // Apps on the connected displays seem to be in different Desktop tasks even with the
+ // multiple desktops flag disabled. So, until multiple desktops is implemented the following
+ // should help with team-fooding Alt+tab on connected displays. Post multiple desktop,
+ // further changes maybe required to support launching selected desktops.
+ if (enableAltTabKqsOnConnectedDisplays()) {
+ mTasks = desktopTasks.stream()
+ .flatMap(t -> t.getTasks().stream())
+ .map(SingleTask::new)
+ .filter(task -> !shouldExcludeTask(task, taskIdsToExclude))
+ .collect(Collectors.toList());
+
+ mNumHiddenTasks = Math.max(0, tasks.size() - desktopTasks.size());
+ } else if (!desktopTasks.isEmpty()) {
+ mTasks = desktopTasks.get(0).getTasks().stream()
.map(SingleTask::new)
.filter(task -> !shouldExcludeTask(task, taskIdsToExclude))
.collect(Collectors.toList());
@@ -289,14 +306,6 @@
}
}
- @Nullable
- private DesktopTask findDesktopTask(List<GroupTask> tasks) {
- return (DesktopTask) tasks.stream()
- .filter(t -> t instanceof DesktopTask)
- .findFirst()
- .orElse(null);
- }
-
void closeQuickSwitchView() {
closeQuickSwitchView(true);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 11ae0c1..b9d1275 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -24,6 +24,8 @@
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
import static android.window.SplashScreen.SPLASH_SCREEN_STYLE_UNDEFINED;
+import static androidx.annotation.VisibleForTesting.PACKAGE_PRIVATE;
+
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
import static com.android.launcher3.AbstractFloatingView.TYPE_ON_BOARD_POPUP;
import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
@@ -45,6 +47,7 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
import static com.android.window.flags.Flags.enableStartLaunchTransitionFromTaskbarBugfix;
+import static com.android.window.flags.Flags.enableTaskbarConnectedDisplays;
import static com.android.wm.shell.Flags.enableTinyTaskbar;
import static java.lang.invoke.MethodHandles.Lookup.PROTECTED;
@@ -431,7 +434,9 @@
.setIsTransientTaskbar(true)
.build();
}
- mNavMode = DisplayController.getNavigationMode(this);
+ mNavMode = (enableTaskbarConnectedDisplays() && !mIsPrimaryDisplay)
+ ? NavigationMode.THREE_BUTTONS : DisplayController.getNavigationMode(this);
+
}
/** Called when the visibility of the bubble bar changed. */
@@ -2008,7 +2013,8 @@
return mControllers.uiController.isIconAlignedWithHotseat();
}
- @VisibleForTesting
+ // TODO(b/395061396): Remove `otherwise` when overview in widow is enabled.
+ @VisibleForTesting(otherwise = PACKAGE_PRIVATE)
public TaskbarControllers getControllers() {
return mControllers;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index ea2dec1..df3869e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -32,6 +32,7 @@
import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
import static com.android.quickstep.util.SystemActionConstants.ACTION_SHOW_TASKBAR;
import static com.android.quickstep.util.SystemActionConstants.SYSTEM_ACTION_ID_TASKBAR;
+import static com.android.window.flags.Flags.enableTaskbarConnectedDisplays;
import android.annotation.SuppressLint;
import android.app.PendingIntent;
@@ -71,6 +72,7 @@
import com.android.quickstep.AllAppsActionManager;
import com.android.quickstep.RecentsActivity;
import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.fallback.window.RecentsDisplayModel;
import com.android.quickstep.fallback.window.RecentsWindowManager;
import com.android.quickstep.util.ContextualSearchInvoker;
import com.android.quickstep.views.RecentsViewContainer;
@@ -115,7 +117,10 @@
private static final Uri NAV_BAR_KIDS_MODE = Settings.Secure.getUriFor(
Settings.Secure.NAV_BAR_KIDS_MODE);
- private final Context mParentContext;
+ private final Context mBaseContext;
+ // TODO: Remove this during the connected displays lifecycle refactor.
+ private final Context mPrimaryWindowContext;
+ private WindowManager mPrimaryWindowManager;
private final TaskbarNavButtonController mDefaultNavButtonController;
private final ComponentCallbacks mDefaultComponentCallbacks;
@@ -182,6 +187,7 @@
new SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, this::showTaskbarFromBroadcast);
private final AllAppsActionManager mAllAppsActionManager;
+ private final RecentsDisplayModel mRecentsDisplayModel;
private final Runnable mActivityOnDestroyCallback = new Runnable() {
@Override
@@ -242,31 +248,34 @@
public TaskbarManager(
Context context,
AllAppsActionManager allAppsActionManager,
- TaskbarNavButtonCallbacks navCallbacks) {
- mParentContext = context;
- createWindowContext(context.getDisplayId());
+ TaskbarNavButtonCallbacks navCallbacks,
+ RecentsDisplayModel recentsDisplayModel) {
+ mBaseContext = context;
mAllAppsActionManager = allAppsActionManager;
+ mRecentsDisplayModel = recentsDisplayModel;
+ mPrimaryWindowContext = createWindowContext(getDefaultDisplayId());
if (enableTaskbarNoRecreate()) {
+ mPrimaryWindowManager = mPrimaryWindowContext.getSystemService(WindowManager.class);
createTaskbarRootLayout(getDefaultDisplayId());
}
mDefaultNavButtonController = createDefaultNavButtonController(context, navCallbacks);
mDefaultComponentCallbacks = createDefaultComponentCallbacks();
- SettingsCache.INSTANCE.get(getPrimaryWindowContext())
+ SettingsCache.INSTANCE.get(mPrimaryWindowContext)
.register(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
- SettingsCache.INSTANCE.get(getPrimaryWindowContext())
+ SettingsCache.INSTANCE.get(mPrimaryWindowContext)
.register(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
Log.d(TASKBAR_NOT_DESTROYED_TAG, "registering component callbacks from constructor.");
- getPrimaryWindowContext().registerComponentCallbacks(mDefaultComponentCallbacks);
- mShutdownReceiver.register(getPrimaryWindowContext(), Intent.ACTION_SHUTDOWN);
+ mPrimaryWindowContext.registerComponentCallbacks(mDefaultComponentCallbacks);
+ mShutdownReceiver.register(mPrimaryWindowContext, Intent.ACTION_SHUTDOWN);
UI_HELPER_EXECUTOR.execute(() -> {
mSharedState.taskbarSystemActionPendingIntent = PendingIntent.getBroadcast(
- getPrimaryWindowContext(),
+ mPrimaryWindowContext,
SYSTEM_ACTION_ID_TASKBAR,
new Intent(ACTION_SHOW_TASKBAR).setPackage(
- getPrimaryWindowContext().getPackageName()),
+ mPrimaryWindowContext.getPackageName()),
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
mTaskbarBroadcastReceiver.register(
- getPrimaryWindowContext(), RECEIVER_NOT_EXPORTED, ACTION_SHOW_TASKBAR);
+ mPrimaryWindowContext, RECEIVER_NOT_EXPORTED, ACTION_SHOW_TASKBAR);
});
debugWhyTaskbarNotDestroyed("TaskbarManager created");
@@ -279,15 +288,15 @@
return new TaskbarNavButtonController(
context,
navCallbacks,
- SystemUiProxy.INSTANCE.get(getPrimaryWindowContext()),
+ SystemUiProxy.INSTANCE.get(mPrimaryWindowContext),
new Handler(),
- new ContextualSearchInvoker(getPrimaryWindowContext()));
+ new ContextualSearchInvoker(mPrimaryWindowContext));
}
private ComponentCallbacks createDefaultComponentCallbacks() {
return new ComponentCallbacks() {
private Configuration mOldConfig =
- getPrimaryWindowContext().getResources().getConfiguration();
+ mPrimaryWindowContext.getResources().getConfiguration();
@Override
public void onConfigurationChanged(Configuration newConfig) {
@@ -297,8 +306,8 @@
"TaskbarManager#mComponentCallbacks.onConfigurationChanged: " + newConfig);
// TODO: adapt this logic to be specific to different displays.
DeviceProfile dp = mUserUnlocked
- ? LauncherAppState.getIDP(getPrimaryWindowContext()).getDeviceProfile(
- getPrimaryWindowContext())
+ ? LauncherAppState.getIDP(mPrimaryWindowContext).getDeviceProfile(
+ mPrimaryWindowContext)
: null;
int configDiff = mOldConfig.diff(newConfig) & ~SKIP_RECREATE_CONFIG_CHANGES;
@@ -350,7 +359,6 @@
int displayId = mTaskbars.keyAt(i);
destroyTaskbarForDisplay(displayId);
removeTaskbarRootViewFromWindow(displayId);
- removeWindowContextFromMap(displayId);
}
}
@@ -417,7 +425,7 @@
*/
public void onUserUnlocked() {
mUserUnlocked = true;
- DisplayController.INSTANCE.get(getPrimaryWindowContext()).addChangeListener(
+ DisplayController.INSTANCE.get(mPrimaryWindowContext).addChangeListener(
mRecreationListener);
recreateTaskbar();
addTaskbarRootViewToWindow(getDefaultDisplayId());
@@ -481,12 +489,23 @@
return ql.getUnfoldTransitionProgressProvider();
}
} else {
- return SystemUiProxy.INSTANCE.get(
- getPrimaryWindowContext()).getUnfoldTransitionProvider();
+ return SystemUiProxy.INSTANCE.get(mPrimaryWindowContext).getUnfoldTransitionProvider();
}
return null;
}
+ /** Creates a {@link TaskbarUIController} to use with non default displays. */
+ private TaskbarUIController createTaskbarUIControllerForNonDefaultDisplay(int displayId) {
+ if (RecentsDisplayModel.enableOverviewInWindow()) {
+ RecentsViewContainer rvc = mRecentsDisplayModel.getRecentsWindowManager(displayId);
+ if (rvc != null) {
+ return createTaskbarUIControllerForRecentsViewContainer(rvc);
+ }
+ }
+
+ return new TaskbarUIController();
+ }
+
/**
* Creates a {@link TaskbarUIController} to use while the given StatefulActivity is active.
*/
@@ -543,7 +562,7 @@
+ " FLAG_HIDE_NAVBAR_WINDOW=" + ENABLE_TASKBAR_NAVBAR_UNIFICATION
+ " dp.isTaskbarPresent=" + (dp == null ? "null" : dp.isTaskbarPresent));
if (!isTaskbarEnabled || !isLargeScreenTaskbar) {
- SystemUiProxy.INSTANCE.get(getPrimaryWindowContext())
+ SystemUiProxy.INSTANCE.get(mPrimaryWindowContext)
.notifyTaskbarStatus(/* visible */ false, /* stashed */ false);
if (!isTaskbarEnabled) {
return;
@@ -561,7 +580,11 @@
mSharedState.allAppsVisible = mSharedState.allAppsVisible && isLargeScreenTaskbar;
taskbar.init(mSharedState);
- if (mRecentsViewContainer != null) {
+ // Non default displays should not use LauncherTaskbarUIController as they shouldn't
+ // have access to the Launcher activity.
+ if (enableTaskbarConnectedDisplays() && !isDefaultDisplay(displayId)) {
+ taskbar.setUIController(createTaskbarUIControllerForNonDefaultDisplay(displayId));
+ } else if (mRecentsViewContainer != null) {
taskbar.setUIController(
createTaskbarUIControllerForRecentsViewContainer(mRecentsViewContainer));
}
@@ -606,6 +629,7 @@
*/
public void setSetupUIVisible(boolean isVisible) {
mSharedState.setupUIVisible = isVisible;
+ mAllAppsActionManager.setSetupUiVisible(isVisible);
TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
if (taskbar != null) {
taskbar.setSetupUIVisible(isVisible);
@@ -719,6 +743,14 @@
* primary device or a previously mirroring display is switched to extended mode.
*/
public void onDisplayAddSystemDecorations(int displayId) {
+ if (isDefaultDisplay(displayId) || !enableTaskbarConnectedDisplays()) {
+ return;
+ }
+
+ Context newWindowContext = createWindowContext(displayId);
+ if (newWindowContext != null) {
+ addWindowContextToMap(displayId, newWindowContext);
+ }
}
/**
@@ -726,6 +758,14 @@
* removed from the primary device.
*/
public void onDisplayRemoved(int displayId) {
+ if (isDefaultDisplay(displayId) || !enableTaskbarConnectedDisplays()) {
+ return;
+ }
+
+ Context windowContext = getWindowContext(displayId);
+ if (windowContext != null) {
+ removeWindowContextFromMap(displayId);
+ }
}
/**
@@ -755,19 +795,19 @@
mRecentsViewContainer = null;
debugWhyTaskbarNotDestroyed("TaskbarManager#destroy()");
removeActivityCallbacksAndListeners();
- mTaskbarBroadcastReceiver.unregisterReceiverSafely(getPrimaryWindowContext());
+ mTaskbarBroadcastReceiver.unregisterReceiverSafely(mPrimaryWindowContext);
if (mUserUnlocked) {
- DisplayController.INSTANCE.get(getPrimaryWindowContext()).removeChangeListener(
+ DisplayController.INSTANCE.get(mPrimaryWindowContext).removeChangeListener(
mRecreationListener);
}
- SettingsCache.INSTANCE.get(getPrimaryWindowContext())
+ SettingsCache.INSTANCE.get(mPrimaryWindowContext)
.unregister(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
- SettingsCache.INSTANCE.get(getPrimaryWindowContext())
+ SettingsCache.INSTANCE.get(mPrimaryWindowContext)
.unregister(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
Log.d(TASKBAR_NOT_DESTROYED_TAG, "unregistering component callbacks from destroy().");
- getPrimaryWindowContext().unregisterComponentCallbacks(mDefaultComponentCallbacks);
- mShutdownReceiver.unregisterReceiverSafely(getPrimaryWindowContext());
+ mPrimaryWindowContext.unregisterComponentCallbacks(mDefaultComponentCallbacks);
+ mShutdownReceiver.unregisterReceiverSafely(mPrimaryWindowContext);
destroyAllTaskbars();
}
@@ -829,6 +869,23 @@
}
/**
+ * Returns the {@link TaskbarUIController} associated with the given display ID.
+ * TODO(b/395061396): Remove this method when overview in widow is enabled.
+ *
+ * @param displayId The ID of the display to retrieve the taskbar for.
+ * @return The {@link TaskbarUIController} for the specified display, or
+ * {@code null} if no taskbar is associated with that display.
+ */
+ @Nullable
+ public TaskbarUIController getUIControllerForDisplay(int displayId) {
+ if (!mTaskbars.contains(displayId)) {
+ return null;
+ }
+
+ return getTaskbarForDisplay(displayId).getControllers().uiController;
+ }
+
+ /**
* Retrieves whether RootLayout was added to window for specific display, or false if no
* such mapping has been made.
*
@@ -855,16 +912,16 @@
* Creates a {@link TaskbarActivityContext} for the given display and adds it to the map.
*/
private TaskbarActivityContext createTaskbarActivityContext(DeviceProfile dp, int displayId) {
- Display display = mParentContext.getSystemService(DisplayManager.class).getDisplay(
+ Display display = mBaseContext.getSystemService(DisplayManager.class).getDisplay(
displayId);
Context navigationBarPanelContext = ENABLE_TASKBAR_NAVBAR_UNIFICATION
- ? mParentContext.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null)
+ ? mBaseContext.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null)
: null;
TaskbarActivityContext newTaskbar = new TaskbarActivityContext(getWindowContext(displayId),
navigationBarPanelContext, dp, mDefaultNavButtonController,
mUnfoldProgressProvider, isDefaultDisplay(displayId),
- SystemUiProxy.INSTANCE.get(getPrimaryWindowContext()));
+ SystemUiProxy.INSTANCE.get(mPrimaryWindowContext));
addTaskbarToMap(displayId, newTaskbar);
return newTaskbar;
@@ -965,22 +1022,26 @@
}
/**
- * Creates {@link Context} for the taskbar on the specified display and›› adds it to map.
+ * Creates {@link Context} for the taskbar on the specified display.
* @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);
+ private @Nullable Context createWindowContext(int displayId) {
+ DisplayManager displayManager = mBaseContext.getSystemService(DisplayManager.class);
if (displayManager == null) {
- return;
+ return null;
}
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);
+ if (display == null) {
+ return null;
}
+
+ int windowType = TYPE_NAVIGATION_BAR_PANEL;
+ if (ENABLE_TASKBAR_NAVBAR_UNIFICATION && isDefaultDisplay(displayId)) {
+ windowType = TYPE_NAVIGATION_BAR;
+ }
+
+ return mBaseContext.createWindowContext(display, windowType, null);
}
/**
@@ -990,12 +1051,13 @@
* @return The Window Context {@link Context} for a given display or {@code null}.
*/
private Context getWindowContext(int displayId) {
- return mWindowContexts.get(displayId);
+ return (isDefaultDisplay(displayId) || !enableTaskbarConnectedDisplays())
+ ? mPrimaryWindowContext : mWindowContexts.get(displayId);
}
@VisibleForTesting
public Context getPrimaryWindowContext() {
- return getWindowContext(getDefaultDisplayId());
+ return mPrimaryWindowContext;
}
/**
@@ -1004,8 +1066,17 @@
* @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);
+ private @Nullable WindowManager getWindowManager(int displayId) {
+ if (isDefaultDisplay(displayId) || !enableTaskbarConnectedDisplays()) {
+ return mPrimaryWindowManager;
+ }
+
+ Context externalDisplayContext = getWindowContext(displayId);
+ if (externalDisplayContext == null) {
+ return null;
+ }
+
+ return externalDisplayContext.getSystemService(WindowManager.class);
}
/**
@@ -1032,7 +1103,7 @@
}
private int getDefaultDisplayId() {
- return mParentContext.getDisplayId();
+ return mBaseContext.getDisplayId();
}
/** Temp logs for b/254119092. */
@@ -1076,7 +1147,7 @@
log.add("\t\tWindowContext.getResources().getConfiguration()="
+ windowContext.getResources().getConfiguration());
if (mUserUnlocked) {
- log.add("\t\tLauncherAppState.getIDP().getDeviceProfile(getPrimaryWindowContext())"
+ log.add("\t\tLauncherAppState.getIDP().getDeviceProfile(mPrimaryWindowContext)"
+ ".isTaskbarPresent=" + contextTaskbarPresent);
} else {
log.add("\t\tCouldn't get DeviceProfile because !mUserUnlocked");
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt
index 99b962b..77a05c1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt
@@ -20,6 +20,7 @@
import androidx.dynamicanimation.animation.SpringAnimation
import com.android.app.animation.Interpolators.DECELERATE
import com.android.launcher3.AbstractFloatingView
+import com.android.launcher3.R
import com.android.launcher3.Utilities.EDGE_NAV_BAR
import com.android.launcher3.Utilities.boundToRange
import com.android.launcher3.Utilities.isRtl
@@ -144,7 +145,7 @@
0f,
dismissLength.toFloat(),
0f,
- DISMISS_MAX_UNDERSHOOT,
+ container.resources.getDimension(R.dimen.task_dismiss_max_undershoot),
DECELERATE,
)
taskBeingDragged.secondaryDismissTranslationProperty.setValue(
@@ -207,6 +208,5 @@
companion object {
private const val DISMISS_THRESHOLD_FRACTION = 0.5f
- private const val DISMISS_MAX_UNDERSHOOT = 25f
}
}
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index f46f9ae..b43c3ac 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -2453,7 +2453,8 @@
}
@Override
- public void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTargets) {
+ public void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTargets,
+ @Nullable TransitionInfo transitionInfo) {
if (mRecentsAnimationController == null) {
return;
}
diff --git a/quickstep/src/com/android/quickstep/AllAppsActionManager.kt b/quickstep/src/com/android/quickstep/AllAppsActionManager.kt
index 6fd68d5..b807a4b 100644
--- a/quickstep/src/com/android/quickstep/AllAppsActionManager.kt
+++ b/quickstep/src/com/android/quickstep/AllAppsActionManager.kt
@@ -21,10 +21,16 @@
import android.app.RemoteAction
import android.content.Context
import android.graphics.drawable.Icon
+import android.provider.Settings
+import android.provider.Settings.Secure.USER_SETUP_COMPLETE
import android.view.accessibility.AccessibilityManager
import com.android.launcher3.R
+import com.android.launcher3.util.SettingsCache
+import com.android.launcher3.util.SettingsCache.OnChangeListener
import java.util.concurrent.Executor
+private val USER_SETUP_COMPLETE_URI = Settings.Secure.getUriFor(USER_SETUP_COMPLETE)
+
/**
* Registers a [RemoteAction] for toggling All Apps if needed.
*
@@ -38,6 +44,12 @@
private val createAllAppsPendingIntent: () -> PendingIntent,
) {
+ private val onSettingsChangeListener = OnChangeListener { v -> isUserSetupComplete = v }
+
+ init {
+ SettingsCache.INSTANCE[context].register(USER_SETUP_COMPLETE_URI, onSettingsChangeListener)
+ }
+
/** `true` if home and overview are the same Activity. */
var isHomeAndOverviewSame = false
set(value) {
@@ -52,12 +64,27 @@
updateSystemAction()
}
+ /** `true` if the setup UI is visible. */
+ var isSetupUiVisible = false
+ set(value) {
+ field = value
+ updateSystemAction()
+ }
+
+ private var isUserSetupComplete =
+ SettingsCache.INSTANCE[context].getValue(USER_SETUP_COMPLETE_URI, 0)
+ set(value) {
+ field = value
+ updateSystemAction()
+ }
+
/** `true` if the action should be registered. */
var isActionRegistered = false
private set
private fun updateSystemAction() {
- val shouldRegisterAction = isHomeAndOverviewSame || isTaskbarPresent
+ val isInSetupFlow = isSetupUiVisible || !isUserSetupComplete
+ val shouldRegisterAction = (isHomeAndOverviewSame || isTaskbarPresent) && !isInSetupFlow
if (isActionRegistered == shouldRegisterAction) return
isActionRegistered = shouldRegisterAction
@@ -84,8 +111,10 @@
isActionRegistered = false
context
.getSystemService(AccessibilityManager::class.java)
- ?.unregisterSystemAction(
- GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS,
- )
+ ?.unregisterSystemAction(GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS)
+ SettingsCache.INSTANCE[context].unregister(
+ USER_SETUP_COMPLETE_URI,
+ onSettingsChangeListener,
+ )
}
}
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index 331580c..7d8a53d 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -50,6 +50,7 @@
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
+import android.window.TransitionInfo;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -172,14 +173,15 @@
}
@Override
- public void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTargets) {
+ public void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTargets,
+ @Nullable TransitionInfo transitionInfo) {
if (mActiveAnimationFactory != null && mActiveAnimationFactory.handleHomeTaskAppeared(
appearedTaskTargets)) {
mActiveAnimationFactory = null;
return;
}
- super.onTasksAppeared(appearedTaskTargets);
+ super.onTasksAppeared(appearedTaskTargets, transitionInfo);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/FocusState.kt b/quickstep/src/com/android/quickstep/FocusState.kt
index ba3991f..7c6aa5b 100644
--- a/quickstep/src/com/android/quickstep/FocusState.kt
+++ b/quickstep/src/com/android/quickstep/FocusState.kt
@@ -27,7 +27,10 @@
class FocusState {
var focusedDisplayId = DEFAULT_DISPLAY
- private set
+ private set(value) {
+ field = value
+ listeners.forEach { it.onFocusedDisplayChanged(value) }
+ }
private var listeners = mutableSetOf<FocusChangeListener>()
@@ -40,9 +43,7 @@
transitions?.setFocusTransitionListener(
object : Stub() {
override fun onFocusedDisplayChanged(displayId: Int) {
- Executors.MAIN_EXECUTOR.execute {
- listeners.forEach { it.onFocusedDisplayChanged(displayId) }
- }
+ Executors.MAIN_EXECUTOR.execute { focusedDisplayId = displayId }
}
}
)
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
index 94d115b..42aa86e 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
@@ -28,6 +28,7 @@
import androidx.annotation.UiThread
import androidx.annotation.VisibleForTesting
import com.android.internal.jank.Cuj
+import com.android.launcher3.Flags.enableAltTabKqsOnConnectedDisplays
import com.android.launcher3.Flags.enableFallbackOverviewInWindow
import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
import com.android.launcher3.Flags.enableLauncherOverviewInWindow
@@ -38,6 +39,8 @@
import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_3_BUTTON
import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_QUICK_SWITCH
import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_SHORTCUT
+import com.android.launcher3.taskbar.TaskbarManager
+import com.android.launcher3.taskbar.TaskbarUIController
import com.android.launcher3.util.Executors
import com.android.launcher3.util.RunnableList
import com.android.launcher3.util.coroutines.DispatcherProvider
@@ -48,6 +51,7 @@
import com.android.quickstep.OverviewCommandHelper.CommandType.KEYBOARD_INPUT
import com.android.quickstep.OverviewCommandHelper.CommandType.SHOW
import com.android.quickstep.OverviewCommandHelper.CommandType.TOGGLE
+import com.android.quickstep.fallback.window.RecentsDisplayModel
import com.android.quickstep.util.ActiveGestureLog
import com.android.quickstep.util.ActiveGestureProtoLogProxy
import com.android.quickstep.views.RecentsView
@@ -72,6 +76,9 @@
private val overviewComponentObserver: OverviewComponentObserver,
private val taskAnimationManager: TaskAnimationManager,
private val dispatcherProvider: DispatcherProvider = ProductionDispatchers,
+ private val recentsDisplayModel: RecentsDisplayModel,
+ private val focusState: FocusState,
+ private val taskbarManager: TaskbarManager,
) {
private val coroutineScope = CoroutineScope(SupervisorJob() + dispatcherProvider.background)
@@ -291,15 +298,51 @@
deviceProfile != null &&
(deviceProfile.isTablet || deviceProfile.isTwoPanels)
+ val focusedDisplayId = focusState.focusedDisplayId
+ val focusedDisplayUIController: TaskbarUIController? =
+ if (RecentsDisplayModel.enableOverviewInWindow()) {
+ Log.d(
+ TAG,
+ "Querying RecentsDisplayModel for TaskbarUIController for display: $focusedDisplayId",
+ )
+ recentsDisplayModel.getRecentsWindowManager(focusedDisplayId)?.taskbarUIController
+ } else {
+ Log.d(
+ TAG,
+ "Querying TaskbarManager for TaskbarUIController for display: $focusedDisplayId",
+ )
+ // TODO(b/395061396): Remove this path when overview in widow is enabled.
+ taskbarManager.getUIControllerForDisplay(focusedDisplayId)
+ }
+ Log.d(
+ TAG,
+ "TaskbarUIController for display $focusedDisplayId was" +
+ "${if (focusedDisplayUIController == null) " not" else ""} found",
+ )
+
when (command.type) {
HIDE -> {
if (!allowQuickSwitch) return true
- keyboardTaskFocusIndex = uiController!!.launchFocusedTask()
+ keyboardTaskFocusIndex =
+ if (
+ enableAltTabKqsOnConnectedDisplays() && focusedDisplayUIController != null
+ ) {
+ focusedDisplayUIController.launchFocusedTask()
+ } else {
+ uiController!!.launchFocusedTask()
+ }
+
if (keyboardTaskFocusIndex == -1) return true
}
KEYBOARD_INPUT ->
if (allowQuickSwitch) {
- uiController!!.openQuickSwitchView()
+ if (
+ enableAltTabKqsOnConnectedDisplays() && focusedDisplayUIController != null
+ ) {
+ focusedDisplayUIController.openQuickSwitchView()
+ } else {
+ uiController!!.openQuickSwitchView()
+ }
return true
} else {
keyboardTaskFocusIndex = 0
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index fca67c3..3d12fdf 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -319,7 +319,7 @@
/**
* Composes the animations for a launch from the recents list if possible.
*/
- private AnimatorSet composeRecentsLaunchAnimator(
+ private AnimatorSet composeRecentsLaunchAnimator(
@NonNull RecentsView recentsView,
@NonNull TaskView taskView,
RemoteAnimationTarget[] appTargets,
@@ -329,7 +329,8 @@
boolean activityClosing = taskIsATargetWithMode(appTargets, getTaskId(), MODE_CLOSING);
PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
createRecentsWindowAnimator(recentsView, taskView, !activityClosing, appTargets,
- wallpaperTargets, nonAppTargets, null /* depthController */, pa);
+ wallpaperTargets, nonAppTargets, /* depthController= */ null ,
+ /* transitionInfo= */ null, pa);
target.play(pa.buildAnim());
// Found a visible recents task that matches the opening app, lets launch the app from there
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index c6b858b..c276447 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -163,11 +163,12 @@
@BinderThread
@Override
- public void onTasksAppeared(RemoteAnimationTarget[] apps) {
+ public void onTasksAppeared(
+ RemoteAnimationTarget[] apps, @Nullable TransitionInfo transitionInfo) {
Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
ActiveGestureProtoLogProxy.logRecentsAnimationCallbacksOnTasksAppeared();
for (RecentsAnimationListener listener : getListeners()) {
- listener.onTasksAppeared(apps);
+ listener.onTasksAppeared(apps, transitionInfo);
}
});
}
@@ -225,6 +226,7 @@
/**
* Callback made when a task started from the recents is ready for an app transition.
*/
- default void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTarget) {}
+ default void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTarget,
+ @Nullable TransitionInfo transitionInfo) {}
}
}
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 87b58e6..1d83d42 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -38,13 +38,18 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppSingleton;
import com.android.launcher3.graphics.ThemeManager;
import com.android.launcher3.graphics.ThemeManager.ThemeChangeListener;
import com.android.launcher3.icons.IconProvider;
+import com.android.launcher3.util.DaggerSingletonObject;
+import com.android.launcher3.util.DaggerSingletonTracker;
+import com.android.launcher3.util.DisplayController;
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.dagger.QuickstepBaseAppComponent;
import com.android.quickstep.recents.data.RecentTasksDataSource;
import com.android.quickstep.recents.data.TaskVisualsChangeNotifier;
import com.android.quickstep.util.DesktopTask;
@@ -65,19 +70,22 @@
import java.util.function.Consumer;
import java.util.function.Predicate;
-import javax.inject.Provider;
+import javax.inject.Inject;
+
+import dagger.Lazy;
/**
* Singleton class to load and manage recents model.
*/
@TargetApi(Build.VERSION_CODES.O)
+@LauncherAppSingleton
public class RecentsModel implements RecentTasksDataSource, TaskStackChangeListener,
TaskVisualsChangeListener, TaskVisualsChangeNotifier,
- ThemeChangeListener, SafeCloseable {
+ ThemeChangeListener {
// We do not need any synchronization for this variable as its only written on UI thread.
- public static final MainThreadInitializedObject<RecentsModel> INSTANCE =
- new MainThreadInitializedObject<>(RecentsModel::new);
+ public static final DaggerSingletonObject<RecentsModel> INSTANCE =
+ new DaggerSingletonObject<>(QuickstepBaseAppComponent::getRecentsModel);
private static final Executor RECENTS_MODEL_EXECUTOR = Executors.newSingleThreadExecutor(
new SimpleThreadFactory("TaskThumbnailIconCache-", THREAD_PRIORITY_BACKGROUND));
@@ -85,53 +93,67 @@
private final ConcurrentLinkedQueue<TaskVisualsChangeListener> mThumbnailChangeListeners =
new ConcurrentLinkedQueue<>();
private final Context mContext;
-
private final RecentTasksList mTaskList;
private final TaskIconCache mIconCache;
private final TaskThumbnailCache mThumbnailCache;
- private final ComponentCallbacks mCallbacks;
- 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));
+ @Inject
+ public RecentsModel(@ApplicationContext Context context,
+ SystemUiProxy systemUiProxy,
+ TopTaskTracker topTaskTracker,
+ DisplayController displayController,
+ LockedUserState lockedUserState,
+ Lazy<ThemeManager> themeManagerLazy,
+ DaggerSingletonTracker tracker
+ ) {
+ // Lazily inject the ThemeManager and access themeManager once the device is
+ // unlocked. See b/393248495 for details.
+ this(context, new IconProvider(context), systemUiProxy, topTaskTracker,
+ displayController, lockedUserState,themeManagerLazy, tracker);
}
@SuppressLint("VisibleForTests")
- private RecentsModel(Context context, IconProvider iconProvider) {
+ private RecentsModel(@ApplicationContext Context context,
+ IconProvider iconProvider,
+ SystemUiProxy systemUiProxy,
+ TopTaskTracker topTaskTracker,
+ DisplayController displayController,
+ LockedUserState lockedUserState,
+ Lazy<ThemeManager> themeManagerLazy,
+ DaggerSingletonTracker tracker) {
this(context,
new RecentTasksList(
context,
MAIN_EXECUTOR,
context.getSystemService(KeyguardManager.class),
- SystemUiProxy.INSTANCE.get(context),
- TopTaskTracker.INSTANCE.get(context)),
- new TaskIconCache(context, RECENTS_MODEL_EXECUTOR, iconProvider),
+ systemUiProxy,
+ topTaskTracker),
+ new TaskIconCache(context, RECENTS_MODEL_EXECUTOR, iconProvider, displayController),
new TaskThumbnailCache(context, RECENTS_MODEL_EXECUTOR),
iconProvider,
TaskStackChangeListeners.getInstance(),
- LockedUserState.get(context),
- () -> ThemeManager.INSTANCE.get(context));
+ lockedUserState,
+ themeManagerLazy,
+ tracker);
}
@VisibleForTesting
- RecentsModel(Context context, RecentTasksList taskList, TaskIconCache iconCache,
- TaskThumbnailCache thumbnailCache, IconProvider iconProvider,
+ RecentsModel(@ApplicationContext Context context,
+ RecentTasksList taskList,
+ TaskIconCache iconCache,
+ TaskThumbnailCache thumbnailCache,
+ IconProvider iconProvider,
TaskStackChangeListeners taskStackChangeListeners,
LockedUserState lockedUserState,
- Provider<ThemeManager> themeManagerProvider) {
+ Lazy<ThemeManager> themeManagerLazy,
+ DaggerSingletonTracker tracker) {
mContext = context;
mTaskList = taskList;
mIconCache = iconCache;
mIconCache.registerTaskVisualsChangeListener(this);
mThumbnailCache = thumbnailCache;
if (isCachePreloadingEnabled()) {
- mCallbacks = new ComponentCallbacks() {
+ ComponentCallbacks componentCallbacks = new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration configuration) {
updateCacheSizeAndPreloadIfNeeded();
@@ -141,20 +163,27 @@
public void onLowMemory() {
}
};
- context.registerComponentCallbacks(mCallbacks);
- } else {
- mCallbacks = null;
+ context.registerComponentCallbacks(componentCallbacks);
+ tracker.addCloseable(() -> context.unregisterComponentCallbacks(componentCallbacks));
}
- mTaskStackChangeListeners = taskStackChangeListeners;
- mTaskStackChangeListeners.registerTaskStackListener(this);
- mIconChangeCloseable = iconProvider.registerIconChangeListener(
+ taskStackChangeListeners.registerTaskStackListener(this);
+ SafeCloseable iconChangeCloseable = iconProvider.registerIconChangeListener(
this::onAppIconChanged, MAIN_EXECUTOR.getHandler());
- mLockedUserState = lockedUserState;
- mThemeManagerProvider = themeManagerProvider;
- mUnlockCallback = () -> mThemeManagerProvider.get().addChangeListener(this);
- lockedUserState.runOnUserUnlocked(mUnlockCallback);
+ Runnable unlockCallback = () -> themeManagerLazy.get().addChangeListener(this);
+ lockedUserState.runOnUserUnlocked(unlockCallback);
+
+ tracker.addCloseable(() -> {
+ taskStackChangeListeners.unregisterTaskStackListener(this);
+ iconChangeCloseable.close();
+ mIconCache.removeTaskVisualsChangeListener();
+ if (lockedUserState.isUserUnlocked()) {
+ themeManagerLazy.get().removeChangeListener(this);
+ } else {
+ lockedUserState.removeOnUserUnlockedRunnable(unlockCallback);
+ }
+ });
}
public TaskIconCache getIconCache() {
@@ -407,22 +436,6 @@
}
}
- @Override
- public void close() {
- if (mCallbacks != null) {
- mContext.unregisterComponentCallbacks(mCallbacks);
- }
- mIconCache.removeTaskVisualsChangeListener();
- mTaskStackChangeListeners.unregisterTaskStackListener(this);
- mIconChangeCloseable.close();
-
- if (mLockedUserState.isUserUnlocked()) {
- mThemeManagerProvider.get().removeChangeListener(this);
- } else {
- mLockedUserState.removeOnUserUnlockedRunnable(mUnlockCallback);
- }
- }
-
private boolean isCachePreloadingEnabled() {
return enableGridOnlyOverview() || enableRefactorTaskThumbnail();
}
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.kt b/quickstep/src/com/android/quickstep/SystemUiProxy.kt
index d5382ad..1f3eb2a 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.kt
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.kt
@@ -149,7 +149,7 @@
private var backToLauncherRunner: IRemoteAnimationRunner? = null
private var dragAndDrop: IDragAndDrop? = null
val homeVisibilityState = HomeVisibilityState()
- private val focusState = FocusState()
+ val focusState = FocusState()
// Used to dedupe calls to SystemUI
private var lastShelfHeight = 0
@@ -1218,8 +1218,12 @@
override fun onAnimationCanceled(taskIds: IntArray?, taskSnapshots: Array<TaskSnapshot>?) =
listener.onAnimationCanceled(wrap(taskIds, taskSnapshots))
- override fun onTasksAppeared(apps: Array<RemoteAnimationTarget>?) =
- listener.onTasksAppeared(apps)
+ override fun onTasksAppeared(
+ apps: Array<RemoteAnimationTarget>?,
+ transitionInfo: TransitionInfo?,
+ ) {
+ listener.onTasksAppeared(apps, transitionInfo)
+ }
}
//
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 63b8aaf..9810308 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -231,7 +231,8 @@
}
@Override
- public void onTasksAppeared(RemoteAnimationTarget[] appearedTaskTargets) {
+ public void onTasksAppeared(RemoteAnimationTarget[] appearedTaskTargets,
+ @Nullable TransitionInfo transitionInfo) {
RemoteAnimationTarget appearedTaskTarget = appearedTaskTargets[0];
BaseContainerInterface containerInterface =
mLastGestureState.getContainerInterface();
@@ -264,7 +265,8 @@
recentsView.launchSideTaskInLiveTileMode(appearedTaskTarget.taskId,
appearedTaskTargets,
new RemoteAnimationTarget[0] /* wallpaper */,
- nonAppTargets /* nonApps */);
+ nonAppTargets /* nonApps */,
+ transitionInfo);
return;
} else {
ActiveGestureProtoLogProxy.logLaunchingSideTaskFailed();
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.kt b/quickstep/src/com/android/quickstep/TaskIconCache.kt
index b82c110..6a7f1af 100644
--- a/quickstep/src/com/android/quickstep/TaskIconCache.kt
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.kt
@@ -53,6 +53,7 @@
private val context: Context,
private val bgExecutor: Executor,
private val iconProvider: IconProvider,
+ displayController: DisplayController,
) : TaskIconDataSource, DisplayInfoChangeListener {
private val iconCache =
TaskKeyLruCache<TaskCacheEntry>(
@@ -71,7 +72,7 @@
var taskVisualsChangeListener: TaskVisualsChangeListener? = null
init {
- DisplayController.INSTANCE.get(context).addChangeListener(this)
+ displayController.addChangeListener(this)
}
override fun onDisplayInfoChanged(context: Context, info: DisplayController.Info, flags: Int) {
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index e47223b..37841e8 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -169,6 +169,7 @@
@NonNull RemoteAnimationTarget[] wallpaperTargets,
@NonNull RemoteAnimationTarget[] nonAppTargets,
@Nullable DepthController depthController,
+ @Nullable TransitionInfo transitionInfo,
PendingAnimation out) {
boolean isQuickSwitch = v.isEndQuickSwitchCuj();
v.setEndQuickSwitchCuj(false);
@@ -191,8 +192,7 @@
RemoteTargetGluer gluer = new RemoteTargetGluer(v.getContext(),
recentsView.getSizeStrategy(), targets, forDesktop);
if (forDesktop) {
- remoteTargetHandles =
- gluer.assignTargetsForDesktop(targets, /* transitionInfo=*/ null);
+ remoteTargetHandles = gluer.assignTargetsForDesktop(targets, transitionInfo);
} else if (v.containsMultipleTasks()) {
remoteTargetHandles = gluer.assignTargetsForSplitScreen(targets,
((GroupedTaskView) v).getSplitBoundsConfig());
@@ -462,7 +462,7 @@
final RecentsView recentsView = launchingTaskView.getRecentsView();
composeRecentsLaunchAnimator(animatorSet, launchingTaskView, appTargets, wallpaperTargets,
nonAppTargets, /* launcherClosing */ true, stateManager, recentsView,
- depthController);
+ depthController, /* transitionInfo= */ null);
t.apply();
animatorSet.start();
@@ -501,7 +501,7 @@
composeRecentsLaunchAnimator(animatorSet, launchingTaskView,
appTargets, wallpaperTargets, nonAppTargets,
true, stateManager,
- recentsView, depthController);
+ recentsView, depthController, /* transitionInfo= */ null);
animatorSet.start();
return;
}
@@ -593,7 +593,7 @@
composeRecentsLaunchAnimator(animatorSet, launchingTaskView, apps, wallpaper, nonApps,
true /* launcherClosing */, stateManager, launchingTaskView.getRecentsView(),
- depthController);
+ depthController, transitionInfo);
return animatorSet;
}
@@ -603,13 +603,13 @@
@NonNull RemoteAnimationTarget[] wallpaperTargets,
@NonNull RemoteAnimationTarget[] nonAppTargets, boolean launcherClosing,
@NonNull StateManager stateManager, @NonNull RecentsView recentsView,
- @Nullable DepthController depthController) {
+ @Nullable DepthController depthController, @Nullable TransitionInfo transitionInfo) {
boolean skipLauncherChanges = !launcherClosing;
TaskView taskView = findTaskViewToLaunch(recentsView, v, appTargets);
PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
createRecentsWindowAnimator(recentsView, taskView, skipLauncherChanges, appTargets,
- wallpaperTargets, nonAppTargets, depthController, pa);
+ wallpaperTargets, nonAppTargets, depthController, transitionInfo, pa);
if (launcherClosing) {
// TODO(b/182592057): differentiate between "restore split" vs "launch fullscreen app"
TaskViewUtils.createSplitAuxiliarySurfacesAnimator(nonAppTargets, true /*shown*/,
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 2df4a45..ba4c65a 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -88,6 +88,7 @@
import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.OverviewCommandHelper.CommandType;
import com.android.quickstep.OverviewComponentObserver.OverviewChangeListener;
+import com.android.quickstep.fallback.window.RecentsDisplayModel;
import com.android.quickstep.fallback.window.RecentsWindowSwipeHandler;
import com.android.quickstep.inputconsumers.BubbleBarInputConsumer;
import com.android.quickstep.inputconsumers.OneHandedModeInputConsumer;
@@ -584,7 +585,8 @@
initInputMonitor("onTrackpadConnected()");
});
- mTaskbarManager = new TaskbarManager(this, mAllAppsActionManager, mNavCallbacks);
+ mTaskbarManager = new TaskbarManager(this, mAllAppsActionManager, mNavCallbacks,
+ RecentsDisplayModel.getINSTANCE().get(this));
mDesktopAppLaunchTransitionManager =
new DesktopAppLaunchTransitionManager(this, SystemUiProxy.INSTANCE.get(this));
mDesktopAppLaunchTransitionManager.registerTransitions();
@@ -641,7 +643,9 @@
mTaskAnimationManager = new TaskAnimationManager(this, mDeviceState);
mOverviewComponentObserver = OverviewComponentObserver.INSTANCE.get(this);
mOverviewCommandHelper = new OverviewCommandHelper(this,
- mOverviewComponentObserver, mTaskAnimationManager);
+ mOverviewComponentObserver, mTaskAnimationManager,
+ RecentsDisplayModel.getINSTANCE().get(this),
+ SystemUiProxy.INSTANCE.get(this).getFocusState(), mTaskbarManager);
mResetGestureInputConsumer = new ResetGestureInputConsumer(
mTaskAnimationManager, mTaskbarManager::getCurrentActivityContext);
mInputConsumer.registerInputConsumer();
diff --git a/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java b/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
index adc45ae..d79a8ea 100644
--- a/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
+++ b/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
@@ -22,6 +22,7 @@
import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.quickstep.OverviewComponentObserver;
import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.RecentsModel;
import com.android.quickstep.RotationTouchHelper;
import com.android.quickstep.SimpleOrientationTouchTransformer;
import com.android.quickstep.SystemUiProxy;
@@ -29,6 +30,7 @@
import com.android.quickstep.fallback.window.RecentsDisplayModel;
import com.android.quickstep.logging.SettingsChangeLogger;
import com.android.quickstep.util.AsyncClockEventDelegate;
+import com.android.quickstep.util.ContextualSearchHapticManager;
import com.android.quickstep.util.ContextualSearchStateManager;
/**
@@ -57,10 +59,14 @@
RotationTouchHelper getRotationTouchHelper();
+ ContextualSearchHapticManager getContextualSearchHapticManager();
+
ContextualSearchStateManager getContextualSearchStateManager();
RecentsAnimationDeviceState getRecentsAnimationDeviceState();
+ RecentsModel getRecentsModel();
+
SettingsChangeLogger getSettingsChangeLogger();
SimpleOrientationTouchTransformer getSimpleOrientationTouchTransformer();
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt b/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt
index 31a1be8..95a3ec2 100644
--- a/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt
@@ -50,10 +50,14 @@
DaggerSingletonObject<RecentsDisplayModel>(
QuickstepBaseAppComponent::getRecentsDisplayModel
)
+
+ @JvmStatic
+ fun enableOverviewInWindow() =
+ Flags.enableFallbackOverviewInWindow() || Flags.enableLauncherOverviewInWindow()
}
init {
- if (Flags.enableFallbackOverviewInWindow() || Flags.enableLauncherOverviewInWindow()) {
+ if (enableOverviewInWindow()) {
displayManager.registerDisplayListener(displayListener, Executors.MAIN_EXECUTOR.handler)
// In the scenario where displays were added before this display listener was
// registered, we should store the RecentsDisplayResources for those displays
diff --git a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
index 002a4e8..b1a5920 100644
--- a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
@@ -66,14 +66,10 @@
)
tasks.value = MapForStateFlow(recentTasks)
- // Request data for completed tasks to prevent stale data.
- // This will prevent thumbnail and icon from being replaced and
- // null due to race condition.
- taskRequests.values.forEach { (taskKey, job) ->
- if (job.isCompleted) {
- requestTaskData(taskKey.id)
- }
- }
+ // Request data for tasks to prevent stale data.
+ // This will prevent thumbnail and icon from being replaced and null due to
+ // race condition. The new request will hit the cache and return immediately.
+ taskRequests.keys.forEach(::requestTaskData)
}
}
return tasks.map { it.values.toList() }
diff --git a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
index 1f428f3..d2f10b6 100644
--- a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
+++ b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
@@ -28,13 +28,11 @@
import com.android.quickstep.recents.data.TasksRepository
import com.android.quickstep.recents.domain.usecase.GetSysUiStatusNavFlagsUseCase
import com.android.quickstep.recents.domain.usecase.GetTaskUseCase
+import com.android.quickstep.recents.domain.usecase.IsThumbnailValidUseCase
import com.android.quickstep.recents.domain.usecase.OrganizeDesktopTasksUseCase
import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
import com.android.quickstep.recents.usecase.GetThumbnailUseCase
import com.android.quickstep.recents.viewmodel.RecentsViewData
-import com.android.quickstep.task.thumbnail.SplashAlphaUseCase
-import com.android.quickstep.task.thumbnail.TaskThumbnailViewData
-import com.android.quickstep.task.viewmodel.TaskContainerData
import com.android.quickstep.task.viewmodel.TaskOverlayViewModel
import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
import com.android.quickstep.task.viewmodel.TaskThumbnailViewModelImpl
@@ -175,14 +173,8 @@
val instance: Any =
when (modelClass) {
RecentsViewData::class.java -> RecentsViewData()
- TaskContainerData::class.java -> TaskContainerData()
- TaskThumbnailViewData::class.java -> TaskThumbnailViewData()
TaskThumbnailViewModel::class.java ->
- TaskThumbnailViewModelImpl(
- dispatcherProvider = inject(),
- getThumbnailPositionUseCase = inject(),
- splashAlphaUseCase = inject(scopeId),
- )
+ TaskThumbnailViewModelImpl(getThumbnailPositionUseCase = inject())
TaskOverlayViewModel::class.java -> {
val task = extras["Task"] as Task
TaskOverlayViewModel(
@@ -193,6 +185,8 @@
dispatcherProvider = inject(),
)
}
+ IsThumbnailValidUseCase::class.java ->
+ IsThumbnailValidUseCase(rotationStateRepository = inject())
GetTaskUseCase::class.java -> GetTaskUseCase(repository = inject())
GetThumbnailUseCase::class.java -> GetThumbnailUseCase(taskRepository = inject())
GetSysUiStatusNavFlagsUseCase::class.java -> GetSysUiStatusNavFlagsUseCase()
@@ -203,14 +197,6 @@
tasksRepository = inject(),
)
OrganizeDesktopTasksUseCase::class.java -> OrganizeDesktopTasksUseCase()
- SplashAlphaUseCase::class.java ->
- SplashAlphaUseCase(
- recentsViewData = inject(),
- taskContainerData = inject(scopeId),
- taskThumbnailViewData = inject(scopeId),
- tasksRepository = inject(),
- rotationStateRepository = inject(),
- )
else -> {
log("Factory for ${modelClass.simpleName} not defined!", Log.ERROR)
error("Factory for ${modelClass.simpleName} not defined!")
diff --git a/quickstep/src/com/android/quickstep/recents/domain/usecase/IsThumbnailValidUseCase.kt b/quickstep/src/com/android/quickstep/recents/domain/usecase/IsThumbnailValidUseCase.kt
new file mode 100644
index 0000000..02f8329
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/domain/usecase/IsThumbnailValidUseCase.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.recents.domain.usecase
+
+import android.graphics.Bitmap
+import android.view.Surface
+import com.android.quickstep.recents.data.RecentsRotationStateRepository
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
+import com.android.systemui.shared.recents.utilities.Utilities
+
+/**
+ * Use case responsible for validating the aspect ratio and rotation of a thumbnail against the
+ * expected values based on the view's dimensions and the current rotation state.
+ *
+ * This class checks if the thumbnail's aspect ratio significantly differs from the aspect ratio of
+ * the view it is intended to be displayed in, and if the thumbnail's rotation is consistent with
+ * the device's current rotation state.
+ *
+ * @property rotationStateRepository Repository providing the current rotation state of the device.
+ */
+class IsThumbnailValidUseCase(private val rotationStateRepository: RecentsRotationStateRepository) {
+ operator fun invoke(thumbnailData: ThumbnailData?, viewWidth: Int, viewHeight: Int): Boolean {
+ val thumbnail = thumbnailData?.thumbnail ?: return false
+ return !isInaccurateThumbnail(thumbnail, viewWidth, viewHeight, thumbnailData.rotation)
+ }
+
+ private fun isInaccurateThumbnail(
+ thumbnail: Bitmap,
+ viewWidth: Int,
+ viewHeight: Int,
+ rotation: Int,
+ ): Boolean =
+ isAspectRatioDifferentFromViewAspectRatio(
+ thumbnail = thumbnail,
+ width = viewWidth.toFloat(),
+ height = viewHeight.toFloat(),
+ ) || isRotationDifferentFromTask(rotation)
+
+ private fun isAspectRatioDifferentFromViewAspectRatio(
+ thumbnail: Bitmap,
+ width: Float,
+ height: Float,
+ ): Boolean {
+ return Utilities.isRelativePercentDifferenceGreaterThan(
+ /* first = */ width / height,
+ /* second = */ thumbnail.width / thumbnail.height.toFloat(),
+ /* bound = */ PreviewPositionHelper.MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT,
+ )
+ }
+
+ private fun isRotationDifferentFromTask(thumbnailRotation: Int): Boolean {
+ val rotationState = rotationStateRepository.getRecentsRotationState()
+ return if (rotationState.orientationHandlerRotation == Surface.ROTATION_0) {
+ (rotationState.activityRotation - thumbnailRotation) % 2 != 0
+ } else {
+ rotationState.orientationHandlerRotation != thumbnailRotation
+ }
+ }
+}
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 961446f..0b299ee 100644
--- a/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModel.kt
+++ b/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModel.kt
@@ -24,8 +24,10 @@
import com.android.quickstep.recents.domain.model.TaskModel
import com.android.quickstep.recents.domain.usecase.GetSysUiStatusNavFlagsUseCase
import com.android.quickstep.recents.domain.usecase.GetTaskUseCase
+import com.android.quickstep.recents.domain.usecase.IsThumbnailValidUseCase
import com.android.quickstep.recents.viewmodel.RecentsViewData
import com.android.quickstep.views.TaskViewType
+import com.android.systemui.shared.recents.model.ThumbnailData
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -45,6 +47,7 @@
recentsViewData: RecentsViewData,
private val getTaskUseCase: GetTaskUseCase,
private val getSysUiStatusNavFlagsUseCase: GetSysUiStatusNavFlagsUseCase,
+ private val isThumbnailValidUseCase: IsThumbnailValidUseCase,
dispatcherProvider: DispatcherProvider,
) {
private var taskIds = MutableStateFlow(emptySet<Int>())
@@ -74,10 +77,12 @@
.flowOn(dispatcherProvider.background)
fun bind(vararg taskId: TaskId) {
- Log.d(TAG, "bind: $taskId")
- taskIds.value = taskId.toSet()
+ taskIds.value = taskId.toSet().also { Log.d(TAG, "bind: $it") }
}
+ fun isThumbnailValid(thumbnail: ThumbnailData?, width: Int, height: Int): Boolean =
+ isThumbnailValidUseCase(thumbnail, width, height)
+
private fun mapToTaskTile(tasks: List<TaskData>, isLiveTile: Boolean): TaskTileUiState {
val firstThumbnailData = (tasks.firstOrNull() as? TaskData.Data)?.thumbnailData
return TaskTileUiState(
diff --git a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt
index a1f8454..2465a46 100644
--- a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt
+++ b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt
@@ -27,8 +27,6 @@
// The settled set of visible taskIds that is updated after RecentsView scroll settles.
val settledFullyVisibleTaskIds = MutableStateFlow(emptySet<Int>())
- val thumbnailSplashProgress = MutableStateFlow(0f)
-
// A list of taskIds that are associated with a RecentsAnimationController. */
val runningTaskIds = MutableStateFlow(emptySet<Int>())
diff --git a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
index 73332fc..5ff8aaa 100644
--- a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
+++ b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
@@ -42,10 +42,6 @@
recentsViewData.overlayEnabled.value = isOverlayEnabled
}
- fun updateThumbnailSplashProgress(taskThumbnailSplashAlpha: Float) {
- recentsViewData.thumbnailSplashProgress.value = taskThumbnailSplashAlpha
- }
-
suspend fun waitForThumbnailsToUpdate(updatedThumbnails: Map<Int, ThumbnailData>?) {
if (updatedThumbnails.isNullOrEmpty()) return
combine(
diff --git a/quickstep/src/com/android/quickstep/recents/viewmodel/TaskContainerViewModel.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/TaskContainerViewModel.kt
deleted file mode 100644
index 723df55..0000000
--- a/quickstep/src/com/android/quickstep/recents/viewmodel/TaskContainerViewModel.kt
+++ /dev/null
@@ -1,26 +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.quickstep.recents.viewmodel
-
-import com.android.quickstep.task.thumbnail.SplashAlphaUseCase
-import kotlinx.coroutines.flow.firstOrNull
-import kotlinx.coroutines.runBlocking
-
-class TaskContainerViewModel(private val splashAlphaUseCase: SplashAlphaUseCase) {
- fun shouldShowThumbnailSplash(taskId: Int): Boolean =
- (runBlocking { splashAlphaUseCase.execute(taskId).firstOrNull() } ?: 0f) > 0f
-}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/SplashAlphaUseCase.kt b/quickstep/src/com/android/quickstep/task/thumbnail/SplashAlphaUseCase.kt
deleted file mode 100644
index 7673c71..0000000
--- a/quickstep/src/com/android/quickstep/task/thumbnail/SplashAlphaUseCase.kt
+++ /dev/null
@@ -1,90 +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.quickstep.task.thumbnail
-
-import android.graphics.Bitmap
-import android.view.Surface
-import com.android.quickstep.recents.data.RecentTasksRepository
-import com.android.quickstep.recents.data.RecentsRotationStateRepository
-import com.android.quickstep.recents.viewmodel.RecentsViewData
-import com.android.quickstep.task.viewmodel.TaskContainerData
-import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
-import com.android.systemui.shared.recents.utilities.Utilities
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
-
-class SplashAlphaUseCase(
- private val recentsViewData: RecentsViewData,
- private val taskContainerData: TaskContainerData,
- private val taskThumbnailViewData: TaskThumbnailViewData,
- private val tasksRepository: RecentTasksRepository,
- private val rotationStateRepository: RecentsRotationStateRepository,
-) {
- fun execute(taskId: Int): Flow<Float> =
- combine(
- taskThumbnailViewData.width,
- taskThumbnailViewData.height,
- tasksRepository.getThumbnailById(taskId),
- taskContainerData.thumbnailSplashProgress,
- recentsViewData.thumbnailSplashProgress
- ) { width, height, thumbnailData, taskSplashProgress, globalSplashProgress ->
- val thumbnail = thumbnailData?.thumbnail
- when {
- thumbnail == null -> 0f
- taskSplashProgress > 0f -> taskSplashProgress
- globalSplashProgress > 0f &&
- isInaccurateThumbnail(thumbnail, thumbnailData.rotation, width, height) ->
- globalSplashProgress
- else -> 0f
- }
- }
- .distinctUntilChanged()
-
- private fun isInaccurateThumbnail(
- thumbnail: Bitmap,
- thumbnailRotation: Int,
- width: Int,
- height: Int
- ): Boolean {
- return isThumbnailAspectRatioDifferentFromThumbnailData(thumbnail, width, height) ||
- isThumbnailRotationDifferentFromTask(thumbnailRotation)
- }
-
- private fun isThumbnailAspectRatioDifferentFromThumbnailData(
- thumbnail: Bitmap,
- viewWidth: Int,
- viewHeight: Int
- ): Boolean {
- val viewAspect: Float = viewWidth / viewHeight.toFloat()
- val thumbnailAspect: Float = thumbnail.width / thumbnail.height.toFloat()
- return Utilities.isRelativePercentDifferenceGreaterThan(
- viewAspect,
- thumbnailAspect,
- PreviewPositionHelper.MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT
- )
- }
-
- private fun isThumbnailRotationDifferentFromTask(thumbnailRotation: Int): Boolean {
- val rotationState = rotationStateRepository.getRecentsRotationState()
- return if (rotationState.orientationHandlerRotation == Surface.ROTATION_0) {
- (rotationState.activityRotation - thumbnailRotation) % 2 != 0
- } else {
- rotationState.orientationHandlerRotation != thumbnailRotation
- }
- }
-}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
index 28152ec..63e93ba 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
@@ -50,10 +50,6 @@
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.dropWhile
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
class TaskThumbnailView : FrameLayout, ViewPool.Reusable {
@@ -62,8 +58,6 @@
// This is initialised here and set in onAttachedToWindow because onLayout can be called before
// onAttachedToWindow so this property needs to be initialised as it is used below.
- private var viewData: TaskThumbnailViewData = RecentsDependencies.get(this)
-
private lateinit var viewModel: TaskThumbnailViewModel
private lateinit var viewAttachedScope: CoroutineScope
@@ -110,18 +104,7 @@
CoroutineScope(
SupervisorJob() + Dispatchers.Main.immediate + CoroutineName("TaskThumbnailView")
)
- viewData = RecentsDependencies.get(this)
- updateViewDataValues()
viewModel = RecentsDependencies.get(this)
- viewModel.splashAlpha
- .dropWhile { it == 0f }
- .flowOn(dispatcherProvider.background)
- .onEach { splashAlpha ->
- splashBackground.alpha = splashAlpha
- splashIcon.alpha = splashAlpha
- }
- .launchIn(viewAttachedScope)
-
clipToOutline = true
outlineProvider =
object : ViewOutlineProvider() {
@@ -144,16 +127,9 @@
resetViews()
}
- override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
- super.onLayout(changed, left, top, right, bottom)
- if (changed) {
- updateViewDataValues()
- }
- }
-
fun setState(state: TaskThumbnailUiState, taskId: Int? = null) {
- logDebug("taskId: $taskId - uiState changed from: $uiState to: $state")
if (uiState == state) return
+ logDebug("taskId: $taskId - uiState changed from: $uiState to: $state")
uiState = state
resetViews()
when (state) {
@@ -164,6 +140,12 @@
}
}
+ /**
+ * Updates the alpha of the dim layer on top of this view. If dimAlpha is 0, no dimming is
+ * applied; if dimAlpha is 1, the thumbnail will be the extracted background color.
+ *
+ * @param tintAmount The amount of alpha that will be applied to the dim layer.
+ */
fun updateTintAmount(tintAmount: Float) {
dimAlpha[ScrimViewAlpha.TintAmount.ordinal].value = tintAmount
}
@@ -172,9 +154,9 @@
dimAlpha[ScrimViewAlpha.MenuProgress.ordinal].value = progress * MAX_SCRIM_ALPHA
}
- private fun updateViewDataValues() {
- viewData.width.value = width
- viewData.height.value = height
+ fun updateSplashAlpha(value: Float) {
+ splashBackground.alpha = value
+ splashIcon.alpha = value
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewData.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewData.kt
deleted file mode 100644
index 3502029..0000000
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewData.kt
+++ /dev/null
@@ -1,24 +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.quickstep.task.thumbnail
-
-import kotlinx.coroutines.flow.MutableStateFlow
-
-class TaskThumbnailViewData {
- val width = MutableStateFlow(0)
- val height = MutableStateFlow(0)
-}
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskContainerData.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskContainerData.kt
deleted file mode 100644
index 279ce39..0000000
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskContainerData.kt
+++ /dev/null
@@ -1,23 +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.quickstep.task.viewmodel
-
-import kotlinx.coroutines.flow.MutableStateFlow
-
-class TaskContainerData {
- val thumbnailSplashProgress = MutableStateFlow(0f)
-}
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
index c89bf01..e641737 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
@@ -17,13 +17,9 @@
package com.android.quickstep.task.viewmodel
import android.graphics.Matrix
-import kotlinx.coroutines.flow.Flow
/** ViewModel for representing TaskThumbnails */
interface TaskThumbnailViewModel {
- /** Provides the alpha of the splash icon */
- val splashAlpha: Flow<Float>
-
/** Attaches this ViewModel to a specific task id for it to provide data from. */
fun bind(taskId: Int)
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
index 635d08b..94c40d1 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
@@ -19,32 +19,17 @@
import android.app.ActivityTaskManager.INVALID_TASK_ID
import android.graphics.Matrix
import android.util.Log
-import com.android.launcher3.util.coroutines.DispatcherProvider
import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
import com.android.quickstep.recents.usecase.ThumbnailPositionState
-import com.android.quickstep.task.thumbnail.SplashAlphaUseCase
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.flowOn
-@OptIn(ExperimentalCoroutinesApi::class)
class TaskThumbnailViewModelImpl(
- dispatcherProvider: DispatcherProvider,
- private val getThumbnailPositionUseCase: GetThumbnailPositionUseCase,
- private val splashAlphaUseCase: SplashAlphaUseCase,
+ private val getThumbnailPositionUseCase: GetThumbnailPositionUseCase
) : TaskThumbnailViewModel {
- private val splashProgress = MutableStateFlow(flowOf(0f))
private var taskId: Int = INVALID_TASK_ID
- override val splashAlpha =
- splashProgress.flatMapLatest { it }.flowOn(dispatcherProvider.background)
-
override fun bind(taskId: Int) {
Log.d(TAG, "bind taskId: $taskId")
this.taskId = taskId
- splashProgress.value = splashAlphaUseCase.execute(taskId)
}
override fun getThumbnailPositionState(width: Int, height: Int, isRtl: Boolean): Matrix =
diff --git a/quickstep/src/com/android/quickstep/util/ContextualSearchHapticManager.kt b/quickstep/src/com/android/quickstep/util/ContextualSearchHapticManager.kt
index 286b77a..7ec605d 100644
--- a/quickstep/src/com/android/quickstep/util/ContextualSearchHapticManager.kt
+++ b/quickstep/src/com/android/quickstep/util/ContextualSearchHapticManager.kt
@@ -21,18 +21,25 @@
import android.os.VibrationEffect.Composition
import android.os.Vibrator
import com.android.launcher3.dagger.ApplicationContext
-import com.android.launcher3.util.MainThreadInitializedObject
-import com.android.launcher3.util.SafeCloseable
+import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.util.DaggerSingletonObject
import com.android.launcher3.util.VibratorWrapper
import com.android.quickstep.DeviceConfigWrapper.Companion.get
+import com.android.quickstep.dagger.QuickstepBaseAppComponent
+import javax.inject.Inject
import kotlin.math.pow
/** Manages haptics relating to Contextual Search invocations. */
+@LauncherAppSingleton
class ContextualSearchHapticManager
-internal constructor(@ApplicationContext private val context: Context) : SafeCloseable {
+@Inject
+internal constructor(
+ @ApplicationContext private val context: Context,
+ private val contextualSearchStateManager: ContextualSearchStateManager,
+ private val vibratorWrapper: VibratorWrapper,
+) {
private var searchEffect = createSearchEffect()
- private var contextualSearchStateManager = ContextualSearchStateManager.INSTANCE[context]
private fun createSearchEffect() =
if (
@@ -50,7 +57,7 @@
/** Indicates that search has been invoked. */
fun vibrateForSearch() {
- searchEffect.let { VibratorWrapper.INSTANCE[context].vibrate(it) }
+ searchEffect.let { vibratorWrapper.vibrate(it) }
}
/** Indicates that search will be invoked if the current gesture is maintained. */
@@ -93,13 +100,13 @@
composition.addPrimitive(Composition.PRIMITIVE_LOW_TICK, scale)
}
}
- VibratorWrapper.INSTANCE[context].vibrate(composition.compose())
+ vibratorWrapper.vibrate(composition.compose())
}
}
- override fun close() {}
-
companion object {
- @JvmField val INSTANCE = MainThreadInitializedObject { ContextualSearchHapticManager(it) }
+ @JvmField
+ val INSTANCE =
+ DaggerSingletonObject(QuickstepBaseAppComponent::getContextualSearchHapticManager)
}
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index 0182969..88d14b7 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -52,9 +52,9 @@
import com.android.launcher3.QuickstepTransitionManager
import com.android.launcher3.R
import com.android.launcher3.Utilities
+import com.android.launcher3.anim.AnimatedFloat
import com.android.launcher3.anim.PendingAnimation
import com.android.launcher3.apppairs.AppPairIcon
-import com.android.launcher3.config.FeatureFlags
import com.android.launcher3.logging.StatsLogManager.EventEnum
import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.statehandlers.DepthController
@@ -65,6 +65,7 @@
import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource
import com.android.launcher3.views.BaseDragLayer
import com.android.quickstep.TaskViewUtils
+import com.android.quickstep.util.SplitScreenUtils.Companion.extractTopParentAndChildren
import com.android.quickstep.views.FloatingAppPairView
import com.android.quickstep.views.FloatingTaskView
import com.android.quickstep.views.GroupedTaskView
@@ -94,7 +95,7 @@
val fadeWithThumbnail: Boolean,
val isStagedTask: Boolean,
val iconView: View?,
- val contentDescription: CharSequence?
+ val contentDescription: CharSequence?,
)
}
@@ -104,7 +105,7 @@
*/
fun getFirstAnimInitViews(
taskViewSupplier: Supplier<TaskView>,
- splitSelectSourceSupplier: Supplier<SplitSelectSource?>
+ splitSelectSourceSupplier: Supplier<SplitSelectSource?>,
): SplitAnimInitProps {
val splitSelectSource = splitSelectSourceSupplier.get()
if (!splitSelectStateController.isAnimateCurrentTaskDismissal) {
@@ -116,7 +117,7 @@
fadeWithThumbnail = false,
isStagedTask = true,
iconView = null,
- splitSelectSource.itemInfo.contentDescription
+ splitSelectSource.itemInfo.contentDescription,
)
} else if (splitSelectStateController.isDismissingFromSplitPair) {
// Initiating split from overview, but on a split pair
@@ -131,7 +132,7 @@
fadeWithThumbnail = true,
isStagedTask = true,
iconView = container.iconView.asView(),
- container.task.titleDescription
+ container.task.titleDescription,
)
}
}
@@ -151,7 +152,7 @@
fadeWithThumbnail = true,
isStagedTask = true,
iconView = it.iconView.asView(),
- it.task.titleDescription
+ it.task.titleDescription,
)
}
}
@@ -189,29 +190,25 @@
deviceProfile: DeviceProfile,
taskViewWidth: Int,
taskViewHeight: Int,
- isPrimaryTaskSplitting: Boolean
+ isPrimaryTaskSplitting: Boolean,
) {
val snapshot = taskContainer.snapshotView
val iconView: View = taskContainer.iconView.asView()
- if (!enableRefactorTaskThumbnail()) {
+ if (enableRefactorTaskThumbnail()) {
+ builder.add(
+ AnimatedFloat { v -> taskContainer.taskView.splitSplashAlpha = v }
+ .animateToValue(1f)
+ )
+ } else {
val thumbnailViewDeprecated = taskContainer.thumbnailViewDeprecated
builder.add(
ObjectAnimator.ofFloat(
thumbnailViewDeprecated,
TaskThumbnailViewDeprecated.SPLASH_ALPHA,
- 1f
+ 1f,
)
)
thumbnailViewDeprecated.setShowSplashForSplitSelection(true)
- } else {
- builder.add(
- ValueAnimator.ofFloat(0f, 1f).apply {
- addUpdateListener {
- taskContainer.taskContainerData.thumbnailSplashProgress.value =
- it.animatedFraction
- }
- }
- )
}
// With the new `IconAppChipView`, we always want to keep the chip pinned to the
// top left of the task / thumbnail.
@@ -220,7 +217,7 @@
ObjectAnimator.ofFloat(
(iconView as IconAppChipView).splitTranslationX,
MULTI_PROPERTY_VALUE,
- 0f
+ 0f,
)
)
builder.add(
@@ -306,7 +303,7 @@
fun addScrimBehindAnim(
pendingAnimation: PendingAnimation,
container: RecentsViewContainer,
- context: Context
+ context: Context,
): View {
val scrim = View(context)
val recentsView = container.getOverviewPanel<RecentsView<*, *>>()
@@ -334,8 +331,8 @@
Interpolators.clampToProgress(
timings.backingScrimFadeInterpolator,
timings.backingScrimFadeInStartOffset,
- timings.backingScrimFadeInEndOffset
- )
+ timings.backingScrimFadeInEndOffset,
+ ),
)
return scrim
@@ -358,7 +355,7 @@
fun createPlaceholderDismissAnim(
container: RecentsViewContainer,
splitDismissEvent: EventEnum,
- duration: Long?
+ duration: Long?,
): AnimatorSet {
val animatorSet = AnimatorSet()
duration?.let { animatorSet.duration = it }
@@ -375,7 +372,7 @@
Rect(0, 0, floatingTask.width, floatingTask.height),
false,
null,
- onScreenRectF
+ onScreenRectF,
)
// Get the part of the floatingTask that intersects with the DragLayer (i.e. the
// on-screen portion)
@@ -383,7 +380,7 @@
dragLayer.left.toFloat(),
dragLayer.top.toFloat(),
dragLayer.right.toFloat(),
- dragLayer.bottom.toFloat()
+ dragLayer.bottom.toFloat(),
)
animatorSet.play(
ObjectAnimator.ofFloat(
@@ -393,8 +390,8 @@
floatingTask,
onScreenRectF,
floatingTask.stagePosition,
- container.deviceProfile
- )
+ container.deviceProfile,
+ ),
)
)
animatorSet.addListener(
@@ -403,7 +400,7 @@
splitSelectStateController.resetState()
safeRemoveViewFromDragLayer(
container,
- splitSelectStateController.splitInstructionsView
+ splitSelectStateController.splitInstructionsView,
)
}
}
@@ -429,8 +426,8 @@
Interpolators.clampToProgress(
Interpolators.LINEAR,
timings.instructionsContainerFadeInStartOffset,
- timings.instructionsContainerFadeInEndOffset
- )
+ timings.instructionsContainerFadeInEndOffset,
+ ),
)
anim.addFloat(
splitInstructionsView,
@@ -440,8 +437,8 @@
Interpolators.clampToProgress(
Interpolators.EMPHASIZED_DECELERATE,
timings.instructionsUnfoldStartOffset,
- timings.instructionsUnfoldEndOffset
- )
+ timings.instructionsUnfoldEndOffset,
+ ),
)
return anim
}
@@ -459,7 +456,7 @@
fun playAnimPlaceholderToFullscreen(
container: RecentsViewContainer,
view: View,
- resetCallback: Optional<Runnable>
+ resetCallback: Optional<Runnable>,
) {
val stagedTaskView = view as FloatingTaskView
@@ -481,7 +478,7 @@
RectF(firstTaskStartingBounds),
firstTaskEndingBounds,
false /* fadeWithThumbnail */,
- true /* isStagedTask */
+ true, /* isStagedTask */
)
pendingAnimation.addEndListener {
@@ -511,7 +508,7 @@
info: TransitionInfo?,
t: Transaction?,
finishCallback: Runnable,
- cornerRadius: Float
+ cornerRadius: Float,
) {
if (info == null && t == null) {
// (Legacy animation) Tapping a split tile in Overview
@@ -530,7 +527,7 @@
nonApps,
stateManager,
depthController,
- finishCallback
+ finishCallback,
)
return
@@ -548,7 +545,7 @@
depthController,
info,
t,
- finishCallback
+ finishCallback,
)
} else if (launchingIconView != null) {
// Tapping an app pair icon
@@ -563,7 +560,7 @@
info,
t,
finishCallback,
- cornerRadius
+ cornerRadius,
)
} else {
composeFullscreenIconSplitLaunchAnimator(
@@ -571,7 +568,7 @@
info,
t,
finishCallback,
- appPairLaunchingAppIndex
+ appPairLaunchingAppIndex,
)
}
} else {
@@ -587,7 +584,7 @@
info,
t,
finishCallback,
- cornerRadius
+ cornerRadius,
)
}
}
@@ -603,7 +600,7 @@
depthController: DepthController?,
info: TransitionInfo,
t: Transaction,
- finishCallback: Runnable
+ finishCallback: Runnable,
) {
TaskViewUtils.composeRecentsSplitLaunchAnimator(
launchingTaskView,
@@ -611,7 +608,7 @@
depthController,
info,
t,
- finishCallback
+ finishCallback,
)
}
@@ -629,7 +626,7 @@
nonApps: Array<RemoteAnimationTarget>,
stateManager: StateManager<*, *>,
depthController: DepthController?,
- finishCallback: Runnable
+ finishCallback: Runnable,
) {
TaskViewUtils.composeRecentsSplitLaunchAnimatorLegacy(
launchingTaskView,
@@ -640,7 +637,7 @@
nonApps,
stateManager,
depthController,
- finishCallback
+ finishCallback,
)
}
@@ -651,7 +648,7 @@
*/
fun hasChangesForBothAppPairs(
launchingIconView: AppPairIcon,
- transitionInfo: TransitionInfo
+ transitionInfo: TransitionInfo,
): Int {
val intent1 = launchingIconView.info.getFirstApp().intent.component?.packageName
val intent2 = launchingIconView.info.getSecondApp().intent.component?.packageName
@@ -712,7 +709,7 @@
transitionInfo: TransitionInfo,
t: Transaction,
finishCallback: Runnable,
- windowRadius: Float
+ windowRadius: Float,
) {
// If launching an app pair from Taskbar inside of an app context (no access to Launcher),
// use the scale-up animation
@@ -721,7 +718,7 @@
transitionInfo,
t,
finishCallback,
- WINDOWING_MODE_MULTI_WINDOW
+ WINDOWING_MODE_MULTI_WINDOW,
)
return
}
@@ -753,8 +750,7 @@
(!isLeftRightSplit && change.endAbsBounds.top <= 0)
}
val dividerPos =
- if (isLeftRightSplit) leftTopApp.endAbsBounds.right
- else leftTopApp.endAbsBounds.bottom
+ if (isLeftRightSplit) leftTopApp.endAbsBounds.right else leftTopApp.endAbsBounds.bottom
// Create a new floating view in Launcher, positioned above the launching icon
val drawableArea = launchingIconView.iconDrawableArea
@@ -769,7 +765,7 @@
drawableArea,
appIcon1,
appIcon2,
- dividerPos
+ dividerPos,
)
floatingView.bringToFront()
@@ -780,7 +776,7 @@
finishCallback,
launcher,
floatingView,
- mainRootCandidate
+ mainRootCandidate,
)
iconLaunchValueAnimator.addListener(
object : AnimatorListenerAdapter() {
@@ -806,7 +802,7 @@
transitionInfo: TransitionInfo,
t: Transaction,
finishCallback: Runnable,
- launchFullscreenIndex: Int
+ launchFullscreenIndex: Int,
) {
// If launching an app pair from Taskbar inside of an app context (no access to Launcher),
// use the scale-up animation
@@ -815,7 +811,7 @@
transitionInfo,
t,
finishCallback,
- WINDOWING_MODE_FULLSCREEN
+ WINDOWING_MODE_FULLSCREEN,
)
return
}
@@ -867,7 +863,7 @@
drawableArea,
appIcon,
null /*appIcon2*/,
- 0 /*dividerPos*/
+ 0, /*dividerPos*/
)
floatingView.bringToFront()
launchAnimation.play(
@@ -882,7 +878,7 @@
finishCallback: Runnable,
launcher: QuickstepLauncher,
floatingView: FloatingAppPairView,
- rootCandidate: Change
+ rootCandidate: Change,
): ValueAnimator {
val progressUpdater = ValueAnimator.ofFloat(0f, 1f)
val timings = AnimUtils.getDeviceAppPairLaunchTimings(dp.isTablet)
@@ -896,7 +892,7 @@
Interpolators.LINEAR,
valueAnimator.animatedFraction,
timings.appRevealStartOffset,
- timings.appRevealEndOffset
+ timings.appRevealEndOffset,
)
// Set the alpha of the shell layer (2 apps + divider)
@@ -913,8 +909,8 @@
Interpolators.clampToProgress(
timings.getStagedRectXInterpolator(),
timings.stagedRectSlideStartOffset,
- timings.stagedRectSlideEndOffset
- )
+ timings.stagedRectSlideEndOffset,
+ ),
)
var mDy =
FloatProp(
@@ -923,8 +919,8 @@
Interpolators.clampToProgress(
Interpolators.EMPHASIZED,
timings.stagedRectSlideStartOffset,
- timings.stagedRectSlideEndOffset
- )
+ timings.stagedRectSlideEndOffset,
+ ),
)
var mScaleX =
FloatProp(
@@ -933,8 +929,8 @@
Interpolators.clampToProgress(
Interpolators.EMPHASIZED,
timings.stagedRectSlideStartOffset,
- timings.stagedRectSlideEndOffset
- )
+ timings.stagedRectSlideEndOffset,
+ ),
)
var mScaleY =
FloatProp(
@@ -943,8 +939,8 @@
Interpolators.clampToProgress(
Interpolators.EMPHASIZED,
timings.stagedRectSlideStartOffset,
- timings.stagedRectSlideEndOffset
- )
+ timings.stagedRectSlideEndOffset,
+ ),
)
override fun onUpdate(percent: Float, initOnly: Boolean) {
@@ -979,42 +975,22 @@
transitionInfo: TransitionInfo,
t: Transaction,
finishCallback: Runnable,
- windowingMode: Int
+ windowingMode: Int,
) {
val launchAnimation = AnimatorSet()
val progressUpdater = ValueAnimator.ofFloat(0f, 1f)
progressUpdater.setDuration(QuickstepTransitionManager.APP_LAUNCH_DURATION)
progressUpdater.interpolator = Interpolators.EMPHASIZED
- var rootCandidate: Change? = null
-
- for (change in transitionInfo.changes) {
- val taskInfo: RunningTaskInfo = change.taskInfo ?: continue
-
- // TODO (b/316490565): Replace this logic when SplitBounds is available to
- // startAnimation() and we can know the precise taskIds of launching tasks.
- if (
- taskInfo.windowingMode == windowingMode &&
- (change.mode == TRANSIT_OPEN || change.mode == TRANSIT_TO_FRONT)
- ) {
- // Found one!
- rootCandidate = change
- break
- }
- }
-
- // If we could not find a proper root candidate, something went wrong.
- check(rootCandidate != null) { "Could not find a split root candidate" }
-
- // Recurse up the tree until parent is null, then we've found our root.
- var parentToken: WindowContainerToken? = rootCandidate.parent
- while (parentToken != null) {
- rootCandidate = transitionInfo.getChange(parentToken) ?: break
- parentToken = rootCandidate.parent
- }
-
- // Make sure nothing weird happened, like getChange() returning null.
- check(rootCandidate != null) { "Failed to find a root leash" }
+ val splitTree: Pair<Change, List<Change>>? = extractTopParentAndChildren(transitionInfo)
+ check(splitTree != null) { "Could not find a split root candidate" }
+ val rootCandidate = splitTree.first
+ val stageRootTaskIds: Set<Int> = splitTree.second
+ .map { it.taskInfo!!.taskId }
+ .toSet()
+ val leafTasks: List<Change> = transitionInfo.changes
+ .filter { it.taskInfo != null && it.taskInfo!!.parentTaskId in stageRootTaskIds}
+ .toList()
// Starting position is a 34% size tile centered in the middle of the screen.
// Ending position is the full device screen.
@@ -1048,6 +1024,29 @@
override fun onAnimationEnd(animation: Animator) {
finishCallback.run()
}
+
+ override fun onAnimationStart(animation: Animator) {
+ // Reset leaf and stage root tasks, animation can begin from freeform windows
+ for (leaf in leafTasks) {
+ val endAbsBounds = leaf.endAbsBounds
+
+ t.setAlpha(leaf.leash, 1f)
+ t.setCrop(leaf.leash, 0f, 0f,
+ endAbsBounds.width().toFloat(), endAbsBounds.height().toFloat())
+ t.setPosition(leaf.leash, 0f, 0f)
+ }
+
+ for (stageRoot in splitTree.second) {
+ val endAbsBounds = stageRoot.endAbsBounds
+
+ t.setAlpha(stageRoot.leash, 1f)
+ t.setCrop(stageRoot.leash, 0f, 0f,
+ endAbsBounds.width().toFloat(), endAbsBounds.height().toFloat())
+ t.setPosition(stageRoot.leash, endAbsBounds.left.toFloat(),
+ endAbsBounds.top.toFloat())
+ }
+ t.apply()
+ }
}
)
@@ -1066,7 +1065,7 @@
transitionInfo: TransitionInfo,
t: Transaction,
finishCallback: Runnable,
- cornerRadius: Float
+ cornerRadius: Float,
) {
var splitRoot1: Change? = null
var splitRoot2: Change? = null
@@ -1131,7 +1130,7 @@
Interpolators.LINEAR,
valueAnimator.animatedFraction,
0.8f,
- 1f
+ 1f,
)
for (leash in openingTargets) {
animTransaction.setAlpha(leash, progress)
diff --git a/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt b/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt
index 4005c5a..7787e30 100644
--- a/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt
@@ -46,8 +46,8 @@
* Given a TransitionInfo, generates the tree structure for those changes and extracts out
* the top most root and it's two immediate children. Changes can be provided in any order.
*
- * @return a [Pair] where first -> top most split root, second -> [List] of 2,
- * leftTop/bottomRight stage roots
+ * @return null if no root is found, otherwise a [Pair] where first -> top most split root,
+ * second -> [List] of 2, leftTop/bottomRight stage roots
*/
fun extractTopParentAndChildren(
transitionInfo: TransitionInfo
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index 828322b..7d5b471 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -169,9 +169,6 @@
if (sourceRectHint.isEmpty()) {
mSourceRectHint.set(getEnterPipWithOverlaySrcRectHint(appBounds, aspectRatio));
- // Create a new overlay layer. We do not call detach on this instance, it's propagated
- // to other classes like PipTaskOrganizer / RecentsAnimationController to complete
- // the cleanup.
mPipContentOverlay = new PipContentOverlay.PipAppIconOverlay(view.getContext(),
mAppBounds, mDestinationBounds,
new IconProvider(context).getIcon(mActivityInfo), appIconSizePx);
diff --git a/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java b/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java
index ceffbe4..35e90f2 100644
--- a/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java
+++ b/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java
@@ -103,23 +103,28 @@
}
case DIRECTION_RIGHT: {
int boundedIndex =
- cycle ? (nextIndex < 0 ? maxSize - 1 : nextIndex) : Math.max(
- nextIndex, 0);
+ cycle ? (nextIndex < 0 ? maxSize - 1 : nextIndex)
+ : Math.max(nextIndex, 0);
boolean inOriginalTop = mOriginalTopRowIds.contains(currentPageTaskViewId);
return inOriginalTop ? mTopRowIds.get(boundedIndex)
: mBottomRowIds.get(boundedIndex);
}
case DIRECTION_TAB: {
int boundedIndex =
- cycle ? nextIndex < 0 ? maxSize - 1 : nextIndex % maxSize : Math.min(
- nextIndex, maxSize - 1);
+ cycle ? (nextIndex < 0 ? maxSize - 1 : nextIndex % maxSize)
+ : Math.min(nextIndex, maxSize - 1);
if (delta >= 0) {
return inTop && mTopRowIds.get(index) != mBottomRowIds.get(index)
? mBottomRowIds.get(index)
: mTopRowIds.get(boundedIndex);
} else {
if (mTopRowIds.contains(currentPageTaskViewId)) {
- return mBottomRowIds.get(boundedIndex);
+ if (boundedIndex < 0) {
+ // If no cycling, always return the first task.
+ return mTopRowIds.get(0);
+ } else {
+ return mBottomRowIds.get(boundedIndex);
+ }
} else {
// Go up to top if there is task above
return mTopRowIds.get(index) != mBottomRowIds.get(index)
@@ -132,4 +137,12 @@
return currentPageTaskViewId;
}
}
+
+ /**
+ * Returns the column of a task's id in the grid.
+ */
+ public int getColumn(int taskViewId) {
+ return mTopRowIds.contains(taskViewId) ? mTopRowIds.indexOf(taskViewId)
+ : mBottomRowIds.indexOf(taskViewId);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
index bb6829a..da160f1 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
@@ -53,9 +53,10 @@
import com.android.quickstep.recents.di.get
import com.android.quickstep.recents.domain.model.DesktopTaskBoundsData
import com.android.quickstep.recents.ui.viewmodel.DesktopTaskViewModel
+import com.android.quickstep.recents.ui.viewmodel.TaskData
import com.android.quickstep.task.thumbnail.TaskThumbnailView
+import com.android.quickstep.util.DesktopTask
import com.android.quickstep.util.RecentsOrientedState
-import com.android.systemui.shared.recents.model.Task
/** TaskView that contains all tasks that are part of the desktop. */
class DesktopTaskView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
@@ -270,10 +271,14 @@
/** Updates this desktop task to the gives task list defined in `tasks` */
fun bind(
- tasks: List<Task>,
+ desktopTask: DesktopTask,
orientedState: RecentsOrientedState,
taskOverlayFactory: TaskOverlayFactory,
) {
+ // TODO(b/370495260): Minimized tasks should not be filtered with desktop exploded view
+ // support.
+ // Minimized tasks should not be shown in Overview.
+ val tasks = desktopTask.tasks.filterNot { it.isMinimized }
if (DEBUG) {
val sb = StringBuilder()
sb.append("bind tasks=").append(tasks.size).append("\n")
@@ -364,6 +369,10 @@
taskContainer.snapshotView.contentDescription = taskContainer.task.titleDescription
}
+ override fun setIconState(container: TaskContainer, state: TaskData?) {
+ container.snapshotView.contentDescription = (state as? TaskData.Data)?.titleDescription
+ }
+
// Ignoring [onIconUnloaded] as all tasks shares the same Desktop icon
override fun onIconUnloaded(taskContainer: TaskContainer) {}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 99bfa7e..424271a 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -132,6 +132,7 @@
import android.widget.Toast;
import android.window.DesktopModeFlags;
import android.window.PictureInPictureSurfaceTransaction;
+import android.window.TransitionInfo;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -212,6 +213,7 @@
import com.android.quickstep.recents.viewmodel.RecentsViewModel;
import com.android.quickstep.util.ActiveGestureProtoLogProxy;
import com.android.quickstep.util.AnimUtils;
+import com.android.quickstep.util.DesktopTask;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.RecentsAtomicAnimationFactory;
@@ -1394,12 +1396,13 @@
RemoteAnimationTargets targets = params.getTargetSet();
if (targets != null && targets.findTask(taskId) != null) {
launchSideTaskInLiveTileMode(taskId, targets.apps, targets.wallpapers,
- targets.nonApps);
+ targets.nonApps, /* transitionInfo= */ null);
}
}
public void launchSideTaskInLiveTileMode(int taskId, RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpaper, RemoteAnimationTarget[] nonApps) {
+ RemoteAnimationTarget[] wallpaper, RemoteAnimationTarget[] nonApps,
+ @Nullable TransitionInfo transitionInfo) {
AnimatorSet anim = new AnimatorSet();
TaskView taskView = getTaskViewByTaskId(taskId);
if (taskView == null || !isTaskViewVisible(taskView)) {
@@ -1451,7 +1454,7 @@
} else {
TaskViewUtils.composeRecentsLaunchAnimator(anim, taskView, apps, wallpaper, nonApps,
true /* launcherClosing */, getStateManager(), this,
- getDepthController());
+ getDepthController(), transitionInfo);
}
anim.start();
}
@@ -1981,12 +1984,7 @@
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.bind(nonMinimizedTasks, mOrientationState,
+ desktopTaskView.bind((DesktopTask) groupTask, mOrientationState,
mTaskOverlayFactory);
} else if (groupTask instanceof SplitTask splitTask) {
Task task = splitTask.getTopLeftTask().key.id == stagedTaskIdToBeRemoved
@@ -3034,7 +3032,7 @@
final TaskView taskView;
if (needDesktopTask) {
taskView = getTaskViewFromPool(TaskViewType.DESKTOP);
- ((DesktopTaskView) taskView).bind(Arrays.asList(runningTasks),
+ ((DesktopTaskView) taskView).bind(new DesktopTask(Arrays.asList(runningTasks)),
mOrientationState, mTaskOverlayFactory);
} else if (needGroupTaskView) {
taskView = getTaskViewFromPool(TaskViewType.GROUPED);
@@ -3541,11 +3539,6 @@
}
private void setTaskThumbnailSplashAlpha(float taskThumbnailSplashAlpha) {
- if (enableRefactorTaskThumbnail()) {
- mRecentsViewModel.updateThumbnailSplashProgress(taskThumbnailSplashAlpha);
- return;
- }
-
mTaskThumbnailSplashAlpha = taskThumbnailSplashAlpha;
for (TaskView taskView : getTaskViews()) {
taskView.setTaskThumbnailSplashAlpha(taskThumbnailSplashAlpha);
@@ -6384,7 +6377,7 @@
}
/**
- * @return true if the task in on the top of the grid
+ * @return true if the task in on the bottom of the grid
*/
public boolean isOnGridBottomRow(TaskView taskView) {
return showAsGrid()
@@ -6947,7 +6940,8 @@
* 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.
+ * spring animation. As it passes the threshold of its settling state, its neighbors will
+ * spring in response to the perceived impact of the settling task.
*/
public SpringAnimation createTaskDismissSettlingSpringAnimation(TaskView draggedTaskView,
float velocity, boolean isDismissing, SingleAxisSwipeDetector detector,
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
index f610335..c7fc448 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
@@ -20,6 +20,7 @@
import android.view.View
import androidx.core.view.children
import androidx.dynamicanimation.animation.FloatPropertyCompat
+import androidx.dynamicanimation.animation.FloatValueHolder
import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
@@ -29,6 +30,7 @@
import com.android.launcher3.util.DynamicResource
import com.android.launcher3.util.IntArray
import com.android.quickstep.util.GroupTask
+import com.android.quickstep.util.TaskGridNavHelper
import com.android.quickstep.util.isExternalDisplay
import com.android.quickstep.views.RecentsView.RUNNING_TASK_ATTACH_ALPHA
import com.android.systemui.shared.recents.model.ThumbnailData
@@ -305,7 +307,8 @@
* 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.
+ * spring animation. As it passes the threshold of its settling state, its neighbors will spring
+ * in response to the perceived impact of the settling task.
*/
fun createTaskDismissSettlingSpringAnimation(
draggedTaskView: TaskView?,
@@ -320,40 +323,219 @@
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)
+ // Animate dragged task towards dismissal or rest state.
+ val draggedTaskViewSpringAnimation =
+ SpringAnimation(draggedTaskView, taskDismissFloatProperty)
+ .setSpring(createExpressiveDismissSpringForce())
+ .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()
}
- recentsView.redrawLiveTile()
}
+ .addEndListener { _, _, _, _ ->
+ if (isDismissing) {
+ recentsView.dismissTask(
+ draggedTaskView,
+ /* animateTaskView = */ false,
+ /* removeTask = */ true,
+ )
+ } else {
+ recentsView.onDismissAnimationEnds()
+ }
+ onEndRunnable()
+ }
+ if (!isDismissing) {
+ addNeighboringSpringAnimationsForDismissCancel(
+ draggedTaskView,
+ draggedTaskViewSpringAnimation,
+ recentsView.pageCount,
+ )
+ }
+ return draggedTaskViewSpringAnimation
+ }
+
+ private fun addNeighboringSpringAnimationsForDismissCancel(
+ draggedTaskView: TaskView,
+ draggedTaskViewSpringAnimation: SpringAnimation,
+ taskCount: Int,
+ ) {
+ // Empty spring animation exists for conditional start, and to drive neighboring springs.
+ val neighborsToSettle =
+ SpringAnimation(FloatValueHolder()).setSpring(createExpressiveDismissSpringForce())
+ var lastPosition = 0f
+ var startSettling = false
+ draggedTaskViewSpringAnimation.addUpdateListener { _, value, velocity ->
+ // Start the settling animation the first time the dragged task passes the origin (from
+ // negative displacement to positive displacement). We do not check for an exact value
+ // to compare to, as the update listener does not necessarily hit every value (e.g. a
+ // value of zero). Do not check again once it has started settling, as a spring can
+ // bounce past the origin multiple times depending on the stifness and damping ratio.
+ if (startSettling) return@addUpdateListener
+ if (lastPosition < 0 && value >= 0) {
+ startSettling = true
}
- .addEndListener { _, _, _, _ ->
- if (isDismissing) {
- recentsView.dismissTask(
- draggedTaskView,
- /* animateTaskView = */ false,
- /* removeTask = */ true,
+ lastPosition = value
+ if (startSettling) {
+ neighborsToSettle.setStartVelocity(velocity).animateToFinalPosition(0f)
+ }
+ }
+
+ // Add tasks before dragged index, fanning out from the dragged task.
+ // The order they are added matters, as each spring drives the next.
+ var previousNeighbor = neighborsToSettle
+ getTasksOffsetPairAdjacentToDraggedTask(draggedTaskView, towardsStart = true).forEach {
+ (taskView, offset) ->
+ previousNeighbor =
+ createNeighboringTaskViewSpringAnimation(
+ taskView,
+ offset * ADDITIONAL_DISMISS_DAMPING_RATIO,
+ previousNeighbor,
+ )
+ }
+ // Add tasks after dragged index, fanning out from the dragged task.
+ // The order they are added matters, as each spring drives the next.
+ previousNeighbor = neighborsToSettle
+ getTasksOffsetPairAdjacentToDraggedTask(draggedTaskView, towardsStart = false).forEach {
+ (taskView, offset) ->
+ previousNeighbor =
+ createNeighboringTaskViewSpringAnimation(
+ taskView,
+ offset * ADDITIONAL_DISMISS_DAMPING_RATIO,
+ previousNeighbor,
+ )
+ }
+ }
+
+ /**
+ * Gets pairs of (TaskView, offset) adjacent the dragged task in visual order.
+ *
+ * <p>Gets tasks either before or after the dragged task along with their offset from it. The
+ * offset is the distance between indices for carousels, or distance between columns for grids.
+ */
+ private fun getTasksOffsetPairAdjacentToDraggedTask(
+ draggedTaskView: TaskView,
+ towardsStart: Boolean,
+ ): Sequence<Pair<TaskView, Int>> {
+ if (recentsView.showAsGrid()) {
+ return gridTaskOffsetPairInTabOrderSequence(draggedTaskView, towardsStart)
+ } else {
+ val taskViewList = taskViews.toList()
+ val draggedTaskViewIndex = taskViewList.indexOf(draggedTaskView)
+
+ return if (towardsStart) {
+ taskViewList
+ .take(draggedTaskViewIndex)
+ .reversed()
+ .mapIndexed { index, taskView -> Pair(taskView, index + 1) }
+ .asSequence()
+ } else {
+ taskViewList
+ .takeLast(taskViewList.size - draggedTaskViewIndex - 1)
+ .mapIndexed { index, taskView -> Pair(taskView, index + 1) }
+ .asSequence()
+ }
+ }
+ }
+
+ /**
+ * Returns a sequence of pairs of (TaskViews, offsets) in the grid, ordered according to tab
+ * navigation, starting from the dragged TaskView, towards the start or end of the grid.
+ *
+ * <p>A positive delta moves forward in the tab order towards the end of the grid, while a
+ * negative value moves backward towards the beginning. The offset is the distance between
+ * columns the tasks are in.
+ */
+ private fun gridTaskOffsetPairInTabOrderSequence(
+ draggedTaskView: TaskView,
+ towardsStart: Boolean,
+ ): Sequence<Pair<TaskView, Int>> = sequence {
+ val taskGridNavHelper =
+ TaskGridNavHelper(
+ recentsView.topRowIdArray,
+ recentsView.bottomRowIdArray,
+ getLargeTaskViewIds(),
+ /* hasAddDesktopButton= */ false,
+ )
+ val draggedTaskViewColumn = taskGridNavHelper.getColumn(draggedTaskView.taskViewId)
+ var nextTaskView: TaskView? = draggedTaskView
+ var previousTaskView: TaskView? = null
+ while (nextTaskView != previousTaskView && nextTaskView != null) {
+ previousTaskView = nextTaskView
+ nextTaskView =
+ recentsView.getTaskViewFromTaskViewId(
+ taskGridNavHelper.getNextGridPage(
+ nextTaskView.taskViewId,
+ if (towardsStart) -1 else 1,
+ TaskGridNavHelper.DIRECTION_TAB,
+ /* cycle = */ false,
)
- }
- onEndRunnable()
+ )
+ if (nextTaskView != null && nextTaskView != previousTaskView) {
+ val columnOffset =
+ abs(
+ taskGridNavHelper.getColumn(nextTaskView.taskViewId) - draggedTaskViewColumn
+ )
+ yield(Pair(nextTaskView, columnOffset))
}
+ }
+ }
+
+ /** Creates a neighboring task view spring, driven by the spring of its neighbor. */
+ private fun createNeighboringTaskViewSpringAnimation(
+ taskView: TaskView,
+ dampingOffsetRatio: Float,
+ previousNeighborSpringAnimation: SpringAnimation,
+ ): SpringAnimation {
+ val neighboringTaskViewSpringAnimation =
+ SpringAnimation(
+ taskView,
+ FloatPropertyCompat.createFloatPropertyCompat(
+ taskView.secondaryDismissTranslationProperty
+ ),
+ )
+ .setSpring(createExpressiveDismissSpringForce(dampingOffsetRatio))
+ // Update live tile on spring animation.
+ if (taskView.isRunningTask && recentsView.enableDrawingLiveTile) {
+ neighboringTaskViewSpringAnimation.addUpdateListener { _, _, _ ->
+ recentsView.runActionOnRemoteHandles { remoteTargetHandle ->
+ remoteTargetHandle.taskViewSimulator.taskSecondaryTranslation.value =
+ taskView.secondaryDismissTranslationProperty.get(taskView)
+ }
+ recentsView.redrawLiveTile()
+ }
+ }
+ // Drive current neighbor's spring with the previous neighbor's.
+ previousNeighborSpringAnimation.addUpdateListener { _, value, _ ->
+ neighboringTaskViewSpringAnimation.animateToFinalPosition(value)
+ }
+ return neighboringTaskViewSpringAnimation
+ }
+
+ private fun createExpressiveDismissSpringForce(dampingRatioOffset: Float = 0f): SpringForce {
+ val resourceProvider = DynamicResource.provider(recentsView.mContainer)
+ return SpringForce()
+ .setDampingRatio(
+ resourceProvider.getFloat(R.dimen.expressive_dismiss_task_trans_y_damping_ratio) +
+ dampingRatioOffset
+ )
+ .setStiffness(
+ resourceProvider.getFloat(R.dimen.expressive_dismiss_task_trans_y_stiffness)
+ )
}
companion object {
val TEMP_RECT = Rect()
+
+ // The additional damping to apply to tasks further from the dismissed task.
+ const val ADDITIONAL_DISMISS_DAMPING_RATIO = 0.15f
}
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
index b6f6bed..bbe1af4 100644
--- a/quickstep/src/com/android/quickstep/views/TaskContainer.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
@@ -25,16 +25,14 @@
import com.android.quickstep.TaskOverlayFactory
import com.android.quickstep.ViewUtils.addAccessibleChildToList
import com.android.quickstep.recents.di.RecentsDependencies
-import com.android.quickstep.recents.di.get
import com.android.quickstep.recents.di.getScope
import com.android.quickstep.recents.di.inject
import com.android.quickstep.recents.ui.mapper.TaskUiStateMapper
import com.android.quickstep.recents.ui.viewmodel.TaskData
-import com.android.quickstep.recents.viewmodel.TaskContainerViewModel
import com.android.quickstep.task.thumbnail.TaskThumbnailView
-import com.android.quickstep.task.viewmodel.TaskContainerData
import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
import com.android.systemui.shared.recents.model.Task
+import com.android.systemui.shared.recents.model.ThumbnailData
/** Holder for all Task dependent information. */
class TaskContainer(
@@ -56,20 +54,14 @@
taskOverlayFactory: TaskOverlayFactory,
) {
val overlay: TaskOverlayFactory.TaskOverlay<*> = taskOverlayFactory.createOverlay(this)
- lateinit var taskContainerData: TaskContainerData
+ // TODO(b/390581380): Remove this after this bug is fixed
private val taskThumbnailViewModel: TaskThumbnailViewModel by
RecentsDependencies.inject(snapshotView)
- // TODO(b/335649589): Ideally create and obtain this from DI.
- private val taskContainerViewModel: TaskContainerViewModel by lazy {
- TaskContainerViewModel(splashAlphaUseCase = RecentsDependencies.get())
- }
-
init {
if (enableRefactorTaskThumbnail()) {
require(snapshotView is TaskThumbnailView)
- taskContainerData = RecentsDependencies.get(this)
RecentsDependencies.getScope(snapshotView).apply {
val taskViewScope = RecentsDependencies.getScope(taskView)
linkTo(taskViewScope)
@@ -82,9 +74,11 @@
}
}
- var splitAnimationThumbnail: Bitmap? = null
- get() = if (enableRefactorTaskThumbnail()) field else thumbnailViewDeprecated.thumbnail
- private set
+ internal var thumbnailData: ThumbnailData? = null
+ val splitAnimationThumbnail: Bitmap?
+ get() =
+ if (enableRefactorTaskThumbnail()) thumbnailData?.thumbnail
+ else thumbnailViewDeprecated.thumbnail
val thumbnailView: TaskThumbnailView
get() {
@@ -98,10 +92,12 @@
return snapshotView as TaskThumbnailViewDeprecated
}
+ var isThumbnailValid: Boolean = false
+ internal set
+
val shouldShowSplashView: Boolean
get() =
- if (enableRefactorTaskThumbnail())
- taskContainerViewModel.shouldShowThumbnailSplash(task.key.id)
+ if (enableRefactorTaskThumbnail()) taskView.shouldShowSplash()
else thumbnailViewDeprecated.shouldShowSplashView()
/** Builds proto for logging */
@@ -111,7 +107,7 @@
fun bind() {
digitalWellBeingToast?.bind(task, taskView, snapshotView, stagePosition)
if (enableRefactorTaskThumbnail()) {
- bindThumbnailView()
+ taskThumbnailViewModel.bind(task.key.id)
} else {
thumbnailViewDeprecated.bind(task, overlay, taskView)
}
@@ -126,6 +122,9 @@
if (enableRefactorTaskThumbnail()) {
RecentsDependencies.getInstance().removeScope(snapshotView)
RecentsDependencies.getInstance().removeScope(this)
+ isThumbnailValid = false
+ } else {
+ thumbnailViewDeprecated.setShowSplashForSplitSelection(false)
}
}
@@ -134,10 +133,6 @@
thumbnailView.destroyScopes()
}
- private fun bindThumbnailView() {
- taskThumbnailViewModel.bind(task.key.id)
- }
-
fun setOverlayEnabled(enabled: Boolean) {
if (!enableRefactorTaskThumbnail()) {
thumbnailViewDeprecated.setOverlayEnabled(enabled)
@@ -157,15 +152,41 @@
TaskUiStateMapper.toTaskThumbnailUiState(state, liveTile, hasHeader),
state?.taskId,
)
- splitAnimationThumbnail =
- if (state is TaskData.Data) state.thumbnailData?.thumbnail else null
+ thumbnailData = if (state is TaskData.Data) state.thumbnailData else null
}
fun updateTintAmount(tintAmount: Float) {
thumbnailView.updateTintAmount(tintAmount)
}
+ /**
+ * Updates the progress of the menu opening animation.
+ *
+ * This function propagates the given `progress` value to the `thumbnailView` allowing the
+ * thumbnail view to animate its visual state in sync with the menu's opening/closing
+ * transition.
+ *
+ * @param progress The progress of the menu opening animation (from closed=0 to fully open=1)
+ */
fun updateMenuOpenProgress(progress: Float) {
thumbnailView.updateMenuOpenProgress(progress)
}
+
+ /**
+ * Updates the thumbnail splash progress for a given task.
+ *
+ * This function manages the visual feedback of a "splash" effect that can be displayed over a
+ * thumbnail image, typically during loading or updating. It calculates the alpha (transparency)
+ * of the splash based on the provided progress and then applies this alpha to the thumbnail
+ * view if it should be displayed.
+ *
+ * @param progress The progress of the operation, ranging from 0.0 to 1.0
+ */
+ fun updateThumbnailSplashProgress(progress: Float) {
+ if (enableRefactorTaskThumbnail()) {
+ thumbnailView.updateSplashAlpha(progress)
+ } else {
+ thumbnailViewDeprecated.setSplashAlpha(progress)
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 0465dbc..56a35bd 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -41,6 +41,7 @@
import android.widget.Toast
import androidx.annotation.IntDef
import androidx.annotation.VisibleForTesting
+import androidx.core.view.doOnLayout
import androidx.core.view.updateLayoutParams
import com.android.app.animation.Interpolators
import com.android.launcher3.Flags.enableCursorHoverStates
@@ -80,6 +81,7 @@
import com.android.quickstep.recents.di.RecentsDependencies
import com.android.quickstep.recents.di.get
import com.android.quickstep.recents.di.inject
+import com.android.quickstep.recents.ui.viewmodel.TaskData
import com.android.quickstep.recents.ui.viewmodel.TaskTileUiState
import com.android.quickstep.recents.ui.viewmodel.TaskViewModel
import com.android.quickstep.util.ActiveGestureErrorDetector
@@ -330,6 +332,12 @@
onModalnessUpdated(field)
}
+ var splitSplashAlpha = 0f
+ set(value) {
+ field = value
+ applyThumbnailSplashAlpha()
+ }
+
protected var taskThumbnailSplashAlpha = 0f
set(value) {
field = value
@@ -647,6 +655,8 @@
viewModel = null
attachAlpha = 1f
splitAlpha = 1f
+ splitSplashAlpha = 0f
+ taskThumbnailSplashAlpha = 0f
// Clear any references to the thumbnail (it will be re-read either from the cache or the
// system on next bind)
if (!enableRefactorTaskThumbnail()) {
@@ -764,14 +774,30 @@
// Updating containers
val mapOfTasks = state.tasks.associateBy { it.taskId }
taskContainers.forEach { container ->
+ val containerState = mapOfTasks[container.task.key.id]
container.setState(
- state = mapOfTasks[container.task.key.id],
+ state = containerState,
liveTile = state.isLiveTile,
hasHeader = type == TaskViewType.DESKTOP,
)
+ updateThumbnailValidity(container)
+
+ if (enableOverviewIconMenu()) {
+ setIconState(container, containerState)
+ }
}
}
+ private fun updateThumbnailValidity(container: TaskContainer) {
+ container.isThumbnailValid =
+ viewModel!!.isThumbnailValid(
+ thumbnail = container.thumbnailData,
+ width = container.thumbnailView.width,
+ height = container.thumbnailView.height,
+ )
+ applyThumbnailSplashAlpha()
+ }
+
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
if (enableRefactorTaskThumbnail()) {
@@ -808,7 +834,7 @@
onBind(orientedState)
}
- open fun onBind(orientedState: RecentsOrientedState) {
+ protected open fun onBind(orientedState: RecentsOrientedState) {
if (enableRefactorTaskThumbnail()) {
viewModel =
TaskViewModel(
@@ -816,20 +842,37 @@
recentsViewData = RecentsDependencies.get(),
getTaskUseCase = RecentsDependencies.get(),
getSysUiStatusNavFlagsUseCase = RecentsDependencies.get(),
+ isThumbnailValidUseCase = RecentsDependencies.get(),
dispatcherProvider = RecentsDependencies.get(),
)
.apply { bind(*taskIds) }
}
- taskContainers.forEach {
- it.bind()
+ taskContainers.forEach { container ->
+ container.bind()
if (enableRefactorTaskThumbnail()) {
- it.thumbnailView.cornerRadius = thumbnailFullscreenParams.currentCornerRadius
+ container.thumbnailView.cornerRadius = thumbnailFullscreenParams.currentCornerRadius
+ container.thumbnailView.doOnLayout { updateThumbnailValidity(container) }
}
}
setOrientationState(orientedState)
}
+ private fun applyThumbnailSplashAlpha() {
+ val alpha = getSplashAlphaProgress()
+ taskContainers.forEach { it.updateThumbnailSplashProgress(alpha) }
+ }
+
+ private fun getSplashAlphaProgress(): Float =
+ when {
+ !enableRefactorTaskThumbnail() -> taskThumbnailSplashAlpha
+ splitSplashAlpha > 0f -> splitSplashAlpha
+ shouldShowSplash() -> taskThumbnailSplashAlpha
+ else -> 0f
+ }
+
+ internal fun shouldShowSplash(): Boolean = taskContainers.any { !it.isThumbnailValid }
+
protected fun createTaskContainer(
task: Task,
@IdRes thumbnailViewId: Int,
@@ -991,7 +1034,7 @@
}
}
}
- if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
+ if (needsUpdate(changes, FLAG_UPDATE_ICON) && !enableOverviewIconMenu()) {
taskContainers.forEach {
if (visible) {
recentsModel.iconCache
@@ -1022,10 +1065,23 @@
pendingIconLoadRequests.clear()
}
+ protected open fun setIconState(container: TaskContainer, state: TaskData?) {
+ if (enableOverviewIconMenu()) {
+ if (state is TaskData.Data) {
+ setIcon(container.iconView, state.icon)
+ container.iconView.setText(state.title)
+ container.digitalWellBeingToast?.initialize()
+ } else {
+ setIcon(container.iconView, null)
+ container.iconView.setText(null)
+ }
+ }
+ }
+
protected open fun onIconLoaded(taskContainer: TaskContainer) {
setIcon(taskContainer.iconView, taskContainer.task.icon)
if (enableOverviewIconMenu()) {
- setText(taskContainer.iconView, taskContainer.task.title)
+ taskContainer.iconView.setText(taskContainer.task.title)
}
taskContainer.digitalWellBeingToast?.initialize()
}
@@ -1033,7 +1089,7 @@
protected open fun onIconUnloaded(taskContainer: TaskContainer) {
setIcon(taskContainer.iconView, null)
if (enableOverviewIconMenu()) {
- setText(taskContainer.iconView, null)
+ taskContainer.iconView.setText(null)
}
}
@@ -1058,10 +1114,6 @@
}
}
- protected fun setText(iconView: TaskViewIcon, text: CharSequence?) {
- iconView.setText(text)
- }
-
@JvmOverloads
open fun setShouldShowScreenshot(
shouldShowScreenshot: Boolean,
@@ -1168,6 +1220,7 @@
recentsView.stateManager,
recentsView,
recentsView.depthController,
+ /* transitionInfo= */ null,
)
addListener(
object : AnimatorListenerAdapter() {
@@ -1295,6 +1348,7 @@
if (isQuickSwitch) {
setFreezeRecentTasksReordering()
}
+ // TODO(b/331754864): Update this to use TV.shouldShowSplash
disableStartingWindow = firstTaskContainer.shouldShowSplashView
}
Executors.UI_HELPER_EXECUTOR.execute {
@@ -1585,14 +1639,6 @@
updateFullscreenParams()
}
- protected open fun applyThumbnailSplashAlpha() {
- if (!enableRefactorTaskThumbnail()) {
- taskContainers.forEach {
- it.thumbnailViewDeprecated.setSplashAlpha(taskThumbnailSplashAlpha)
- }
- }
- }
-
private fun applyTranslationX() {
translationX =
dismissTranslationX +
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/FakeTaskThumbnailViewModel.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/FakeTaskThumbnailViewModel.kt
index 3e0c186..1a2b1c3 100644
--- a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/FakeTaskThumbnailViewModel.kt
+++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/FakeTaskThumbnailViewModel.kt
@@ -18,11 +18,8 @@
import android.graphics.Matrix
import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
-import kotlinx.coroutines.flow.MutableStateFlow
class FakeTaskThumbnailViewModel : TaskThumbnailViewModel {
- override val splashAlpha = MutableStateFlow(0f)
-
override fun bind(taskId: Int) {
// no-op
}
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
index 356080a..c3b4d15 100644
--- a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
+++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
@@ -24,6 +24,7 @@
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
import com.google.android.apps.nexuslauncher.imagecomparison.goldenpathmanager.ViewScreenshotGoldenPathManager
+import org.junit.After
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -47,6 +48,11 @@
private val taskThumbnailViewModel = FakeTaskThumbnailViewModel()
+ @After
+ fun tearDown() {
+ RecentsDependencies.destroy()
+ }
+
@Test
fun taskThumbnailView_uninitializedByDefault() {
screenshotRule.screenshotTest("taskThumbnailView_uninitialized") { activity ->
@@ -125,7 +131,6 @@
as TaskThumbnailView
taskThumbnailView.cornerRadius = CORNER_RADIUS
val ttvDiScopeId = di.getScope(taskThumbnailView).scopeId
- di.provide(TaskThumbnailViewData::class.java, ttvDiScopeId) { TaskThumbnailViewData() }
di.provide(TaskThumbnailViewModel::class.java, ttvDiScopeId) { taskThumbnailViewModel }
return taskThumbnailView
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 de0da64..adfbca5 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
@@ -47,6 +47,7 @@
import com.google.common.truth.Truth.assertThat
import dagger.BindsInstance
import dagger.Component
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -78,6 +79,11 @@
RecentsDependencies.initialize(context)
}
+ @After
+ fun tearDown() {
+ RecentsDependencies.destroy()
+ }
+
@Test
fun singleTask() {
val taskContainers = listOf(createTaskContainer(createTask(1)))
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendControllerTest.kt
index 785e585..50d6aff 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendControllerTest.kt
@@ -22,6 +22,7 @@
import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING
import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_IN_LAUNCHER
import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_TOUCHING
+import com.android.launcher3.taskbar.rules.SandboxParams
import com.android.launcher3.taskbar.rules.TaskbarModeRule
import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.TRANSIENT
import com.android.launcher3.taskbar.rules.TaskbarModeRule.TaskbarMode
@@ -46,15 +47,15 @@
@get:Rule(order = 0)
val context =
- TaskbarWindowSandboxContext.create { builder ->
- builder.bindSystemUiProxy(
+ TaskbarWindowSandboxContext.create(
+ SandboxParams({
spy(SystemUiProxy(ApplicationProvider.getApplicationContext())) { proxy ->
doAnswer { latestSuspendNotification = it.getArgument(0) }
.whenever(proxy)
.notifyTaskbarAutohideSuspend(anyOrNull())
}
- )
- }
+ })
+ )
@get:Rule(order = 1) val animatorTestRule = AnimatorTestRule(this)
@get:Rule(order = 2) val taskbarModeRule = TaskbarModeRule(context)
@get:Rule(order = 3) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
index 9ca8a1b..bfd53ef 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
@@ -26,21 +26,29 @@
import com.android.launcher3.Flags.FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR
import com.android.launcher3.Flags.FLAG_TASKBAR_OVERFLOW
import com.android.launcher3.R
+import com.android.launcher3.dagger.LauncherAppSingleton
import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync
import com.android.launcher3.taskbar.TaskbarViewTestUtil.createHotseatItems
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController
+import com.android.launcher3.taskbar.rules.DisplayControllerModule
+import com.android.launcher3.taskbar.rules.MockedRecentsModelHelper
import com.android.launcher3.taskbar.rules.MockedRecentsModelTestRule
+import com.android.launcher3.taskbar.rules.SandboxParams
import com.android.launcher3.taskbar.rules.TaskbarModeRule
import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.PINNED
import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.TRANSIENT
import com.android.launcher3.taskbar.rules.TaskbarModeRule.TaskbarMode
+import com.android.launcher3.taskbar.rules.TaskbarSandboxComponent
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController
import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext
+import com.android.launcher3.util.AllModulesForTest
+import com.android.launcher3.util.FakePrefsModule
import com.android.launcher3.util.LauncherMultivalentJUnit
import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
import com.android.launcher3.util.TestUtil.getOnUiThread
+import com.android.quickstep.RecentsModel
import com.android.quickstep.SystemUiProxy
import com.android.quickstep.util.DesktopTask
import com.android.systemui.shared.recents.model.Task
@@ -49,6 +57,8 @@
import com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_BAR
import com.android.wm.shell.desktopmode.IDesktopTaskListener
import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -70,19 +80,25 @@
class TaskbarOverflowTest {
@get:Rule(order = 0) val setFlagsRule = SetFlagsRule()
+ val mockRecentsModelHelper: MockedRecentsModelHelper = MockedRecentsModelHelper()
+
@get:Rule(order = 1)
val context =
- TaskbarWindowSandboxContext.create { builder ->
- builder.bindSystemUiProxy(
- spy(SystemUiProxy(ApplicationProvider.getApplicationContext())) { proxy ->
- doAnswer { desktopTaskListener = it.getArgument(0) }
- .whenever(proxy)
- .setDesktopTaskListener(anyOrNull())
- }
+ TaskbarWindowSandboxContext.create(
+ SandboxParams(
+ {
+ spy(SystemUiProxy(ApplicationProvider.getApplicationContext())) { proxy ->
+ doAnswer { desktopTaskListener = it.getArgument(0) }
+ .whenever(proxy)
+ .setDesktopTaskListener(anyOrNull())
+ }
+ },
+ DaggerTaskbarOverflowComponent.builder()
+ .bindRecentsModel(mockRecentsModelHelper.mockRecentsModel),
)
- }
+ )
- @get:Rule(order = 2) val recentsModel = MockedRecentsModelTestRule(context)
+ @get:Rule(order = 2) val recentsModel = MockedRecentsModelTestRule(mockRecentsModelHelper)
@get:Rule(order = 3) val taskbarModeRule = TaskbarModeRule(context)
@@ -404,3 +420,18 @@
return maxNumIconViews
}
}
+
+/** TaskbarOverflowComponent used to bind the RecentsModel. */
+@LauncherAppSingleton
+@Component(
+ modules = [AllModulesForTest::class, FakePrefsModule::class, DisplayControllerModule::class]
+)
+interface TaskbarOverflowComponent : TaskbarSandboxComponent {
+
+ @Component.Builder
+ interface Builder : TaskbarSandboxComponent.Builder {
+ @BindsInstance fun bindRecentsModel(model: RecentsModel): Builder
+
+ override fun build(): TaskbarOverflowComponent
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarScrimViewControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarScrimViewControllerTest.kt
index 360f019..ba53dcd 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarScrimViewControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarScrimViewControllerTest.kt
@@ -25,6 +25,7 @@
import androidx.test.core.app.ApplicationProvider
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController
+import com.android.launcher3.taskbar.rules.SandboxParams
import com.android.launcher3.taskbar.rules.TaskbarModeRule
import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.PINNED
import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.TRANSIENT
@@ -55,13 +56,14 @@
@get:Rule(order = 0) val setFlagsRule = SetFlagsRule()
@get:Rule(order = 1)
val context =
- TaskbarWindowSandboxContext.create { builder ->
- builder.bindSystemUiProxy(
+ TaskbarWindowSandboxContext.create(
+ SandboxParams({
spy(SystemUiProxy(ApplicationProvider.getApplicationContext())) {
doAnswer { backPressed = true }.whenever(it).onBackEvent(anyOrNull())
}
- )
- }
+ })
+ )
+
@get:Rule(order = 2) val taskbarModeRule = TaskbarModeRule(context)
@get:Rule(order = 3) val animatorTestRule = AnimatorTestRule(this)
@get:Rule(order = 4) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelHelper.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelHelper.kt
new file mode 100644
index 0000000..a7bfa9a
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelHelper.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.taskbar.rules
+
+import com.android.quickstep.RecentsModel
+import com.android.quickstep.RecentsModel.RecentTasksChangedListener
+import com.android.quickstep.TaskIconCache
+import com.android.quickstep.util.GroupTask
+import java.util.function.Consumer
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+
+/** Helper class to mock the {@link RecentsModel} object in test */
+class MockedRecentsModelHelper {
+ private val mockIconCache: TaskIconCache = mock()
+ var taskListId = 0
+ var recentTasksChangedListener: RecentTasksChangedListener? = null
+ var taskRequests: MutableList<(List<GroupTask>) -> Unit> = mutableListOf()
+
+ val mockRecentsModel: RecentsModel = mock {
+ on { iconCache } doReturn mockIconCache
+
+ on { unregisterRecentTasksChangedListener() } doAnswer { recentTasksChangedListener = null }
+
+ on { registerRecentTasksChangedListener(any<RecentTasksChangedListener>()) } doAnswer
+ {
+ recentTasksChangedListener = it.getArgument<RecentTasksChangedListener>(0)
+ }
+
+ on { getTasks(anyOrNull(), anyOrNull()) } doAnswer
+ {
+ val request = it.getArgument<Consumer<List<GroupTask>>?>(0)
+ if (request != null) {
+ taskRequests.add { response -> request.accept(response) }
+ }
+ taskListId
+ }
+
+ on { getTasks(anyOrNull()) } doAnswer
+ {
+ val request = it.getArgument<Consumer<List<GroupTask>>?>(0)
+ if (request != null) {
+ taskRequests.add { response -> request.accept(response) }
+ }
+ taskListId
+ }
+
+ on { isTaskListValid(any()) } doAnswer { taskListId == it.getArgument(0) }
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelTestRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelTestRule.kt
index ed1443d..359b876 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelTestRule.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelTestRule.kt
@@ -16,64 +16,17 @@
package com.android.launcher3.taskbar.rules
-import com.android.quickstep.RecentsModel
-import com.android.quickstep.RecentsModel.RecentTasksChangedListener
-import com.android.quickstep.TaskIconCache
import com.android.quickstep.util.GroupTask
-import java.util.function.Consumer
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
-import org.mockito.kotlin.any
-import org.mockito.kotlin.anyOrNull
-import org.mockito.kotlin.doAnswer
-import org.mockito.kotlin.doReturn
-import org.mockito.kotlin.mock
-class MockedRecentsModelTestRule(private val context: TaskbarWindowSandboxContext) : TestRule {
-
- private val mockIconCache: TaskIconCache = mock()
-
- private val mockRecentsModel: RecentsModel = mock {
- on { iconCache } doReturn mockIconCache
-
- on { unregisterRecentTasksChangedListener() } doAnswer { recentTasksChangedListener = null }
-
- on { registerRecentTasksChangedListener(any<RecentTasksChangedListener>()) } doAnswer
- {
- recentTasksChangedListener = it.getArgument<RecentTasksChangedListener>(0)
- }
-
- on { getTasks(anyOrNull(), anyOrNull()) } doAnswer
- {
- val request = it.getArgument<Consumer<List<GroupTask>>?>(0)
- if (request != null) {
- taskRequests.add { response -> request.accept(response) }
- }
- taskListId
- }
-
- on { getTasks(anyOrNull()) } doAnswer
- {
- val request = it.getArgument<Consumer<List<GroupTask>>?>(0)
- if (request != null) {
- taskRequests.add { response -> request.accept(response) }
- }
- taskListId
- }
-
- on { isTaskListValid(any()) } doAnswer { taskListId == it.getArgument(0) }
- }
-
+class MockedRecentsModelTestRule(private val modelHelper: MockedRecentsModelHelper) : TestRule {
private var recentTasks: List<GroupTask> = emptyList()
- private var taskListId = 0
- private var recentTasksChangedListener: RecentTasksChangedListener? = null
- private var taskRequests: MutableList<(List<GroupTask>) -> Unit> = mutableListOf()
override fun apply(base: Statement?, description: Description?): Statement {
return object : Statement() {
override fun evaluate() {
- context.putObject(RecentsModel.INSTANCE, mockRecentsModel)
base?.evaluate()
}
}
@@ -82,15 +35,15 @@
// NOTE: For the update to take effect, `resolvePendingTaskRequests()` needs to be called, so
// calbacks to any pending `RecentsModel.getTasks()` get called with the updated task list.
fun updateRecentTasks(tasks: List<GroupTask>) {
- ++taskListId
+ ++modelHelper.taskListId
recentTasks = tasks
- recentTasksChangedListener?.onRecentTasksChanged()
+ modelHelper.recentTasksChangedListener?.onRecentTasksChanged()
}
fun resolvePendingTaskRequests() {
val requests = mutableListOf<(List<GroupTask>) -> Unit>()
- requests.addAll(taskRequests)
- taskRequests.clear()
+ requests.addAll(modelHelper.taskRequests)
+ modelHelper.taskRequests.clear()
requests.forEach { it(recentTasks) }
}
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 90c9553..0204b2d 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
@@ -33,6 +33,7 @@
import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
import com.android.launcher3.util.TestUtil
import com.android.quickstep.AllAppsActionManager
+import com.android.quickstep.fallback.window.RecentsDisplayModel
import java.lang.reflect.Field
import java.lang.reflect.ParameterizedType
import java.util.Locale
@@ -110,6 +111,7 @@
PendingIntent(IIntentSender.Default())
},
object : TaskbarNavButtonCallbacks {},
+ RecentsDisplayModel.INSTANCE.get(context),
) {
override fun recreateTaskbar() {
super.recreateTaskbar()
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
index e6dc2a2..95e8980 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
@@ -47,10 +47,6 @@
import org.junit.runner.Description
import org.junit.runners.model.Statement
-/** Include additional bindings when building a [TaskbarSandboxComponent]. */
-typealias TaskbarComponentBinder =
- TaskbarWindowSandboxContext.(TaskbarSandboxComponent.Builder) -> Unit
-
/**
* [SandboxApplication] for running Taskbar tests.
*
@@ -61,7 +57,7 @@
private constructor(
private val base: SandboxApplication,
val virtualDisplay: VirtualDisplay,
- private val componentBinder: TaskbarComponentBinder?,
+ private val params: SandboxParams,
) : ContextWrapper(base), ObjectSandbox by base, TestRule {
val settingsCacheSandbox = SettingsCacheSandbox()
@@ -76,10 +72,9 @@
override fun before() {
val context = this@TaskbarWindowSandboxContext
val builder =
- DaggerTaskbarSandboxComponent.builder()
- .bindSystemUiProxy(SystemUiProxy(context))
+ params.builderBase
+ .bindSystemUiProxy(params.systemUiProxyProvider.invoke(context))
.bindSettingsCache(settingsCacheSandbox.cache)
- componentBinder?.invoke(context, builder)
base.initDaggerComponent(builder)
}
}
@@ -95,10 +90,9 @@
private const val VIRTUAL_DISPLAY_NAME = "TaskbarSandboxDisplay"
/** Creates a [SandboxApplication] for Taskbar tests. */
- fun create(componentBinder: TaskbarComponentBinder? = null): TaskbarWindowSandboxContext {
+ fun create(params: SandboxParams = SandboxParams()): TaskbarWindowSandboxContext {
val base = ApplicationProvider.getApplicationContext<Context>()
val displayManager = checkNotNull(base.getSystemService(DisplayManager::class.java))
-
// Create virtual display to avoid clashing with Taskbar on default display.
val virtualDisplay =
base.resources.displayMetrics.let {
@@ -115,7 +109,7 @@
return TaskbarWindowSandboxContext(
SandboxApplication(base.createDisplayContext(virtualDisplay.display)),
virtualDisplay,
- componentBinder,
+ params,
)
}
}
@@ -157,3 +151,9 @@
override fun build(): TaskbarSandboxComponent
}
}
+
+/** Include additional bindings when building a [TaskbarSandboxComponent]. */
+data class SandboxParams(
+ val systemUiProxyProvider: (Context) -> SystemUiProxy = { SystemUiProxy(it) },
+ val builderBase: TaskbarSandboxComponent.Builder = DaggerTaskbarSandboxComponent.builder(),
+)
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/util/SettingsCacheSandbox.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/util/SettingsCacheSandbox.kt
index dcd5352..52238c8 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/util/SettingsCacheSandbox.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/util/SettingsCacheSandbox.kt
@@ -17,19 +17,22 @@
package com.android.launcher3.util
import android.net.Uri
+import com.android.launcher3.util.SettingsCache.OnChangeListener
import org.mockito.kotlin.any
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
-/**
- * Provides a sandboxed [SettingsCache] for testing.
- *
- * Note that listeners registered to [cache] will never be invoked.
- */
+/** Provides [SettingsCache] sandboxed from system settings for testing. */
class SettingsCacheSandbox {
private val values = mutableMapOf<Uri, Int>()
+ private val listeners = mutableMapOf<Uri, MutableSet<OnChangeListener>>()
- /** Fake cache that delegates [SettingsCache.getValue] to [values]. */
+ /**
+ * Fake cache that delegates:
+ * - [SettingsCache.getValue] to [values]
+ * - [SettingsCache.mListenerMap] to [listeners].
+ */
val cache =
mock<SettingsCache> {
on { getValue(any<Uri>()) } doAnswer { mock.getValue(it.getArgument(0), 1) }
@@ -37,11 +40,22 @@
{
values.getOrDefault(it.getArgument(0), it.getArgument(1)) == 1
}
+
+ doAnswer {
+ listeners.getOrPut(it.getArgument(0)) { mutableSetOf() }.add(it.getArgument(1))
+ }
+ .whenever(mock)
+ .register(any(), any())
+ doAnswer { listeners[it.getArgument(0)]?.remove(it.getArgument(1)) }
+ .whenever(mock)
+ .unregister(any(), any())
}
operator fun get(key: Uri): Int? = values[key]
operator fun set(key: Uri, value: Int) {
+ if (value == values[key]) return
values[key] = value
+ listeners[key]?.forEach { it.onSettingsChanged(value == 1) }
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/AllAppsActionManagerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/AllAppsActionManagerTest.kt
index 73b35e8..a1bd107 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/AllAppsActionManagerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/AllAppsActionManagerTest.kt
@@ -18,32 +18,59 @@
import android.app.PendingIntent
import android.content.IIntentSender
+import android.provider.Settings
+import android.provider.Settings.Secure.USER_SETUP_COMPLETE
import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.util.AllModulesForTest
import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
+import com.android.launcher3.util.SandboxApplication
+import com.android.launcher3.util.SettingsCache
+import com.android.launcher3.util.SettingsCacheSandbox
import com.android.launcher3.util.TestUtil
import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
import java.util.concurrent.Semaphore
import java.util.concurrent.TimeUnit.SECONDS
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
private const val TIMEOUT = 5L
+private val USER_SETUP_COMPLETE_URI = Settings.Secure.getUriFor(USER_SETUP_COMPLETE)
@RunWith(AndroidJUnit4::class)
class AllAppsActionManagerTest {
private val callbackSemaphore = Semaphore(0)
private val bgExecutor = UI_HELPER_EXECUTOR
- private val allAppsActionManager =
- AllAppsActionManager(
- InstrumentationRegistry.getInstrumentation().targetContext,
- bgExecutor,
- ) {
- callbackSemaphore.release()
- PendingIntent(IIntentSender.Default())
+ @get:Rule val context = SandboxApplication()
+
+ private val settingsCacheSandbox =
+ SettingsCacheSandbox().also { it[USER_SETUP_COMPLETE_URI] = 1 }
+
+ private val allAppsActionManager by
+ lazy(LazyThreadSafetyMode.NONE) {
+ AllAppsActionManager(context, bgExecutor) {
+ callbackSemaphore.release()
+ PendingIntent(IIntentSender.Default())
+ }
}
+ @Before
+ fun initDaggerComponent() {
+ context.initDaggerComponent(
+ DaggerAllAppsActionManagerTestComponent.builder()
+ .bindSettingsCache(settingsCacheSandbox.cache)
+ )
+ }
+
+ @After fun destroyManager() = allAppsActionManager.onDestroy()
+
@Test
fun taskbarPresent_actionRegistered() {
allAppsActionManager.isTaskbarPresent = true
@@ -88,4 +115,50 @@
assertThat(callbackSemaphore.tryAcquire(TIMEOUT, SECONDS)).isTrue()
assertThat(allAppsActionManager.isActionRegistered).isTrue()
}
+
+ @Test
+ fun taskbarPresent_userSetupIncomplete_actionUnregistered() {
+ settingsCacheSandbox[USER_SETUP_COMPLETE_URI] = 0
+ allAppsActionManager.isTaskbarPresent = true
+ assertThat(allAppsActionManager.isActionRegistered).isFalse()
+ }
+
+ @Test
+ fun taskbarPresent_setupUiVisible_actionUnregistered() {
+ allAppsActionManager.isSetupUiVisible = true
+ allAppsActionManager.isTaskbarPresent = true
+ assertThat(allAppsActionManager.isActionRegistered).isFalse()
+ }
+
+ @Test
+ fun taskbarPresent_userSetupCompleted_actionRegistered() {
+ settingsCacheSandbox[USER_SETUP_COMPLETE_URI] = 0
+ allAppsActionManager.isTaskbarPresent = true
+
+ settingsCacheSandbox[USER_SETUP_COMPLETE_URI] = 1
+ assertThat(callbackSemaphore.tryAcquire(TIMEOUT, SECONDS)).isTrue()
+ assertThat(allAppsActionManager.isActionRegistered).isTrue()
+ }
+
+ @Test
+ fun taskbarPresent_setupUiDismissed_actionRegistered() {
+ allAppsActionManager.isSetupUiVisible = true
+ allAppsActionManager.isTaskbarPresent = true
+
+ allAppsActionManager.isSetupUiVisible = false
+ assertThat(callbackSemaphore.tryAcquire(TIMEOUT, SECONDS)).isTrue()
+ assertThat(allAppsActionManager.isActionRegistered).isTrue()
+ }
+}
+
+@LauncherAppSingleton
+@Component(modules = [AllModulesForTest::class])
+interface AllAppsActionManagerTestComponent : LauncherAppComponent {
+
+ @Component.Builder
+ interface Builder : LauncherAppComponent.Builder {
+ @BindsInstance fun bindSettingsCache(settingsCache: SettingsCache): Builder
+
+ override fun build(): AllAppsActionManagerTestComponent
+ }
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/OverviewCommandHelperTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/OverviewCommandHelperTest.kt
index 0ae710f..56c01f9 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/OverviewCommandHelperTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/OverviewCommandHelperTest.kt
@@ -68,7 +68,10 @@
touchInteractionService = mock(),
overviewComponentObserver = mock(),
taskAnimationManager = mock(),
- dispatcherProvider = TestDispatcherProvider(dispatcher)
+ dispatcherProvider = TestDispatcherProvider(dispatcher),
+ recentsDisplayModel = mock(),
+ focusState = mock(),
+ taskbarManager = mock(),
)
)
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java
index c399bdb..8b17958 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java
@@ -97,6 +97,8 @@
// Set desktop mode supported
when(mContext.getResources()).thenReturn(mResources);
when(mResources.getBoolean(R.bool.config_isDesktopModeSupported)).thenReturn(true);
+ when(mResources.getBoolean(R.bool.config_canInternalDisplayHostDesktops))
+ .thenReturn(true);
mRecentTasksList = new RecentTasksList(mContext, mockMainThreadExecutor,
mockKeyguardManager, mSystemUiProxy, mTopTaskTracker);
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsModelTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsModelTest.java
index 722e1da..2eb2e4c 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.DaggerSingletonTracker;
import com.android.launcher3.util.LockedUserState;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.quickstep.util.GroupTask;
@@ -109,7 +110,7 @@
mRecentsModel = new RecentsModel(mContext, mTasksList, mock(TaskIconCache.class),
mThumbnailCache, mock(IconProvider.class), mock(TaskStackChangeListeners.class),
- mLockedUserState, () -> mThemeManager);
+ mLockedUserState, () -> mThemeManager, mock(DaggerSingletonTracker.class));
mResource = mock(Resources.class);
when(mResource.getInteger((R.integer.recentsThumbnailCacheSize))).thenReturn(3);
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/IsThumbnailValidUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/IsThumbnailValidUseCaseTest.kt
new file mode 100644
index 0000000..e8bca93
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/IsThumbnailValidUseCaseTest.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.quickstep.recents.domain.usecase
+
+import android.graphics.Bitmap
+import android.view.Surface
+import android.view.Surface.ROTATION_90
+import com.android.quickstep.recents.data.FakeRecentsRotationStateRepository
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+class IsThumbnailValidUseCaseTest {
+ private val recentsRotationStateRepository = FakeRecentsRotationStateRepository()
+ private val systemUnderTest = IsThumbnailValidUseCase(recentsRotationStateRepository)
+
+ @Test
+ fun withNullThumbnail_returnsInvalid() = runTest {
+ val isThumbnailValid = systemUnderTest(thumbnailData = null, viewWidth = 0, viewHeight = 0)
+ assertThat(isThumbnailValid).isEqualTo(false)
+ }
+
+ @Test
+ fun sameAspectRatio_sameRotation_returnsValid() = runTest {
+ val isThumbnailValid =
+ systemUnderTest.invoke(
+ thumbnailData = createThumbnailData(),
+ viewWidth = THUMBNAIL_WIDTH * 2,
+ viewHeight = THUMBNAIL_HEIGHT * 2,
+ )
+ assertThat(isThumbnailValid).isEqualTo(true)
+ }
+
+ @Test
+ fun differentAspectRatio_sameRotation_returnsInvalid() = runTest {
+ val isThumbnailValid =
+ systemUnderTest.invoke(
+ thumbnailData = createThumbnailData(),
+ viewWidth = THUMBNAIL_WIDTH,
+ viewHeight = THUMBNAIL_HEIGHT * 2,
+ )
+ assertThat(isThumbnailValid).isEqualTo(false)
+ }
+
+ @Test
+ fun sameAspectRatio_differentRotation_returnsInvalid() = runTest {
+ val isThumbnailValid =
+ systemUnderTest.invoke(
+ thumbnailData = createThumbnailData(rotation = ROTATION_90),
+ viewWidth = THUMBNAIL_WIDTH * 2,
+ viewHeight = THUMBNAIL_HEIGHT * 2,
+ )
+ assertThat(isThumbnailValid).isEqualTo(false)
+ }
+
+ @Test
+ fun differentAspectRatio_differentRotation_returnsInvalid() = runTest {
+ val isThumbnailValid =
+ systemUnderTest.invoke(
+ thumbnailData = createThumbnailData(rotation = ROTATION_90),
+ viewWidth = THUMBNAIL_WIDTH,
+ viewHeight = THUMBNAIL_HEIGHT * 2,
+ )
+ assertThat(isThumbnailValid).isEqualTo(false)
+ }
+
+ private fun createThumbnailData(
+ rotation: Int = Surface.ROTATION_0,
+ width: Int = THUMBNAIL_WIDTH,
+ height: Int = THUMBNAIL_HEIGHT,
+ ): ThumbnailData {
+ val bitmap = mock<Bitmap>()
+ whenever(bitmap.width).thenReturn(width)
+ whenever(bitmap.height).thenReturn(height)
+ return ThumbnailData(thumbnail = bitmap, rotation = rotation)
+ }
+
+ companion object {
+ const val THUMBNAIL_WIDTH = 100
+ const val THUMBNAIL_HEIGHT = 200
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModelTest.kt
index c031150..08e459b 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModelTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModelTest.kt
@@ -25,9 +25,11 @@
import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_NAV
import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_STATUS
import com.android.launcher3.util.TestDispatcherProvider
+import com.android.quickstep.recents.data.FakeRecentsRotationStateRepository
import com.android.quickstep.recents.domain.model.TaskModel
import com.android.quickstep.recents.domain.usecase.GetSysUiStatusNavFlagsUseCase
import com.android.quickstep.recents.domain.usecase.GetTaskUseCase
+import com.android.quickstep.recents.domain.usecase.IsThumbnailValidUseCase
import com.android.quickstep.recents.viewmodel.RecentsViewData
import com.android.quickstep.views.TaskViewType
import com.android.systemui.shared.recents.model.ThumbnailData
@@ -41,6 +43,10 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
@@ -52,6 +58,8 @@
private val recentsViewData = RecentsViewData()
private val getTaskUseCase = mock<GetTaskUseCase>()
+ private val isThumbnailValidUseCase =
+ spy(IsThumbnailValidUseCase(FakeRecentsRotationStateRepository()))
private lateinit var sut: TaskViewModel
@Before
@@ -62,6 +70,7 @@
recentsViewData = recentsViewData,
getTaskUseCase = getTaskUseCase,
getSysUiStatusNavFlagsUseCase = GetSysUiStatusNavFlagsUseCase(),
+ isThumbnailValidUseCase = isThumbnailValidUseCase,
dispatcherProvider = TestDispatcherProvider(unconfinedTestDispatcher),
)
whenever(getTaskUseCase.invoke(TASK_MODEL_1.id)).thenReturn(flow { emit(TASK_MODEL_1) })
@@ -102,6 +111,7 @@
recentsViewData = recentsViewData,
getTaskUseCase = getTaskUseCase,
getSysUiStatusNavFlagsUseCase = GetSysUiStatusNavFlagsUseCase(),
+ isThumbnailValidUseCase = isThumbnailValidUseCase,
dispatcherProvider = TestDispatcherProvider(unconfinedTestDispatcher),
)
sut.bind(TASK_MODEL_1.id)
@@ -225,6 +235,12 @@
assertThat(sut.state.first()).isEqualTo(expectedResult)
}
+ @Test
+ fun shouldShowSplash_calls_useCase() {
+ sut.isThumbnailValid(null, 0, 0)
+ verify(isThumbnailValidUseCase).invoke(anyOrNull(), anyInt(), anyInt())
+ }
+
private fun TaskModel.toUiState() =
TaskData.Data(
taskId = id,
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/SplashAlphaUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/SplashAlphaUseCaseTest.kt
deleted file mode 100644
index 0767fb9..0000000
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/SplashAlphaUseCaseTest.kt
+++ /dev/null
@@ -1,149 +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.quickstep.task.thumbnail
-
-import android.content.ComponentName
-import android.content.Intent
-import android.graphics.Bitmap
-import android.graphics.Color
-import android.graphics.drawable.Drawable
-import android.view.Surface
-import android.view.Surface.ROTATION_90
-import com.android.quickstep.recents.data.FakeRecentsRotationStateRepository
-import com.android.quickstep.recents.data.FakeTasksRepository
-import com.android.quickstep.recents.viewmodel.RecentsViewData
-import com.android.quickstep.task.viewmodel.TaskContainerData
-import com.android.systemui.shared.recents.model.Task
-import com.android.systemui.shared.recents.model.ThumbnailData
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.whenever
-
-class SplashAlphaUseCaseTest {
- private val recentsViewData = RecentsViewData()
- private val taskContainerData = TaskContainerData()
- private val taskThumbnailViewData = TaskThumbnailViewData()
- private val recentTasksRepository = FakeTasksRepository()
- private val recentsRotationStateRepository = FakeRecentsRotationStateRepository()
- private val systemUnderTest =
- SplashAlphaUseCase(
- recentsViewData,
- taskContainerData,
- taskThumbnailViewData,
- recentTasksRepository,
- recentsRotationStateRepository,
- )
-
- @Test
- fun execute_withNullThumbnail_showsSplash() = runTest {
- assertThat(systemUnderTest.execute(0).first()).isEqualTo(SPLASH_HIDDEN)
- }
-
- @Test
- fun execute_withTaskSpecificSplashAlpha_showsSplash() = runTest {
- setupTask(2)
- taskContainerData.thumbnailSplashProgress.value = 0.7f
-
- assertThat(systemUnderTest.execute(2).first()).isEqualTo(0.7f)
- }
-
- @Test
- fun execute_withNoGlobalSplashEnabled_doesntShowSplash() = runTest {
- setupTask(2)
-
- assertThat(systemUnderTest.execute(2).first()).isEqualTo(SPLASH_HIDDEN)
- }
-
- @Test
- fun execute_withSameAspectRatioAndRotation_withGlobalSplashEnabled_doesntShowSplash() =
- runTest {
- setupTask(2)
- recentsViewData.thumbnailSplashProgress.value = 0.5f
- taskThumbnailViewData.width.value = THUMBNAIL_WIDTH * 2
- taskThumbnailViewData.height.value = THUMBNAIL_HEIGHT * 2
-
- assertThat(systemUnderTest.execute(2).first()).isEqualTo(SPLASH_HIDDEN)
- }
-
- @Test
- fun execute_withDifferentAspectRatioAndSameRotation_showsSplash() = runTest {
- setupTask(2)
- recentsViewData.thumbnailSplashProgress.value = 0.5f
- taskThumbnailViewData.width.value = THUMBNAIL_WIDTH
- taskThumbnailViewData.height.value = THUMBNAIL_HEIGHT * 2
-
- assertThat(systemUnderTest.execute(2).first()).isEqualTo(0.5f)
- }
-
- @Test
- fun execute_withSameAspectRatioAndDifferentRotation_showsSplash() = runTest {
- setupTask(2, createThumbnailData(rotation = ROTATION_90))
- recentsViewData.thumbnailSplashProgress.value = 0.5f
- taskThumbnailViewData.width.value = THUMBNAIL_WIDTH * 2
- taskThumbnailViewData.height.value = THUMBNAIL_HEIGHT * 2
-
- assertThat(systemUnderTest.execute(2).first()).isEqualTo(0.5f)
- }
-
- @Test
- fun execute_withDifferentAspectRatioAndRotation_showsSplash() = runTest {
- setupTask(2, createThumbnailData(rotation = ROTATION_90))
- recentsViewData.thumbnailSplashProgress.value = 0.5f
- taskThumbnailViewData.width.value = THUMBNAIL_WIDTH
- taskThumbnailViewData.height.value = THUMBNAIL_HEIGHT * 2
-
- assertThat(systemUnderTest.execute(2).first()).isEqualTo(0.5f)
- }
-
- private val tasks = (0..5).map(::createTaskWithId)
-
- private fun setupTask(taskId: Int, thumbnailData: ThumbnailData = createThumbnailData()) {
- recentTasksRepository.seedThumbnailData(mapOf(taskId to thumbnailData))
- val expectedIconData = mock<Drawable>()
- recentTasksRepository.seedIconData(taskId, "Task $taskId", "", expectedIconData)
- recentTasksRepository.seedTasks(tasks)
- recentTasksRepository.setVisibleTasks(setOf(taskId))
- }
-
- private fun createThumbnailData(
- rotation: Int = Surface.ROTATION_0,
- width: Int = THUMBNAIL_WIDTH,
- height: Int = THUMBNAIL_HEIGHT,
- ): ThumbnailData {
- val bitmap = mock<Bitmap>()
- whenever(bitmap.width).thenReturn(width)
- whenever(bitmap.height).thenReturn(height)
-
- return ThumbnailData(thumbnail = bitmap, rotation = rotation)
- }
-
- private fun createTaskWithId(taskId: Int) =
- Task(Task.TaskKey(taskId, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
- colorBackground = Color.argb(taskId, taskId, taskId, taskId)
- }
-
- companion object {
- const val THUMBNAIL_WIDTH = 100
- const val THUMBNAIL_HEIGHT = 200
-
- const val SPLASH_HIDDEN = 0f
- const val SPLASH_SHOWN = 1f
- }
-}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt
index aec586d..4b4e2eb 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt
@@ -19,7 +19,6 @@
import android.graphics.Matrix
import android.platform.test.flag.junit.SetFlagsRule
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.launcher3.util.TestDispatcherProvider
import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
import com.android.quickstep.recents.usecase.ThumbnailPositionState.MatrixScaling
import com.android.quickstep.recents.usecase.ThumbnailPositionState.MissingThumbnail
@@ -42,17 +41,9 @@
private val dispatcher = StandardTestDispatcher()
private val testScope = TestScope(dispatcher)
- private val dispatcherProvider = TestDispatcherProvider(dispatcher)
private val mGetThumbnailPositionUseCase = mock<GetThumbnailPositionUseCase>()
- private val splashAlphaUseCase: SplashAlphaUseCase = mock()
- private val systemUnderTest by lazy {
- TaskThumbnailViewModelImpl(
- dispatcherProvider,
- mGetThumbnailPositionUseCase,
- splashAlphaUseCase,
- )
- }
+ private val systemUnderTest by lazy { TaskThumbnailViewModelImpl(mGetThumbnailPositionUseCase) }
@Test
fun getSnapshotMatrix_MissingThumbnail() =
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.kt
index 7066d21..f2fa0c5 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.kt
@@ -447,6 +447,37 @@
}
/*
+ 5 3 [1]
+ CLEAR_ALL
+ 6 4 2
+ */
+ @Test
+ fun equalLengthRows_noFocused_onTop_pressTabWithShift_noCycle_staysOnTop() {
+ assertThat(
+ getNextGridPage(currentPageTaskViewId = 1, DIRECTION_TAB, delta = -1, cycle = false)
+ )
+ .isEqualTo(1)
+ }
+
+ /*
+ 5 3 1
+ [CLEAR_ALL]
+ 6 4 2
+ */
+ @Test
+ fun equalLengthRows_noFocused_onClearAll_pressTab_noCycle_staysOnClearAll() {
+ assertThat(
+ getNextGridPage(
+ currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID,
+ DIRECTION_TAB,
+ delta = 1,
+ cycle = false,
+ )
+ )
+ .isEqualTo(CLEAR_ALL_PLACEHOLDER_ID)
+ }
+
+ /*
5 3 1
CLEAR_ALL FOCUSED_TASK←--DESKTOP
6 4 2
@@ -783,10 +814,11 @@
bottomIds: IntArray = IntArray.wrap(2, 4, 6),
largeTileIds: List<Int> = emptyList(),
hasAddDesktopButton: Boolean = false,
+ cycle: Boolean = true,
): Int {
val taskGridNavHelper =
TaskGridNavHelper(topIds, bottomIds, largeTileIds, hasAddDesktopButton)
- return taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, true)
+ return taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle)
}
private companion object {
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index e0560e2..79d3c19 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -539,6 +539,24 @@
}
}
+ @Test
+ @PortraitLandscape
+ public void testDismissCancel() throws Exception {
+ startTestAppsWithCheck();
+ Overview overview = mLauncher.goHome().switchToOverview();
+ assertIsInState("Launcher internal state didn't switch to Overview",
+ ExpectedState.OVERVIEW);
+ final Integer numTasks = getFromRecentsView(RecentsView::getTaskViewCount);
+ OverviewTask task = overview.getCurrentTask();
+ assertNotNull("overview.getCurrentTask() returned null (2)", task);
+
+ task.dismissCancel();
+
+ runOnRecentsView(recentsView -> assertEquals(
+ "Canceling dismissing a task removed a task from Overview",
+ numTasks == null ? 0 : numTasks, recentsView.getTaskViewCount()));
+ }
+
private void startTestAppsWithCheck() throws Exception {
startTestApps();
expectLaunchedAppState();
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 8b7033f..ff8a541 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -143,7 +143,7 @@
<string name="title_change_settings" msgid="1376365968844349552">"Promijeni postavke"</string>
<string name="notification_dots_service_title" msgid="4284221181793592871">"Prikaži tačke za obavještenja"</string>
<string name="developer_options_title" msgid="700788437593726194">"Opcije za programere"</string>
- <string name="auto_add_shortcuts_label" msgid="4926805029653694105">"Dodaj ikone aplikacija na početni ekran"</string>
+ <string name="auto_add_shortcuts_label" msgid="4926805029653694105">"Dodavanje ikona aplikacija na početni ekran"</string>
<string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Za nove aplikacije"</string>
<string name="package_state_unknown" msgid="7592128424511031410">"Nepoznato"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Ukloni"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index a1e822a..0c10f0d 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -129,7 +129,7 @@
<string name="app_pair_name_format" msgid="8134106404716224054">"Ζεύγος εφαρμογών: <xliff:g id="APP1">%1$s</xliff:g> και <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Ταπετσαρία και στιλ"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Επεξεργασία αρχικής οθόνης"</string>
- <string name="settings_button_text" msgid="8873672322605444408">"Ρυθμίσεις Αρχ. Οθ."</string>
+ <string name="settings_button_text" msgid="8873672322605444408">"Ρυθμ. Αρχικής οθόνης"</string>
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"Απενεργοποιήθηκε από τον διαχειριστή σας"</string>
<string name="allow_rotation_title" msgid="7222049633713050106">"Να επιτρέπεται η περιστροφή της αρχικής οθόνης"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Όταν το τηλέφωνο περιστρέφεται"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index db2b601..8696ff8 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -50,7 +50,7 @@
<string name="add_to_home_screen" msgid="9168649446635919791">"होम स्क्रीन पर जोड़ें"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> विजेट को होम स्क्रीन पर जोड़ा गया"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"सुझाव"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"ज़रूरी ऐप्लिकेशन"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"अहम जानकारी"</string>
<string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"खबरों और पत्रिकाओं वाले ऐप्लिकेशन"</string>
<string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"मनोरंजन से जुड़े ऐप्लिकेशन"</string>
<string name="social_widget_recommendation_category_label" msgid="689147679536384717">"सोशल मीडिया ऐप्लिकेशन"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 9f4ac5a..1a1f405 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -54,7 +54,7 @@
<string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Újságok és magazinok"</string>
<string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Szórakozás"</string>
<string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Közösségi"</string>
- <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Neked javasolt"</string>
+ <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Javaslatok"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"A <xliff:g id="SELECTED_HEADER">%1$s</xliff:g>-modulok a jobb, a kereső és a beállítások pedig a bal oldalon találhatók"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# modul}other{# modul}}"</string>
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# gyorsparancs}other{# gyorsparancs}}"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index ec6da15..34bfbad 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -93,7 +93,7 @@
<string name="all_apps_button_personal_label" msgid="1315764287305224468">"רשימת אפליקציות אישיות"</string>
<string name="all_apps_button_work_label" msgid="7270707118948892488">"רשימת אפליקציות עבודה"</string>
<string name="remove_drop_target_label" msgid="7812859488053230776">"הסרה"</string>
- <string name="uninstall_drop_target_label" msgid="4722034217958379417">"להסרת התקנה"</string>
+ <string name="uninstall_drop_target_label" msgid="4722034217958379417">"הסרת ההתקנה"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"פרטי האפליקציה"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"התקנה במרחב הפרטי"</string>
<string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"הסרת האפליקציה"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index b23b97b..bd792ac 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -129,7 +129,7 @@
<string name="app_pair_name_format" msgid="8134106404716224054">"Programų pora: „<xliff:g id="APP1">%1$s</xliff:g>“ ir „<xliff:g id="APP2">%2$s</xliff:g>“"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Ekrano fonas ir stilius"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Redaguoti pagrindinį ekraną"</string>
- <string name="settings_button_text" msgid="8873672322605444408">"„Home“ nustatymai"</string>
+ <string name="settings_button_text" msgid="8873672322605444408">"Pagrindinio ekrano nustatymai"</string>
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"Išjungė administratorius"</string>
<string name="allow_rotation_title" msgid="7222049633713050106">"Leisti pasukti pagrindinį ekraną"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Kai telefonas pasukamas"</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index e3bde3a..de509df 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -136,7 +136,7 @@
<string name="landscape_mode_title" msgid="5138814555934843926">"ल्यान्डस्केप मोड"</string>
<string name="landscape_mode_desc" msgid="7372569859592816793">"फोनमा ल्यान्डस्केप मोड अन गर्नुहोस्"</string>
<string name="notification_dots_title" msgid="9062440428204120317">"नोटिफिकेसन डट"</string>
- <string name="notification_dots_desc_on" msgid="1679848116452218908">"सक्रिय"</string>
+ <string name="notification_dots_desc_on" msgid="1679848116452218908">"अन छ"</string>
<string name="notification_dots_desc_off" msgid="1760796511504341095">"निष्क्रिय"</string>
<string name="title_missing_notification_access" msgid="7503287056163941064">"सूचनासम्बन्धी पहुँच आवश्यक हुन्छ"</string>
<string name="msg_missing_notification_access" msgid="281113995110910548">"नोटिफिकेसन डट देखाउन <xliff:g id="NAME">%1$s</xliff:g> को एपसम्बन्धी सूचनाहरूलाई अन गर्नुहोस्"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index d771c6f..92b0c6d 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -129,7 +129,7 @@
<string name="app_pair_name_format" msgid="8134106404716224054">"App-paar: <xliff:g id="APP1">%1$s</xliff:g> en <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Achtergrond en stijl"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Startscherm bewerken"</string>
- <string name="settings_button_text" msgid="8873672322605444408">"Instellingen start"</string>
+ <string name="settings_button_text" msgid="8873672322605444408">"Instellingen Start"</string>
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"Uitgezet door je beheerder"</string>
<string name="allow_rotation_title" msgid="7222049633713050106">"Draaien van startscherm toestaan"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Als de telefoon gedraaid is"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index ac5f3be..c15e2e9 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -50,7 +50,7 @@
<string name="add_to_home_screen" msgid="9168649446635919791">"ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਸ਼ਾਮਲ ਕਰੋ"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> ਵਿਜੇਟ ਨੂੰ ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"ਸੁਝਾਅ"</string>
- <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"ਲੋੜੀਂਦੀਆਂ"</string>
+ <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"ਲੋੜੀਂਦੀਆਂ ਐਪਾਂ ਦੇ ਵਿਜੇਟ"</string>
<string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"ਖਬਰਾਂ ਅਤੇ ਰਸਾਲੇ"</string>
<string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"ਮਨੋਰੰਜਨ"</string>
<string name="social_widget_recommendation_category_label" msgid="689147679536384717">"ਸੋਸ਼ਲ"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index b2dd767..f019544 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -49,7 +49,7 @@
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"Нажмите на виджет и удерживайте его, чтобы переместить в нужное место на главном экране."</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Добавить на главный экран"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Виджет \"<xliff:g id="WIDGET_NAME">%1$s</xliff:g>\" добавлен на главный экран"</string>
- <string name="suggested_widgets_header_title" msgid="1844314680798145222">"Рекомендованные"</string>
+ <string name="suggested_widgets_header_title" msgid="1844314680798145222">"Рекомендации"</string>
<string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Основное"</string>
<string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Новости и журналы"</string>
<string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Развлечения"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index b2d2a1b..cecfedb 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -46,7 +46,7 @@
<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_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_item_request_drag_hint" msgid="8730547755622776606">"轻触并按住此微件即可将其拖拽到主屏幕上任意位置"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"添加到主屏幕"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"已将“<xliff:g id="WIDGET_NAME">%1$s</xliff:g>”微件添加到主屏幕"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"建议"</string>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index f740489..a22f943 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -268,8 +268,13 @@
<attr name="allAppsCellSpecsTwoPanelId" format="reference" />
<!-- defaults to false, if not specified -->
<attr name="isFixedLandscape" format="boolean" />
- <!-- defaults to false, if not specified -->
- <attr name="isOldGrid" format="boolean" />
+ <!-- By default all grid types are enabled -->
+ <attr name="gridType" format="integer">
+ <!-- Enable on phone only -->
+ <flag name="one_grid" value="1" />
+ <!-- Enable on tablets only -->
+ <flag name="non_one_grid" value="2" />
+ </attr>
<!-- By default all categories are enabled -->
<attr name="deviceCategory" format="integer">
diff --git a/res/values/config.xml b/res/values/config.xml
index a545f0c..07f97bc 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -117,6 +117,10 @@
<item name="swipe_up_rect_y_damping_ratio" type="dimen" format="float">0.95</item>
<item name="swipe_up_rect_y_stiffness" type="dimen" format="float">400</item>
+ <!-- Expressive Dismiss -->
+ <item name="expressive_dismiss_task_trans_y_damping_ratio" type="dimen" format="float">0.6</item>
+ <item name="expressive_dismiss_task_trans_y_stiffness" type="dimen" format="float">900</item>
+
<!-- Taskbar -->
<!-- This is a float because it is converted to dp later in DeviceProfile -->
<item name="taskbar_icon_size" type="dimen" format="float">0</item>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index c48f140..c3cb31d 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -480,6 +480,7 @@
<dimen name="task_thumbnail_icon_drawable_size_grid">0dp</dimen>
<dimen name="task_thumbnail_icon_menu_drawable_touch_size">0dp</dimen>
<dimen name="task_menu_edge_padding">0dp</dimen>
+ <dimen name="task_dismiss_max_undershoot">0dp</dimen>
<dimen name="overview_task_margin">0dp</dimen>
<dimen name="overview_actions_height">0dp</dimen>
<dimen name="overview_actions_button_spacing">0dp</dimen>
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index d3684b2..fb847f9 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -77,7 +77,6 @@
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.graphics.PreloadIconDrawable;
-import com.android.launcher3.graphics.ThemeManager;
import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
@@ -484,9 +483,7 @@
}
private void setNonPendingIcon(ItemInfoWithIcon info) {
- ThemeManager themeManager = ThemeManager.INSTANCE.get(getContext());
- int flags = (shouldUseTheme()
- && themeManager.isMonoThemeEnabled()) ? FLAG_THEMED : 0;
+ int flags = shouldUseTheme() ? FLAG_THEMED : 0;
// Remove badge on icons smaller than 48dp.
if (mHideBadge || mDisplay == DISPLAY_SEARCH_RESULT_SMALL) {
flags |= FLAG_NO_BADGE;
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 813d8f1..3b283c3 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -387,7 +387,8 @@
}
/** TODO: Once we fully migrate to staged split, remove "isMultiWindowMode" */
- DeviceProfile(Context context, InvariantDeviceProfile inv, Info info, WindowBounds windowBounds,
+ DeviceProfile(Context context, InvariantDeviceProfile inv, Info info,
+ WindowManagerProxy wmProxy, IconShape iconShape, WindowBounds windowBounds,
SparseArray<DotRenderer> dotRendererCache, boolean isMultiWindowMode,
boolean transposeLayoutWithOrientation, boolean isMultiDisplay, boolean isGestureMode,
@NonNull final ViewScaleProvider viewScaleProvider,
@@ -420,7 +421,7 @@
isPhone = !isTablet;
isTwoPanels = isTablet && isMultiDisplay;
isTaskbarPresent = (isTablet || (enableTinyTaskbar() && isGestureMode))
- && WindowManagerProxy.INSTANCE.get(context).isTaskbarDrawnInProcess();
+ && wmProxy.isTaskbarDrawnInProcess();
// Some more constants.
context = getContext(context, info, inv.isFixedLandscape
@@ -845,8 +846,8 @@
dimensionOverrideProvider.accept(this);
// This is done last, after iconSizePx is calculated above.
- mDotRendererWorkSpace = createDotRenderer(context, iconSizePx, dotRendererCache);
- mDotRendererAllApps = createDotRenderer(context, allAppsIconSizePx, dotRendererCache);
+ mDotRendererWorkSpace = createDotRenderer(iconShape, iconSizePx, dotRendererCache);
+ mDotRendererAllApps = createDotRenderer(iconShape, allAppsIconSizePx, dotRendererCache);
}
/**
@@ -868,12 +869,12 @@
}
private static DotRenderer createDotRenderer(
- @NonNull Context context, int size, @NonNull SparseArray<DotRenderer> cache) {
+ @NonNull IconShape iconShape, int size, @NonNull SparseArray<DotRenderer> cache) {
DotRenderer renderer = cache.get(size);
if (renderer == null) {
renderer = new DotRenderer(
size,
- IconShape.INSTANCE.get(context).getShape().getPath(DEFAULT_DOT_SIZE),
+ iconShape.getShape().getPath(DEFAULT_DOT_SIZE),
DEFAULT_DOT_SIZE);
cache.put(size, renderer);
}
@@ -1090,7 +1091,7 @@
dotRendererCache.put(iconSizePx, mDotRendererWorkSpace);
dotRendererCache.put(allAppsIconSizePx, mDotRendererAllApps);
- return new Builder(context, inv, mInfo)
+ return inv.newDPBuilder(context, mInfo)
.setWindowBounds(bounds)
.setIsMultiDisplay(isMultiDisplay)
.setMultiWindowMode(isMultiWindowMode)
@@ -2473,9 +2474,11 @@
}
public static class Builder {
- private Context mContext;
- private InvariantDeviceProfile mInv;
- private Info mInfo;
+ private final Context mContext;
+ private final InvariantDeviceProfile mInv;
+ private final Info mInfo;
+ private final WindowManagerProxy mWMProxy;
+ private final IconShape mIconShape;
private WindowBounds mWindowBounds;
private boolean mIsMultiDisplay;
@@ -2491,10 +2494,13 @@
private boolean mIsTransientTaskbar;
- public Builder(Context context, InvariantDeviceProfile inv, Info info) {
+ public Builder(Context context, InvariantDeviceProfile inv, Info info,
+ WindowManagerProxy wmProxy, IconShape iconShape) {
mContext = context;
mInv = inv;
mInfo = info;
+ mWMProxy = wmProxy;
+ mIconShape = iconShape;
mIsTransientTaskbar = info.isTransientTaskbar();
}
@@ -2575,7 +2581,8 @@
if (mOverrideProvider == null) {
mOverrideProvider = DEFAULT_DIMENSION_PROVIDER;
}
- return new DeviceProfile(mContext, mInv, mInfo, mWindowBounds, mDotRendererCache,
+ return new DeviceProfile(mContext, mInv, mInfo, mWMProxy, mIconShape,
+ mWindowBounds, mDotRendererCache,
mIsMultiWindowMode, mTransposeLayoutWithOrientation, mIsMultiDisplay,
mIsGestureMode, mViewScaleProvider, mOverrideProvider, mIsTransientTaskbar);
}
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index e47a44a..43876a6 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -20,6 +20,7 @@
import static com.android.launcher3.LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE;
import static com.android.launcher3.LauncherPrefs.FIXED_LANDSCAPE_MODE;
import static com.android.launcher3.LauncherPrefs.GRID_NAME;
+import static com.android.launcher3.LauncherPrefs.NON_FIXED_LANDSCAPE_GRID_NAME;
import static com.android.launcher3.Utilities.dpiFromPx;
import static com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURCE_HANDLE;
import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
@@ -29,7 +30,6 @@
import static com.android.launcher3.util.DisplayController.CHANGE_TASKBAR_PINNING;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
@@ -45,7 +45,6 @@
import android.util.Log;
import android.util.SparseArray;
import android.util.Xml;
-import android.view.Display;
import androidx.annotation.DimenRes;
import androidx.annotation.IntDef;
@@ -55,18 +54,21 @@
import androidx.core.content.res.ResourcesCompat;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.DeviceGridState;
import com.android.launcher3.provider.RestoreDbTask;
import com.android.launcher3.testing.shared.ResourceUtils;
+import com.android.launcher3.util.DaggerSingletonObject;
+import com.android.launcher3.util.DaggerSingletonTracker;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.Info;
-import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Partner;
import com.android.launcher3.util.ResourceHelper;
-import com.android.launcher3.util.RunnableList;
-import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.launcher3.util.WindowBounds;
import com.android.launcher3.util.window.CachedDisplayInfo;
@@ -85,14 +87,19 @@
import java.util.Objects;
import java.util.stream.Collectors;
-public class InvariantDeviceProfile implements SafeCloseable {
+import javax.inject.Inject;
+
+@LauncherAppSingleton
+public class InvariantDeviceProfile {
public static final String TAG = "IDP";
// We do not need any synchronization for this variable as its only written on UI thread.
- public static final MainThreadInitializedObject<InvariantDeviceProfile> INSTANCE =
- new MainThreadInitializedObject<>(InvariantDeviceProfile::new);
+ public static final DaggerSingletonObject<InvariantDeviceProfile> INSTANCE =
+ new DaggerSingletonObject<>(LauncherAppComponent::getIDP);
public static final String GRID_NAME_PREFS_KEY = "idp_grid_name";
+ public static final String NON_FIXED_LANDSCAPE_GRID_NAME_PREFS_KEY =
+ "idp_non_fixed_landscape_grid_name";
@Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_PHONE, TYPE_MULTI_DISPLAY, TYPE_TABLET})
@@ -126,7 +133,10 @@
private static final String RES_GRID_NUM_COLUMNS = "grid_num_columns";
private static final String RES_GRID_ICON_SIZE_DP = "grid_icon_size_dp";
- private final RunnableList mCloseActions = new RunnableList();
+ private final DisplayController mDisplayController;
+ private final WindowManagerProxy mWMProxy;
+ private final LauncherPrefs mPrefs;
+ private final IconShape mIconShape;
/**
* Number of icons per row and column in the workspace.
@@ -244,16 +254,22 @@
private final ArrayList<OnIDPChangeListener> mChangeListeners = new ArrayList<>();
- @VisibleForTesting
- public InvariantDeviceProfile() {
- }
+ @Inject
+ InvariantDeviceProfile(
+ @ApplicationContext Context context,
+ LauncherPrefs prefs,
+ DisplayController dc,
+ WindowManagerProxy wmProxy,
+ IconShape iconShape,
+ DaggerSingletonTracker lifeCycle) {
+ mDisplayController = dc;
+ mWMProxy = wmProxy;
+ mPrefs = prefs;
+ mIconShape = iconShape;
- @TargetApi(23)
- private InvariantDeviceProfile(Context context) {
- String gridName = getCurrentGridName(context);
+ String gridName = prefs.get(GRID_NAME);
initGrid(context, gridName);
- DisplayController dc = DisplayController.INSTANCE.get(context);
dc.setPriorityListener(
(displayContext, info, flags) -> {
if ((flags & (CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS
@@ -262,119 +278,46 @@
onConfigChanged(displayContext);
}
});
- mCloseActions.add(() -> dc.setPriorityListener(null));
+ lifeCycle.addCloseable(() -> dc.setPriorityListener(null));
LauncherPrefChangeListener prefListener = key -> {
if (FIXED_LANDSCAPE_MODE.getSharedPrefKey().equals(key)
- && isFixedLandscape != FIXED_LANDSCAPE_MODE.get(context)) {
+ && isFixedLandscape != prefs.get(FIXED_LANDSCAPE_MODE)) {
Trace.beginSection("InvariantDeviceProfile#setFixedLandscape");
- onConfigChanged(context);
+ if (isFixedLandscape) {
+ setCurrentGrid(context, prefs.get(NON_FIXED_LANDSCAPE_GRID_NAME));
+ } else {
+ prefs.put(NON_FIXED_LANDSCAPE_GRID_NAME, mPrefs.get(GRID_NAME));
+ onConfigChanged(context);
+ }
Trace.endSection();
} else if (ENABLE_TWOLINE_ALLAPPS_TOGGLE.getSharedPrefKey().equals(key)
- && enableTwoLinesInAllApps != ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(context)) {
+ && enableTwoLinesInAllApps != prefs.get(ENABLE_TWOLINE_ALLAPPS_TOGGLE)) {
onConfigChanged(context);
}
};
- LauncherPrefs prefs = LauncherPrefs.INSTANCE.get(context);
prefs.addListener(prefListener, FIXED_LANDSCAPE_MODE, ENABLE_TWOLINE_ALLAPPS_TOGGLE);
- mCloseActions.add(() -> prefs.removeListener(prefListener,
+ lifeCycle.addCloseable(() -> prefs.removeListener(prefListener,
FIXED_LANDSCAPE_MODE, ENABLE_TWOLINE_ALLAPPS_TOGGLE));
SimpleBroadcastReceiver localeReceiver = new SimpleBroadcastReceiver(
MAIN_EXECUTOR, i -> onConfigChanged(context));
localeReceiver.register(context, Intent.ACTION_LOCALE_CHANGED);
- mCloseActions.add(() -> localeReceiver.unregisterReceiverSafely(context));
- }
-
- /**
- * This constructor should NOT have any monitors by design.
- */
- public InvariantDeviceProfile(Context context, String gridName) {
- String newName = initGrid(context, gridName);
- if (newName == null || !newName.equals(gridName)) {
- throw new IllegalArgumentException("Unknown grid name: " + gridName);
- }
- }
-
- /**
- * This constructor should NOT have any monitors by design.
- */
- public InvariantDeviceProfile(Context context, Display display) {
- // Ensure that the main device profile is initialized
- INSTANCE.get(context);
- String gridName = getCurrentGridName(context);
-
- // Get the display info based on default display and interpolate it to existing display
- Info defaultInfo = DisplayController.INSTANCE.get(context).getInfo();
- @DeviceType int defaultDeviceType = defaultInfo.getDeviceType();
- DisplayOption defaultDisplayOption = invDistWeightedInterpolate(
- defaultInfo,
- getPredefinedDeviceProfiles(
- context,
- gridName,
- defaultInfo,
- /*allowDisabledGrid=*/false,
- FIXED_LANDSCAPE_MODE.get(context)
- ),
- defaultDeviceType);
-
- Context displayContext = context.createDisplayContext(display);
- Info myInfo = new Info(displayContext);
- @DeviceType int deviceType = myInfo.getDeviceType();
- DisplayOption myDisplayOption = invDistWeightedInterpolate(
- myInfo,
- getPredefinedDeviceProfiles(
- context,
- gridName,
- myInfo,
- /*allowDisabledGrid=*/false,
- FIXED_LANDSCAPE_MODE.get(context)
- ),
- deviceType);
-
- DisplayOption result = new DisplayOption(defaultDisplayOption.grid)
- .add(myDisplayOption);
- result.iconSizes[INDEX_DEFAULT] =
- defaultDisplayOption.iconSizes[INDEX_DEFAULT];
- for (int i = 1; i < COUNT_SIZES; i++) {
- result.iconSizes[i] = Math.min(
- defaultDisplayOption.iconSizes[i], myDisplayOption.iconSizes[i]);
- }
-
- System.arraycopy(defaultDisplayOption.minCellSize, 0, result.minCellSize, 0,
- COUNT_SIZES);
- System.arraycopy(defaultDisplayOption.borderSpaces, 0, result.borderSpaces, 0,
- COUNT_SIZES);
-
- initGrid(context, myInfo, result);
- }
-
- @Override
- public void close() {
- mCloseActions.executeAllAndDestroy();
- }
-
- public static String getCurrentGridName(Context context) {
- return LauncherPrefs.get(context).get(GRID_NAME);
+ lifeCycle.addCloseable(() -> localeReceiver.unregisterReceiverSafely(context));
}
private String initGrid(Context context, String gridName) {
- FileLog.d(TAG, "Before initGrid:"
- + "gridName:" + gridName
- + ", dbFile:" + dbFile
- + ", LauncherPrefs GRID_NAME:" + LauncherPrefs.get(context).get(GRID_NAME)
- + ", LauncherPrefs DB_FILE:" + LauncherPrefs.get(context).get(DB_FILE));
- Info displayInfo = DisplayController.INSTANCE.get(context).getInfo();
+ Info displayInfo = mDisplayController.getInfo();
List<DisplayOption> allOptions = getPredefinedDeviceProfiles(
context,
gridName,
displayInfo,
- RestoreDbTask.isPending(context),
- FIXED_LANDSCAPE_MODE.get(context)
+ RestoreDbTask.isPending(mPrefs),
+ mPrefs.get(FIXED_LANDSCAPE_MODE)
);
// Filter out options that don't have the same number of columns as the grid
- DeviceGridState deviceGridState = new DeviceGridState(context);
+ DeviceGridState deviceGridState = new DeviceGridState(mPrefs);
List<DisplayOption> allOptionsFilteredByColCount =
filterByColumnCount(allOptions, deviceGridState.getColumns());
@@ -385,22 +328,23 @@
displayInfo.getDeviceType());
if (!displayOption.grid.name.equals(gridName)) {
- LauncherPrefs.get(context).put(GRID_NAME, displayOption.grid.name);
+ mPrefs.put(GRID_NAME, displayOption.grid.name);
}
initGrid(context, displayInfo, displayOption);
FileLog.d(TAG, "After initGrid:"
+ "gridName:" + gridName
+ ", dbFile:" + dbFile
- + ", LauncherPrefs GRID_NAME:" + LauncherPrefs.get(context).get(GRID_NAME)
- + ", LauncherPrefs DB_FILE:" + LauncherPrefs.get(context).get(DB_FILE));
+ + ", LauncherPrefs GRID_NAME:" + mPrefs.get(GRID_NAME)
+ + ", LauncherPrefs DB_FILE:" + mPrefs.get(DB_FILE));
return displayOption.grid.name;
}
private List<DisplayOption> filterByColumnCount(
List<DisplayOption> allOptions, int numColumns) {
- return allOptions.stream().filter(
- option -> option.grid.numColumns == numColumns).toList();
+ return allOptions.stream()
+ .filter(option -> option.grid.numColumns == numColumns)
+ .collect(Collectors.toList());
}
/**
@@ -409,18 +353,13 @@
*/
@Deprecated
public void reset(Context context) {
- initGrid(context, getCurrentGridName(context));
- }
-
- @VisibleForTesting
- public static String getDefaultGridName(Context context) {
- return new InvariantDeviceProfile().initGrid(context, null);
+ initGrid(context, mPrefs.get(GRID_NAME));
}
private void initGrid(Context context, Info displayInfo, DisplayOption displayOption) {
enableTwoLinesInAllApps = Flags.enableTwolineToggle()
&& Utilities.isEnglishLanguage(context)
- && ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(context);
+ && mPrefs.get(ENABLE_TWOLINE_ALLAPPS_TOGGLE);
mLocale = context.getResources().getConfiguration().locale.toString();
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
@@ -511,7 +450,7 @@
defaultWallpaperSize = new Point(displayInfo.currentSize);
SparseArray<DotRenderer> dotRendererCache = new SparseArray<>();
for (WindowBounds bounds : displayInfo.supportedBounds) {
- localSupportedProfiles.add(new DeviceProfile.Builder(context, this, displayInfo)
+ localSupportedProfiles.add(newDPBuilder(context, displayInfo)
.setIsMultiDisplay(deviceType == TYPE_MULTI_DISPLAY)
.setWindowBounds(bounds)
.setDotRendererCache(dotRendererCache)
@@ -550,6 +489,10 @@
});
}
+ DeviceProfile.Builder newDPBuilder(Context context, Info info) {
+ return new DeviceProfile.Builder(context, this, info, mWMProxy, mIconShape);
+ }
+
public void addOnChangeListener(OnIDPChangeListener listener) {
mChangeListeners.add(listener);
}
@@ -563,7 +506,7 @@
* migration.
*/
public void setCurrentGrid(Context context, String newGridName) {
- LauncherPrefs.get(context).put(GRID_NAME, newGridName);
+ mPrefs.put(GRID_NAME, newGridName);
MAIN_EXECUTOR.execute(() -> {
Trace.beginSection("InvariantDeviceProfile#setCurrentGrid");
onConfigChanged(context.getApplicationContext());
@@ -583,8 +526,7 @@
Object[] oldState = toModelState();
// Re-init grid
- String gridName = getCurrentGridName(context);
- initGrid(context, gridName);
+ initGrid(context, mPrefs.get(GRID_NAME));
boolean modelPropsChanged = !Arrays.equals(oldState, toModelState());
for (OnIDPChangeListener listener : mChangeListeners) {
@@ -901,11 +843,20 @@
return out;
}
- public DeviceProfile getDeviceProfile(Context context) {
- WindowManagerProxy windowManagerProxy = WindowManagerProxy.INSTANCE.get(context);
- Rect bounds = windowManagerProxy.getCurrentBounds(context);
- int rotation = windowManagerProxy.getRotation(context);
+ public DeviceProfile createDeviceProfileForSecondaryDisplay(Context displayContext) {
+ // Disable transpose layout and use multi-window mode so that the icons are scaled properly
+ return newDPBuilder(displayContext, new Info(displayContext))
+ .setIsMultiDisplay(false)
+ .setMultiWindowMode(true)
+ .setWindowBounds(mWMProxy.getRealBounds(
+ displayContext, mWMProxy.getDisplayInfo(displayContext)))
+ .setTransposeLayoutWithOrientation(false)
+ .build();
+ }
+ public DeviceProfile getDeviceProfile(Context context) {
+ Rect bounds = mWMProxy.getCurrentBounds(context);
+ int rotation = mWMProxy.getRotation(context);
return getBestMatch(bounds.width(), bounds.height(), rotation);
}
@@ -983,6 +934,9 @@
private static final int DEVICE_CATEGORY_PHONE = 1 << 0;
private static final int DEVICE_CATEGORY_TABLET = 1 << 1;
private static final int DEVICE_CATEGORY_MULTI_DISPLAY = 1 << 2;
+ private static final int GRID_TYPE_ONE_GRID = 1 << 0;
+ private static final int GRID_TYPE_NON_ONE_GRID = 1 << 1;
+ private static final int GRID_TYPE_ALL = 1 << 2;
private static final int DEVICE_CATEGORY_ALL =
DEVICE_CATEGORY_PHONE | DEVICE_CATEGORY_TABLET | DEVICE_CATEGORY_MULTI_DISPLAY;
@@ -999,6 +953,7 @@
public final int numColumns;
public final int numSearchContainerColumns;
public final int deviceCategory;
+ public final int gridType;
private final int[] numFolderRows = new int[COUNT_SIZES];
private final int[] numFolderColumns = new int[COUNT_SIZES];
@@ -1037,7 +992,6 @@
private final int mAllAppsCellSpecsTwoPanelId;
private final int mGridSizeSpecsId;
private final boolean mIsFixedLandscape;
- private final boolean mIsOldGrid;
public GridOption(Context context, AttributeSet attrs, Info displayInfo) {
TypedArray a = context.obtainStyledAttributes(
@@ -1185,7 +1139,7 @@
}
mIsFixedLandscape = a.getBoolean(R.styleable.GridDisplayOption_isFixedLandscape, false);
- mIsOldGrid = a.getBoolean(R.styleable.GridDisplayOption_isOldGrid, false);
+ gridType = a.getInt(R.styleable.GridDisplayOption_gridType, GRID_TYPE_ALL);
int inlineForRotation = a.getInt(R.styleable.GridDisplayOption_inlineQsb,
DONT_INLINE_QSB);
@@ -1230,13 +1184,11 @@
return mIsFixedLandscape && isFixedLandscape && Flags.oneGridSpecs();
}
- // Here we return true if we want to show the new grids.
- if (mGridSizeSpecsId != INVALID_RESOURCE_HANDLE) {
+ // If the grid type is one grid we return true when the flag is on, if the grid type
+ // is non-one grid we return true when the flag is off. Otherwise, we return true.
+ if (gridType == GRID_TYPE_ONE_GRID) {
return Flags.oneGridSpecs();
- }
-
- // Here we return true if we want to show the old grids.
- if (mIsOldGrid) {
+ } else if (gridType == GRID_TYPE_NON_ONE_GRID) {
return !Flags.oneGridSpecs();
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 58fd154..315301a 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -288,6 +288,7 @@
import java.util.Set;
import java.util.function.Predicate;
import java.util.function.Supplier;
+import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
@@ -2256,8 +2257,9 @@
*/
@Override
public void bindItems(final List<ItemInfo> items, final boolean forceAnimateIcons) {
- bindInflatedItems(items.stream().map(i -> Pair.create(
- i, getItemInflater().inflateItem(i, getModelWriter()))).toList(),
+ bindInflatedItems(items.stream()
+ .map(i -> Pair.create(i, getItemInflater().inflateItem(i, getModelWriter())))
+ .collect(Collectors.toList()),
forceAnimateIcons ? new AnimatorSet() : null);
}
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index 1120ec8..2a5cd63 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -21,6 +21,7 @@
import androidx.annotation.VisibleForTesting
import com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN
import com.android.launcher3.InvariantDeviceProfile.GRID_NAME_PREFS_KEY
+import com.android.launcher3.InvariantDeviceProfile.NON_FIXED_LANDSCAPE_GRID_NAME_PREFS_KEY
import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY
import com.android.launcher3.LauncherFiles.SHARED_PREFERENCES_KEY
import com.android.launcher3.dagger.ApplicationContext
@@ -304,6 +305,16 @@
@JvmField
val FIXED_LANDSCAPE_MODE = backedUpItem(SettingsActivity.FIXED_LANDSCAPE_MODE, false)
+ @JvmField
+ val NON_FIXED_LANDSCAPE_GRID_NAME =
+ ConstantItem(
+ NON_FIXED_LANDSCAPE_GRID_NAME_PREFS_KEY,
+ isBackedUp = true,
+ defaultValue = null,
+ encryptionType = EncryptionType.ENCRYPTED,
+ type = String::class.java,
+ )
+
// Preferences for widget configurations
@JvmField
val RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN =
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index d93c07f..cb3a0bc 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -658,9 +658,9 @@
appState.getInvariantDeviceProfile().fillResIconDpi);
// Only fetch badge if the icon is on workspace
if (info.id != ItemInfo.NO_ID && badge == null) {
- badge = appState.getIconCache().getShortcutInfoBadge(si)
- .newIcon(context, ThemeManager.INSTANCE.get(context)
- .isMonoThemeEnabled() ? FLAG_THEMED : 0);
+ badge = appState.getIconCache().getShortcutInfoBadge(si).newIcon(
+ context, ThemeManager.INSTANCE.get(context).isIconThemeEnabled()
+ ? FLAG_THEMED : 0);
}
}
} else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index b0001af..260ff9f 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -273,12 +273,7 @@
mFastScroller = findViewById(R.id.fast_scroller);
mFastScroller.setPopupView(findViewById(R.id.fast_scroller_popup));
mFastScrollLetterLayout = findViewById(R.id.scroll_letter_layout);
- if (Flags.letterFastScroller()) {
- // Set clip children to false otherwise the scroller letters will be clipped.
- setClipChildren(false);
- } else {
- setClipChildren(true);
- }
+ setClipChildren(false);
mSearchContainer = inflateSearchBar();
if (!isSearchBarFloating()) {
diff --git a/src/com/android/launcher3/celllayout/ReorderAlgorithm.java b/src/com/android/launcher3/celllayout/ReorderAlgorithm.java
index c303783..043c3be 100644
--- a/src/com/android/launcher3/celllayout/ReorderAlgorithm.java
+++ b/src/com/android/launcher3/celllayout/ReorderAlgorithm.java
@@ -26,6 +26,7 @@
import java.util.Comparator;
import java.util.List;
import java.util.Map.Entry;
+import java.util.stream.Collectors;
/**
* Contains the logic of a reorder.
@@ -143,12 +144,14 @@
// and not by the views hash which is "random".
// The views are sorted twice, once for the X position and a second time for the Y position
// to ensure same order everytime.
- Comparator comparator = Comparator.comparing(
- view -> ((CellLayoutLayoutParams) ((View) view).getLayoutParams()).getCellX()
+ Comparator<View> comparator = Comparator.comparing(
+ (View view) -> ((CellLayoutLayoutParams) view.getLayoutParams()).getCellX()
).thenComparing(
- view -> ((CellLayoutLayoutParams) ((View) view).getLayoutParams()).getCellY()
+ (View view) -> ((CellLayoutLayoutParams) view.getLayoutParams()).getCellY()
);
- List<View> views = solution.map.keySet().stream().sorted(comparator).toList();
+ List<View> views = solution.map.keySet().stream()
+ .sorted(comparator)
+ .collect(Collectors.toList());
for (View child : views) {
if (child == ignoreView) continue;
CellAndSpan c = solution.map.get(child);
diff --git a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
index 7bd7c3e..87b5459 100644
--- a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
+++ b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
@@ -18,6 +18,7 @@
import android.content.Context;
+import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.graphics.ThemeManager;
@@ -72,6 +73,7 @@
DisplayController getDisplayController();
WallpaperColorHints getWallpaperColorHints();
LockedUserState getLockedUserState();
+ InvariantDeviceProfile getIDP();
/** Builder for LauncherBaseAppComponent. */
interface Builder {
diff --git a/src/com/android/launcher3/dagger/LauncherComponentProvider.kt b/src/com/android/launcher3/dagger/LauncherComponentProvider.kt
index 5015e54..71e3354 100644
--- a/src/com/android/launcher3/dagger/LauncherComponentProvider.kt
+++ b/src/com/android/launcher3/dagger/LauncherComponentProvider.kt
@@ -47,6 +47,10 @@
.component
}
+ /** Extension method easily access LauncherAppComponent */
+ val Context.appComponent: LauncherAppComponent
+ get() = get(this)
+
private data class Holder(
val component: LauncherAppComponent,
private val filter: LayoutInflater.Filter?,
diff --git a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
index 80743af..12c65c7 100644
--- a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
+++ b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
@@ -55,6 +55,8 @@
import java.lang.ref.WeakReference;
import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -124,6 +126,8 @@
private static final int MESSAGE_ID_UPDATE_GRID = 7414;
private static final int MESSAGE_ID_UPDATE_COLOR = 856;
+ private static final String DEFAULT_SHAPE_KEY = "circle";
+
// Set of all active previews used to track duplicate memory allocations
private final Set<PreviewLifecycleObserver> mActivePreviews =
Collections.newSetFromMap(new ConcurrentHashMap<>());
@@ -157,7 +161,7 @@
// Handle default for when current shape doesn't match new shapes.
if (selectedShape.isEmpty()) {
selectedShape = Optional.ofNullable(ShapesProvider.INSTANCE.getIconShapes()
- .get("circle"));
+ .get(DEFAULT_SHAPE_KEY));
}
for (IconShapeModel shape : ShapesProvider.INSTANCE.getIconShapes().values()) {
@@ -177,7 +181,13 @@
KEY_NAME, KEY_GRID_TITLE, KEY_ROWS, KEY_COLS, KEY_PREVIEW_COUNT,
KEY_IS_DEFAULT, KEY_GRID_ICON_ID});
InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(getContext());
- for (GridOption gridOption : idp.parseAllGridOptions(getContext())) {
+ List<GridOption> gridOptionList = idp.parseAllGridOptions(getContext());
+ if (com.android.launcher3.Flags.oneGridSpecs()) {
+ gridOptionList.sort(Comparator
+ .comparingInt((GridOption option) -> option.numColumns)
+ .reversed());
+ }
+ for (GridOption gridOption : gridOptionList) {
cursor.newRow()
.add(KEY_NAME, gridOption.name)
.add(KEY_GRID_TITLE, gridOption.gridTitle)
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 911064c..03f0582 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -24,6 +24,7 @@
import static com.android.launcher3.BubbleTextView.DISPLAY_WORKSPACE;
import static com.android.launcher3.DeviceProfile.DEFAULT_SCALE;
import static com.android.launcher3.Hotseat.ALPHA_CHANNEL_PREVIEW_RENDERER;
+import static com.android.launcher3.LauncherPrefs.GRID_NAME;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
@@ -137,14 +138,14 @@
private final String mPrefName;
- public PreviewContext(Context base, InvariantDeviceProfile idp) {
+ public PreviewContext(Context base, String gridName) {
super(base);
mPrefName = "preview-" + UUID.randomUUID().toString();
- initDaggerComponent(DaggerLauncherPreviewRenderer_PreviewAppComponent.builder()
- .bindPrefs(new ProxyPrefs(
- this, getSharedPreferences(mPrefName, MODE_PRIVATE))));
-
- putObject(InvariantDeviceProfile.INSTANCE, idp);
+ LauncherPrefs prefs =
+ new ProxyPrefs(this, getSharedPreferences(mPrefName, MODE_PRIVATE));
+ prefs.put(GRID_NAME, gridName);
+ initDaggerComponent(
+ DaggerLauncherPreviewRenderer_PreviewAppComponent.builder().bindPrefs(prefs));
putObject(LauncherAppState.INSTANCE,
new LauncherAppState(this, null /* iconCacheFileName */));
}
@@ -192,8 +193,8 @@
this::getAppWidgetScale).build();
if (context instanceof PreviewContext) {
Context tempContext = ((PreviewContext) context).getBaseContext();
- mDpOrig = new InvariantDeviceProfile(tempContext, InvariantDeviceProfile
- .getCurrentGridName(tempContext)).getDeviceProfile(tempContext)
+ mDpOrig = InvariantDeviceProfile.INSTANCE.get(tempContext)
+ .getDeviceProfile(tempContext)
.copy(tempContext);
} else {
mDpOrig = mDp;
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index 7a60814..686024d 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -20,6 +20,7 @@
import static android.content.res.Configuration.UI_MODE_NIGHT_YES;
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.launcher3.LauncherPrefs.GRID_NAME;
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.graphics.ThemeManager.PREF_ICON_SHAPE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -60,7 +61,6 @@
import com.android.launcher3.model.BaseLauncherBinder;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.Callbacks;
-import com.android.launcher3.model.GridSizeMigrationDBController;
import com.android.launcher3.model.LoaderTask;
import com.android.launcher3.model.ModelDbController;
import com.android.launcher3.provider.LauncherDbUtils;
@@ -117,7 +117,7 @@
mGridName = bundle.getString("name");
bundle.remove("name");
if (mGridName == null) {
- mGridName = InvariantDeviceProfile.getCurrentGridName(context);
+ mGridName = LauncherPrefs.get(context).get(GRID_NAME);
}
mWallpaperColors = bundle.getParcelable(KEY_COLORS);
if (Flags.newCustomizationPickerUi()) {
@@ -316,11 +316,10 @@
@WorkerThread
private void loadModelData() {
final Context inflationContext = getPreviewContext();
- final InvariantDeviceProfile idp = new InvariantDeviceProfile(inflationContext, mGridName);
- if (GridSizeMigrationDBController.needsToMigrate(inflationContext, idp)
+ if (!mGridName.equals(LauncherPrefs.INSTANCE.get(mContext).get(GRID_NAME))
|| mShapeKey != null) {
// Start the migration
- PreviewContext previewContext = new PreviewContext(inflationContext, idp);
+ PreviewContext previewContext = new PreviewContext(inflationContext, mGridName);
if (mShapeKey != null) {
LauncherPrefs.INSTANCE.get(previewContext).put(PREF_ICON_SHAPE, mShapeKey);
}
@@ -348,6 +347,7 @@
@Override
public void run() {
+ InvariantDeviceProfile idp = LauncherAppState.getIDP(previewContext);
DeviceProfile deviceProfile = idp.getDeviceProfile(previewContext);
String query =
LauncherSettings.Favorites.SCREEN + " = " + Workspace.FIRST_SCREEN_ID
@@ -371,7 +371,7 @@
LauncherAppState.getInstance(inflationContext).getModel().loadAsync(dataModel -> {
if (dataModel != null) {
MAIN_EXECUTOR.execute(() -> renderView(inflationContext, dataModel, null,
- null, idp));
+ null, LauncherAppState.getIDP(inflationContext)));
} else {
Log.e(TAG, "Model loading failed");
}
diff --git a/src/com/android/launcher3/graphics/ThemeManager.kt b/src/com/android/launcher3/graphics/ThemeManager.kt
index 9f35e4a..242220a 100644
--- a/src/com/android/launcher3/graphics/ThemeManager.kt
+++ b/src/com/android/launcher3/graphics/ThemeManager.kt
@@ -40,8 +40,8 @@
open class ThemeManager
@Inject
constructor(
- @ApplicationContext private val context: Context,
- private val prefs: LauncherPrefs,
+ @ApplicationContext protected val context: Context,
+ protected val prefs: LauncherPrefs,
lifecycle: DaggerSingletonTracker,
) {
@@ -53,9 +53,11 @@
set(value) = prefs.put(THEMED_ICONS, value)
get() = prefs.get(THEMED_ICONS)
- var themeController: IconThemeController? =
- if (isMonoThemeEnabled) MonoIconThemeController() else null
- private set
+ val themeController: IconThemeController?
+ get() = iconState.themeController
+
+ val isIconThemeEnabled: Boolean
+ get() = themeController != null
private val listeners = CopyOnWriteArrayList<ThemeChangeListener>()
@@ -77,12 +79,10 @@
}
}
- private fun verifyIconState() {
+ protected fun verifyIconState() {
val newState = parseIconState()
if (newState == iconState) return
-
iconState = newState
- themeController = if (isMonoThemeEnabled) MonoIconThemeController() else null
listeners.forEach { it.onThemeChanged() }
}
@@ -105,15 +105,19 @@
return IconState(
iconMask = iconMask,
folderShapeMask = shapeModel?.folderPathString ?: iconMask,
- isMonoTheme = isMonoThemeEnabled,
+ themeController = createThemeController(),
)
}
+ protected open fun createThemeController(): IconThemeController? {
+ return if (isMonoThemeEnabled) MONO_THEME_CONTROLLER else null
+ }
+
data class IconState(
val iconMask: String,
val folderShapeMask: String,
- val isMonoTheme: Boolean,
- val themeCode: String = if (isMonoTheme) "with-theme" else "no-theme",
+ val themeController: IconThemeController?,
+ val themeCode: String = themeController?.themeID ?: "no-theme",
) {
fun toUniqueId() = "${iconMask.hashCode()},$themeCode"
}
@@ -135,5 +139,8 @@
private const val ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED"
private val CONFIG_ICON_MASK_RES_ID: Int =
Resources.getSystem().getIdentifier("config_icon_mask", "string", "android")
+
+ // Use a constant to allow equality check in verifyIconState
+ private val MONO_THEME_CONTROLLER = MonoIconThemeController()
}
}
diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java
deleted file mode 100644
index 5c6debe..0000000
--- a/src/com/android/launcher3/icons/LauncherIcons.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2016 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.icons;
-
-import static android.graphics.Color.BLACK;
-
-import static com.android.launcher3.graphics.ThemeManager.PREF_ICON_SHAPE;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Path;
-import android.graphics.Rect;
-import android.graphics.drawable.AdaptiveIconDrawable;
-import android.os.UserHandle;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.Flags;
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherPrefs;
-import com.android.launcher3.graphics.IconShape;
-import com.android.launcher3.graphics.ThemeManager;
-import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.SafeCloseable;
-import com.android.launcher3.util.UserIconInfo;
-
-import java.util.concurrent.ConcurrentLinkedQueue;
-
-/**
- * Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class
- * that are threadsafe.
- */
-public class LauncherIcons extends BaseIconFactory implements AutoCloseable {
-
- private static final float SEVEN_SIDED_COOKIE_SCALE = 72f / 80f;
- private static final float FOUR_SIDED_COOKIE_SCALE = 72f / 83.4f;
- private static final float VERY_SUNNY_SCALE = 72f / 92f;
- private static final float DEFAULT_ICON_SCALE = 1f;
-
-
- private static final MainThreadInitializedObject<Pool> POOL =
- new MainThreadInitializedObject<>(Pool::new);
-
- /**
- * Return a new Message instance from the global pool. Allows us to
- * avoid allocating new objects in many cases.
- */
- public static LauncherIcons obtain(Context context) {
- return POOL.get(context).obtain();
- }
-
- public static void clearPool(Context context) {
- POOL.get(context).close();
- }
-
- private final ConcurrentLinkedQueue<LauncherIcons> mPool;
-
- protected LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize,
- ConcurrentLinkedQueue<LauncherIcons> pool) {
- super(context, fillResIconDpi, iconBitmapSize);
- mThemeController = ThemeManager.INSTANCE.get(context).getThemeController();
- mPool = pool;
- }
-
- /**
- * Recycles a LauncherIcons that may be in-use.
- */
- public void recycle() {
- clear();
- mPool.add(this);
- }
-
- @NonNull
- @Override
- protected UserIconInfo getUserInfo(@NonNull UserHandle user) {
- return UserCache.INSTANCE.get(mContext).getUserInfo(user);
- }
-
- @NonNull
- @Override
- public Path getShapePath(AdaptiveIconDrawable drawable, Rect iconBounds) {
- if (!Flags.enableLauncherIconShapes()) return drawable.getIconMask();
- return IconShape.INSTANCE.get(mContext).getShape().getPath(iconBounds);
- }
-
- @Override
- protected void drawAdaptiveIcon(
- @NonNull Canvas canvas,
- @NonNull AdaptiveIconDrawable drawable,
- @NonNull Path overridePath
- ) {
- if (!Flags.enableLauncherIconShapes()) {
- super.drawAdaptiveIcon(canvas, drawable, overridePath);
- return;
- }
- String shapeKey = LauncherPrefs.get(mContext).get(PREF_ICON_SHAPE);
- float iconScale = switch (shapeKey) {
- case "seven_sided_cookie" -> SEVEN_SIDED_COOKIE_SCALE;
- case "four_sided_cookie" -> FOUR_SIDED_COOKIE_SCALE;
- case "sunny" -> VERY_SUNNY_SCALE;
- default -> DEFAULT_ICON_SCALE;
- };
- canvas.clipPath(overridePath);
- canvas.drawColor(BLACK);
- canvas.save();
- canvas.scale(iconScale, iconScale, canvas.getWidth() / 2f, canvas.getHeight() / 2f);
- if (drawable.getBackground() != null) {
- drawable.getBackground().draw(canvas);
- }
- if (drawable.getForeground() != null) {
- drawable.getForeground().draw(canvas);
- }
- canvas.restore();
- }
-
- @Override
- public void close() {
- recycle();
- }
-
- private static class Pool implements SafeCloseable {
-
- private final Context mContext;
-
- @NonNull
- private ConcurrentLinkedQueue<LauncherIcons> mPool = new ConcurrentLinkedQueue<>();
-
- private Pool(Context context) {
- mContext = context;
- }
-
- public LauncherIcons obtain() {
- ConcurrentLinkedQueue<LauncherIcons> pool = mPool;
- LauncherIcons m = pool.poll();
-
- if (m == null) {
- InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(mContext);
- return new LauncherIcons(mContext, idp.fillResIconDpi, idp.iconBitmapSize, pool);
- } else {
- return m;
- }
- }
-
- @Override
- public void close() {
- mPool = new ConcurrentLinkedQueue<>();
- }
- }
-}
diff --git a/src/com/android/launcher3/icons/LauncherIcons.kt b/src/com/android/launcher3/icons/LauncherIcons.kt
new file mode 100644
index 0000000..518f29d
--- /dev/null
+++ b/src/com/android/launcher3/icons/LauncherIcons.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2016 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.icons
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Path
+import android.graphics.Rect
+import android.graphics.drawable.AdaptiveIconDrawable
+import android.os.UserHandle
+import com.android.launcher3.Flags
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.LauncherPrefs
+import com.android.launcher3.graphics.IconShape
+import com.android.launcher3.graphics.ThemeManager
+import com.android.launcher3.pm.UserCache
+import com.android.launcher3.util.MainThreadInitializedObject
+import com.android.launcher3.util.SafeCloseable
+import com.android.launcher3.util.UserIconInfo
+import java.util.concurrent.ConcurrentLinkedQueue
+
+/**
+ * Wrapper class to provide access to [BaseIconFactory] and also to provide pool of this class that
+ * are threadsafe.
+ */
+class LauncherIcons
+protected constructor(
+ context: Context,
+ fillResIconDpi: Int,
+ iconBitmapSize: Int,
+ private val pool: ConcurrentLinkedQueue<LauncherIcons>,
+) : BaseIconFactory(context, fillResIconDpi, iconBitmapSize), AutoCloseable {
+
+ init {
+ mThemeController = ThemeManager.INSTANCE[context].themeController
+ }
+
+ /** Recycles a LauncherIcons that may be in-use. */
+ fun recycle() {
+ clear()
+ pool.add(this)
+ }
+
+ override fun getUserInfo(user: UserHandle): UserIconInfo {
+ return UserCache.INSTANCE[mContext].getUserInfo(user)
+ }
+
+ public override fun getShapePath(drawable: AdaptiveIconDrawable, iconBounds: Rect): Path {
+ if (!Flags.enableLauncherIconShapes()) return drawable.iconMask
+ return IconShape.INSTANCE[mContext].shape.getPath(iconBounds)
+ }
+
+ override fun drawAdaptiveIcon(
+ canvas: Canvas,
+ drawable: AdaptiveIconDrawable,
+ overridePath: Path,
+ ) {
+ if (!Flags.enableLauncherIconShapes()) {
+ super.drawAdaptiveIcon(canvas, drawable, overridePath)
+ return
+ }
+ val shapeKey = LauncherPrefs.get(mContext).get(ThemeManager.PREF_ICON_SHAPE)
+ val iconScale =
+ when (shapeKey) {
+ "seven_sided_cookie" -> SEVEN_SIDED_COOKIE_SCALE
+ "four_sided_cookie" -> FOUR_SIDED_COOKIE_SCALE
+ "sunny" -> VERY_SUNNY_SCALE
+ else -> DEFAULT_ICON_SCALE
+ }
+ canvas.clipPath(overridePath)
+ canvas.drawColor(Color.BLACK)
+ canvas.save()
+ canvas.scale(iconScale, iconScale, canvas.width / 2f, canvas.height / 2f)
+ if (drawable.background != null) {
+ drawable.background.draw(canvas)
+ }
+ if (drawable.foreground != null) {
+ drawable.foreground.draw(canvas)
+ }
+ canvas.restore()
+ }
+
+ override fun close() {
+ recycle()
+ }
+
+ private class Pool(private val context: Context) : SafeCloseable {
+ private var pool = ConcurrentLinkedQueue<LauncherIcons>()
+
+ fun obtain(): LauncherIcons {
+ val pool = pool
+ return pool.poll()
+ ?: InvariantDeviceProfile.INSTANCE[context].let {
+ LauncherIcons(context, it.fillResIconDpi, it.iconBitmapSize, pool)
+ }
+ }
+
+ override fun close() {
+ pool = ConcurrentLinkedQueue()
+ }
+ }
+
+ companion object {
+ private const val SEVEN_SIDED_COOKIE_SCALE = 72f / 80f
+ private const val FOUR_SIDED_COOKIE_SCALE = 72f / 83.4f
+ private const val VERY_SUNNY_SCALE = 72f / 92f
+ private const val DEFAULT_ICON_SCALE = 1f
+
+ private val POOL = MainThreadInitializedObject { Pool(it) }
+
+ /**
+ * Return a new Message instance from the global pool. Allows us to avoid allocating new
+ * objects in many cases.
+ */
+ @JvmStatic
+ fun obtain(context: Context): LauncherIcons {
+ return POOL[context].obtain()
+ }
+
+ @JvmStatic
+ fun clearPool(context: Context) {
+ POOL[context].close()
+ }
+ }
+}
diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java
index de74ae8..003bef3 100644
--- a/src/com/android/launcher3/model/BaseLauncherBinder.java
+++ b/src/com/android/launcher3/model/BaseLauncherBinder.java
@@ -390,8 +390,9 @@
ModelWriter writer = mApp.getModel()
.getWriter(false /* verifyChanges */, CellPosMapper.DEFAULT, null);
- List<Pair<ItemInfo, View>> bindItems = items.stream().map(i ->
- Pair.create(i, inflater.inflateItem(i, writer, null))).toList();
+ List<Pair<ItemInfo, View>> bindItems = items.stream()
+ .map(i -> Pair.create(i, inflater.inflateItem(i, writer, null)))
+ .collect(Collectors.toList());
executeCallbacksTask(c -> c.bindInflatedItems(bindItems), executor);
}
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index ddc775d..eab28b7 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -46,6 +46,7 @@
import com.android.launcher3.BuildConfig;
import com.android.launcher3.Workspace;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.CollectionInfo;
import com.android.launcher3.model.data.ItemInfo;
@@ -210,7 +211,7 @@
}
/**
- * Updates the deep shortucts state in system to match out internal model, pinning any missing
+ * Updates the deep shortcuts state in system to match out internal model, pinning any missing
* shortcuts and unpinning any extra shortcuts.
*/
public void updateShortcutPinnedState(Context context) {
@@ -266,6 +267,8 @@
|| !systemShortcuts.containsAll(modelShortcuts)) {
// Update system state for this package
try {
+ FileLog.d(TAG, "updateShortcutPinnedState:"
+ + " Pinning Shortcuts: " + entry.getKey() + ": " + modelShortcuts);
context.getSystemService(LauncherApps.class).pinShortcuts(
entry.getKey(), new ArrayList<>(modelShortcuts), user);
} catch (SecurityException | IllegalStateException e) {
@@ -278,6 +281,9 @@
systemMap.keySet().forEach(packageName -> {
// Update system state
try {
+ FileLog.d(TAG, "updateShortcutPinnedState:"
+ + " Unpinning extra Shortcuts for package: " + packageName
+ + ": " + systemMap.get(packageName));
context.getSystemService(LauncherApps.class).pinShortcuts(
packageName, Collections.emptyList(), user);
} catch (SecurityException | IllegalStateException e) {
diff --git a/src/com/android/launcher3/model/DeviceGridState.java b/src/com/android/launcher3/model/DeviceGridState.java
index 90af215..6f12c97 100644
--- a/src/com/android/launcher3/model/DeviceGridState.java
+++ b/src/com/android/launcher3/model/DeviceGridState.java
@@ -68,7 +68,10 @@
}
public DeviceGridState(Context context) {
- LauncherPrefs lp = LauncherPrefs.get(context);
+ this(LauncherPrefs.get(context));
+ }
+
+ public DeviceGridState(LauncherPrefs lp) {
mGridSizeString = lp.get(WORKSPACE_SIZE);
mNumHotseat = lp.get(HOTSEAT_COUNT);
mDeviceType = lp.get(DEVICE_TYPE);
diff --git a/src/com/android/launcher3/model/GridSizeMigrationDBController.java b/src/com/android/launcher3/model/GridSizeMigrationDBController.java
index b291421..47f13bd 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationDBController.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationDBController.java
@@ -193,7 +193,8 @@
List<DbEntry> filteredDstHotseatItems = dstHotseatItems;
if (srcHotseatSize < destHotseatSize) {
filteredDstHotseatItems = filteredDstHotseatItems.stream()
- .filter(entry -> entry.screenId < srcHotseatSize).toList();
+ .filter(entry -> entry.screenId < srcHotseatSize)
+ .collect(Collectors.toList());
}
final List<DbEntry> dstWorkspaceItems = destReader.loadAllWorkspaceEntries();
final List<DbEntry> hotseatToBeAdded = new ArrayList<>(1);
@@ -237,9 +238,12 @@
Collections.sort(hotseatToBeAdded);
Collections.sort(workspaceToBeAdded);
- List<Integer> idsInUse = dstWorkspaceItems.stream().map(entry -> entry.id).collect(
- Collectors.toList());
- idsInUse.addAll(dstHotseatItems.stream().map(entry -> entry.id).toList());
+ List<Integer> idsInUse = dstWorkspaceItems.stream()
+ .map(entry -> entry.id)
+ .collect(Collectors.toList());
+ idsInUse.addAll(dstHotseatItems.stream()
+ .map(entry -> entry.id)
+ .collect(Collectors.toList()));
// Migrate hotseat
solveHotseatPlacement(helper, destHotseatSize,
@@ -269,7 +273,8 @@
int screenId = destReader.mLastScreenId + 1;
while (!workspaceToBeAdded.isEmpty()) {
solveGridPlacement(helper, srcReader, destReader, screenId, trgX, trgY,
- workspaceToBeAdded, srcWorkspaceItems.stream().map(entry -> entry.id).toList());
+ workspaceToBeAdded,
+ srcWorkspaceItems.stream().map(entry -> entry.id).collect(Collectors.toList()));
screenId++;
}
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 6a8d86b..bd8c36b 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -24,6 +24,7 @@
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED;
import android.content.ComponentName;
import android.content.ContentValues;
@@ -307,7 +308,7 @@
* Make an WorkspaceItemInfo object for a restored application or shortcut item that points
* to a package that is not yet installed on the system.
*/
- public WorkspaceItemInfo getRestoredItemInfo(Intent intent) {
+ public WorkspaceItemInfo getRestoredItemInfo(Intent intent, boolean isArchived) {
final WorkspaceItemInfo info = new WorkspaceItemInfo();
info.user = user;
info.intent = intent;
@@ -317,7 +318,7 @@
mIconCache.getTitleAndIcon(info, DEFAULT_LOOKUP_FLAG);
}
- if (hasRestoreFlag(WorkspaceItemInfo.FLAG_RESTORED_ICON)) {
+ if (hasRestoreFlag(WorkspaceItemInfo.FLAG_RESTORED_ICON) || isArchived) {
String title = getTitle();
if (!TextUtils.isEmpty(title)) {
info.title = Utilities.trim(title);
@@ -333,6 +334,7 @@
info.contentDescription = mIconCache.getUserBadgedLabel(info.title, info.user);
info.itemType = itemType;
info.status = restoreFlag;
+ if (isArchived) info.runtimeStatusFlags |= FLAG_ARCHIVED;
return info;
}
diff --git a/src/com/android/launcher3/model/ModelDbController.java b/src/com/android/launcher3/model/ModelDbController.java
index 0138390..3a55aa7 100644
--- a/src/com/android/launcher3/model/ModelDbController.java
+++ b/src/com/android/launcher3/model/ModelDbController.java
@@ -90,6 +90,7 @@
import java.io.InputStream;
import java.io.StringReader;
import java.util.List;
+import java.util.stream.Collectors;
/**
* Utility class which maintains an instance of Launcher database and provides utility methods
@@ -377,7 +378,7 @@
// to run in grid migration based on if that grid already existed before migration or not.
List<String> existingDBs = LauncherFiles.GRID_DB_FILES.stream()
.filter(dbName -> mContext.getDatabasePath(dbName).exists())
- .toList();
+ .collect(Collectors.toList());
mOpenHelper = (mContext instanceof SandboxContext) ? oldHelper
: createDatabaseHelper(true, new DeviceGridState(idp).getDbFile());
@@ -460,7 +461,7 @@
// to run in grid migration based on if that grid already existed before migration or not.
List<String> existingDBs = LauncherFiles.GRID_DB_FILES.stream()
.filter(dbName -> mContext.getDatabasePath(dbName).exists())
- .toList();
+ .collect(Collectors.toList());
mOpenHelper = (mContext instanceof SandboxContext) ? oldHelper
: createDatabaseHelper(true /* forMigration */, targetDbName);
try {
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 6bef292..d1eceb9 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.model;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
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_ENABLED;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_WORK_PROFILE_QUIET_MODE_ENABLED;
@@ -39,7 +40,6 @@
import com.android.launcher3.Flags;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel.ModelUpdateTask;
-import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.IconCache;
@@ -238,19 +238,22 @@
if (itemInfo.isPromise() && isNewApkAvailable) {
boolean isTargetValid = !cn.getClassName().equals(
IconCache.EMPTY_CLASS_NAME);
- if (itemInfo.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+ if (itemInfo.itemType == ITEM_TYPE_DEEP_SHORTCUT) {
List<ShortcutInfo> shortcut =
new ShortcutRequest(context, mUser)
.forPackage(cn.getPackageName(),
itemInfo.getDeepShortcutId())
.query(ShortcutRequest.PINNED);
- if (shortcut.isEmpty()) {
+ if (shortcut.isEmpty()
+ && !(Flags.restoreArchivedShortcuts()
+ && !itemInfo.isArchived())
+ ) {
isTargetValid = false;
if (DEBUG) {
Log.d(TAG, "Pinned Shortcut not found for updated"
+ " package=" + itemInfo.getTargetPackage());
}
- } else {
+ } else if (!shortcut.isEmpty()) {
if (DEBUG) {
Log.d(TAG, "Found pinned shortcut for updated"
+ " package=" + itemInfo.getTargetPackage()
@@ -269,7 +272,7 @@
|| itemInfo.isArchived())) {
if (updateWorkspaceItemIntent(context, itemInfo, packageName)) {
infoUpdated = true;
- } else if (itemInfo.hasPromiseIconUi()) {
+ } else if (shouldRemoveRestoredShortcut(itemInfo)) {
removedShortcuts.add(itemInfo.id);
if (DEBUG) {
FileLog.w(TAG, "Removing restored shortcut promise icon"
@@ -436,7 +439,7 @@
*/
private boolean updateWorkspaceItemIntent(Context context,
WorkspaceItemInfo si, String packageName) {
- if (si.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+ if (si.itemType == ITEM_TYPE_DEEP_SHORTCUT) {
// Do not update intent for deep shortcuts as they contain additional information
// about the shortcut.
return false;
@@ -452,6 +455,15 @@
return false;
}
+ private boolean shouldRemoveRestoredShortcut(WorkspaceItemInfo itemInfo) {
+ if (itemInfo.hasPromiseIconUi() && !Flags.restoreArchivedShortcuts()) {
+ return true;
+ }
+ return Flags.restoreArchivedShortcuts()
+ && !itemInfo.isArchived()
+ && itemInfo.itemType == ITEM_TYPE_DEEP_SHORTCUT;
+ }
+
private String getOpString() {
return switch (mOp) {
case OP_NONE -> "NONE";
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.kt b/src/com/android/launcher3/model/ShortcutsChangedTask.kt
index 2e4f75f..56e9e43 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.kt
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.kt
@@ -17,6 +17,7 @@
import android.content.pm.ShortcutInfo
import android.os.UserHandle
+import com.android.launcher3.Flags
import com.android.launcher3.LauncherModel.ModelUpdateTask
import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
import com.android.launcher3.icons.CacheableShortcutInfo
@@ -59,8 +60,11 @@
val infoWrapper = ApplicationInfoWrapper(context, packageName, user)
if (shortcuts.isEmpty()) {
// Verify that the app is indeed installed.
- if (!infoWrapper.isInstalled() && !infoWrapper.isArchived()) {
- // App is not installed or archived, ignoring package events
+ if (
+ (!infoWrapper.isInstalled() && !infoWrapper.isArchived()) ||
+ (Flags.restoreArchivedShortcuts() && infoWrapper.isArchived())
+ ) {
+ // App is not installed or is archived, ignoring package events
return
}
}
@@ -75,7 +79,7 @@
val nonPinnedIds: MutableSet<String> = HashSet(allLauncherKnownIds)
val updatedWorkspaceItemInfos = ArrayList<WorkspaceItemInfo>()
for (fullDetails in shortcuts) {
- if (!fullDetails.isPinned) {
+ if (!fullDetails.isPinned && !Flags.restoreArchivedShortcuts()) {
continue
}
val shortcutId = fullDetails.id
diff --git a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
index 90f11a3..3919eb7 100644
--- a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
+++ b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
@@ -194,27 +194,36 @@
if (intent.`package` == null) {
intent.`package` = targetPkg
}
+ val isPreArchived = appInfoWrapper.isArchived() && c.restoreFlag != 0
+
// else if cn == null => can't infer much, leave it
// else if !validPkg => could be restored icon or missing sd-card
when {
- !TextUtils.isEmpty(targetPkg) && !validTarget -> {
+ !TextUtils.isEmpty(targetPkg) && (!validTarget || isPreArchived) -> {
// Points to a valid app (superset of cn != null) but the apk
// is not available.
when {
- c.restoreFlag != 0 -> {
+ c.restoreFlag != 0 || isPreArchived -> {
// Package is not yet available but might be
// installed later.
- FileLog.d(TAG, "package not yet restored: $targetPkg")
+ FileLog.d(
+ TAG,
+ "package not yet restored: $targetPkg, itemType=${c.itemType}" +
+ "isPreArchived=$isPreArchived, restoreFlag=${c.restoreFlag}",
+ )
tempPackageKey.update(targetPkg, c.user)
when {
c.hasRestoreFlag(WorkspaceItemInfo.FLAG_RESTORE_STARTED) -> {
// Restore has started once.
}
- installingPkgs.containsKey(tempPackageKey) -> {
+ installingPkgs.containsKey(tempPackageKey) || isPreArchived -> {
// App restore has started. Update the flag
c.restoreFlag =
c.restoreFlag or WorkspaceItemInfo.FLAG_RESTORE_STARTED
- FileLog.d(TAG, "restore started for installing app: $targetPkg")
+ FileLog.d(
+ TAG,
+ "restore started for installing app: $targetPkg, itemType=${c.itemType}",
+ )
c.updater().put(Favorites.RESTORED, c.restoreFlag).commit()
}
else -> {
@@ -253,9 +262,18 @@
}
}
if (c.restoreFlag and WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI != 0) {
+ FileLog.d(
+ TAG,
+ "restore flag set AND WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI != 0, setting valid target to false: $targetPkg, itemType=${c.itemType}, restoreFlag=${c.restoreFlag}",
+ )
validTarget = false
}
- if (validTarget) {
+ if (validTarget && !isPreArchived) {
+ FileLog.d(
+ TAG,
+ "valid target true, marking restored: $targetPkg," +
+ " itemType=${c.itemType}, restoreFlag=${c.restoreFlag}",
+ )
// The shortcut points to a valid target (either no target
// or something which is ready to be used)
c.markRestored()
@@ -265,7 +283,7 @@
when {
c.restoreFlag != 0 -> {
// Already verified above that user is same as default user
- info = c.getRestoredItemInfo(intent)
+ info = c.getRestoredItemInfo(intent, isPreArchived)
}
c.itemType == Favorites.ITEM_TYPE_APPLICATION ->
info = c.getAppShortcutInfo(intent, allowMissingTarget, useLowResIcon, false)
diff --git a/src/com/android/launcher3/model/data/AppPairInfo.kt b/src/com/android/launcher3/model/data/AppPairInfo.kt
index e620ac9..c0fe4fd 100644
--- a/src/com/android/launcher3/model/data/AppPairInfo.kt
+++ b/src/com/android/launcher3/model/data/AppPairInfo.kt
@@ -23,6 +23,7 @@
import com.android.launcher3.icons.IconCache
import com.android.launcher3.logger.LauncherAtom
import com.android.launcher3.views.ActivityContext
+import java.util.stream.Collectors
/** A type of app collection that launches multiple apps into split screen. */
class AppPairInfo() : CollectionInfo() {
@@ -54,7 +55,7 @@
/** Returns the app pair's member apps as an ArrayList of [ItemInfo]. */
override fun getContents(): ArrayList<ItemInfo> =
- ArrayList(contents.stream().map { it as ItemInfo }.toList())
+ ArrayList(contents.stream().map { it as ItemInfo }.collect(Collectors.toList()))
/** Returns the app pair's member apps as an ArrayList of [WorkspaceItemInfo]. */
override fun getAppContents(): ArrayList<WorkspaceItemInfo> = contents
diff --git a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
index 7fb0152..ff40f30 100644
--- a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
@@ -323,7 +323,7 @@
* Returns a FastBitmapDrawable with the icon and context theme applied
*/
public FastBitmapDrawable newIcon(Context context, @DrawableCreationFlags int creationFlags) {
- if (!ThemeManager.INSTANCE.get(context).isMonoThemeEnabled()) {
+ if (!ThemeManager.INSTANCE.get(context).isIconThemeEnabled()) {
creationFlags &= ~FLAG_THEMED;
}
FastBitmapDrawable drawable = bitmap.newIcon(context, creationFlags);
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index f56888b..34c9117 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -176,7 +176,7 @@
// At this point idp.dbFile contains the name of the dbFile from the previous phone
return LauncherFiles.GRID_DB_FILES.stream()
.filter(dbName -> context.getDatabasePath(dbName).exists())
- .toList();
+ .collect(Collectors.toList());
}
/**
@@ -415,7 +415,11 @@
}
public static boolean isPending(Context context) {
- return LauncherPrefs.get(context).has(RESTORE_DEVICE);
+ return isPending(LauncherPrefs.get(context));
+ }
+
+ public static boolean isPending(LauncherPrefs prefs) {
+ return prefs.has(RESTORE_DEVICE);
}
/**
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index df27b54..c20d655 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -115,15 +115,9 @@
if (mDragLayer != null) {
return;
}
- InvariantDeviceProfile currentDisplayIdp = new InvariantDeviceProfile(
- this, getWindow().getDecorView().getDisplay());
- // Disable transpose layout and use multi-window mode so that the icons are scaled properly
- mDeviceProfile = currentDisplayIdp.getDeviceProfile(this)
- .toBuilder(this)
- .setMultiWindowMode(true)
- .setTransposeLayoutWithOrientation(false)
- .build();
+ mDeviceProfile = InvariantDeviceProfile.INSTANCE.get(this)
+ .createDeviceProfileForSecondaryDisplay(this);
mDeviceProfile.autoResizeAllAppsCells();
setContentView(R.layout.secondary_launcher);
diff --git a/src/com/android/launcher3/shapes/ShapesProvider.kt b/src/com/android/launcher3/shapes/ShapesProvider.kt
index f1ea3a0..7e1f640 100644
--- a/src/com/android/launcher3/shapes/ShapesProvider.kt
+++ b/src/com/android/launcher3/shapes/ShapesProvider.kt
@@ -152,13 +152,20 @@
val iconShapes =
if (Flags.newCustomizationPickerUi() && LauncherFlags.enableLauncherIconShapes()) {
mapOf(
- "arch" to
+ "circle" to
IconShapeModel(
- key = "arch",
- title = "arch",
+ 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 =
- "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"]!!,
+ "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"]!!,
),
"four_sided_cookie" to
IconShapeModel(
@@ -176,6 +183,14 @@
"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"]!!,
),
+ "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"]!!,
+ ),
"sunny" to
IconShapeModel(
key = "sunny",
@@ -184,21 +199,6 @@
"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(
diff --git a/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java b/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
index d042b1d..4ccf16b 100644
--- a/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
@@ -222,7 +222,8 @@
if (shouldShowFullPageView(recommendations)) {
// Show all widgets in single page with unlimited available height.
return setRecommendations(
- recommendations.values().stream().flatMap(Collection::stream).toList(),
+ recommendations.values().stream().flatMap(Collection::stream)
+ .collect(Collectors.toList()),
deviceProfile, /*availableHeight=*/ Float.MAX_VALUE, availableWidth,
cellPadding);
@@ -369,7 +370,7 @@
// Show only those widgets that were displayed when user first opened the picker.
if (!mDisplayedWidgets.isEmpty()) {
filteredRecommendedWidgets = recommendedWidgets.stream().filter(
- w -> mDisplayedWidgets.contains(w.componentName)).toList();
+ w -> mDisplayedWidgets.contains(w.componentName)).collect(Collectors.toList());
}
Context context = getContext();
LayoutInflater inflater = LayoutInflater.from(context);
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index ab0f9a7..7a218ae 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -87,6 +87,7 @@
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
+import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
@@ -650,7 +651,7 @@
mRecommendedWidgets = mActivityContext.getWidgetPickerDataProvider().get()
.getRecommendations()
.values().stream()
- .flatMap(Collection::stream).toList();
+ .flatMap(Collection::stream).collect(Collectors.toList());
mRecommendedWidgetsCount = mWidgetRecommendationsView.setRecommendations(
mRecommendedWidgets,
mDeviceProfile,
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
index 0bcab60..216f4d4 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
@@ -41,6 +41,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.stream.Collectors;
/** A {@link TableLayout} for showing recommended widgets. */
public final class WidgetsRecommendationTableLayout extends TableLayout {
@@ -163,6 +164,7 @@
}
// Perform re-ordering once we have filtered out recommendations that fit.
- return filteredRows.stream().sorted(WIDGETS_TABLE_ROW_COUNT_COMPARATOR).toList();
+ return filteredRows.stream().sorted(WIDGETS_TABLE_ROW_COUNT_COMPARATOR)
+ .collect(Collectors.toList());
}
}
diff --git a/src/com/android/launcher3/widget/util/WidgetsTableUtils.java b/src/com/android/launcher3/widget/util/WidgetsTableUtils.java
index df72f07..1134781 100644
--- a/src/com/android/launcher3/widget/util/WidgetsTableUtils.java
+++ b/src/com/android/launcher3/widget/util/WidgetsTableUtils.java
@@ -95,7 +95,7 @@
List<ArrayList<WidgetItem>> rows = groupWidgetItemsUsingRowPxWithoutReordering(
sortedWidgetItems, context, dp, rowPx,
cellPadding);
- return rows.stream().sorted(WIDGETS_TABLE_ROW_SIZE_COMPARATOR).toList();
+ return rows.stream().sorted(WIDGETS_TABLE_ROW_SIZE_COMPARATOR).collect(Collectors.toList());
}
/**
diff --git a/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
index 9c64ec9..9d42e1b 100644
--- a/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
@@ -28,11 +28,13 @@
import android.view.Surface
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.LauncherPrefs.Companion.GRID_NAME
import com.android.launcher3.dagger.LauncherAppComponent
import com.android.launcher3.dagger.LauncherAppSingleton
import com.android.launcher3.testing.shared.ResourceUtils
import com.android.launcher3.util.AllModulesMinusWMProxy
import com.android.launcher3.util.DisplayController
+import com.android.launcher3.util.FakePrefsModule
import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
import com.android.launcher3.util.NavigationMode
import com.android.launcher3.util.WindowBounds
@@ -70,7 +72,7 @@
protected open val runningContext: Context = getApplicationContext()
private val displayController: DisplayController = mock()
private val windowManagerProxy: WindowManagerProxy = mock()
- private val launcherPrefs: LauncherPrefs = mock()
+ private lateinit var launcherPrefs: LauncherPrefs
@get:Rule val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT)
@@ -132,6 +134,7 @@
isGestureMode: Boolean = true,
isVerticalBar: Boolean = false,
isFixedLandscape: Boolean = false,
+ gridName: String? = GRID_NAME.defaultValue,
) {
val (naturalX, naturalY) = deviceSpec.naturalSize
val windowsBounds = phoneWindowsBounds(deviceSpec, isGestureMode, naturalX, naturalY)
@@ -145,6 +148,7 @@
isGestureMode,
densityDpi = deviceSpec.densityDpi,
isFixedLandscape = isFixedLandscape,
+ gridName = gridName,
)
}
@@ -152,6 +156,7 @@
deviceSpec: DeviceSpec,
isLandscape: Boolean = false,
isGestureMode: Boolean = true,
+ gridName: String? = GRID_NAME.defaultValue,
) {
val (naturalX, naturalY) = deviceSpec.naturalSize
val windowsBounds = tabletWindowsBounds(deviceSpec, naturalX, naturalY)
@@ -164,6 +169,7 @@
rotation = if (isLandscape) Surface.ROTATION_0 else Surface.ROTATION_90,
isGestureMode,
densityDpi = deviceSpec.densityDpi,
+ gridName = gridName,
)
}
@@ -173,6 +179,7 @@
isLandscape: Boolean = false,
isGestureMode: Boolean = true,
isFolded: Boolean = false,
+ gridName: String? = GRID_NAME.defaultValue,
) {
val (unfoldedNaturalX, unfoldedNaturalY) = deviceSpecUnfolded.naturalSize
val unfoldedWindowsBounds =
@@ -199,6 +206,7 @@
rotation = if (isLandscape) Surface.ROTATION_90 else Surface.ROTATION_0,
isGestureMode = isGestureMode,
densityDpi = deviceSpecFolded.densityDpi,
+ gridName = gridName,
)
} else {
initializeCommonVars(
@@ -207,6 +215,7 @@
rotation = if (isLandscape) Surface.ROTATION_0 else Surface.ROTATION_90,
isGestureMode = isGestureMode,
densityDpi = deviceSpecUnfolded.densityDpi,
+ gridName = gridName,
)
}
}
@@ -282,6 +291,7 @@
isGestureMode: Boolean = true,
densityDpi: Int,
isFixedLandscape: Boolean = false,
+ gridName: String? = GRID_NAME.defaultValue,
) {
setFlagsRule.setFlags(true, Flags.FLAG_ENABLE_TWOLINE_TOGGLE)
val windowsBounds = perDisplayBoundsCache[displayInfo]!!
@@ -311,18 +321,23 @@
context.initDaggerComponent(
DaggerAbsDPTestSandboxComponent.builder()
.bindWMProxy(windowManagerProxy)
- .bindLauncherPrefs(launcherPrefs)
.bindDisplayController(displayController)
)
+ launcherPrefs = context.appComponent.launcherPrefs
+ launcherPrefs.put(
+ LauncherPrefs.TASKBAR_PINNING.to(false),
+ LauncherPrefs.TASKBAR_PINNING_IN_DESKTOP_MODE.to(true),
+ LauncherPrefs.FIXED_LANDSCAPE_MODE.to(isFixedLandscape),
+ LauncherPrefs.HOTSEAT_COUNT.to(-1),
+ LauncherPrefs.DEVICE_TYPE.to(-1),
+ LauncherPrefs.WORKSPACE_SIZE.to(""),
+ LauncherPrefs.DB_FILE.to(""),
+ LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.to(true),
+ )
+ if (gridName != null) {
+ launcherPrefs.put(GRID_NAME, gridName)
+ }
- whenever(launcherPrefs.get(LauncherPrefs.TASKBAR_PINNING)).thenReturn(false)
- whenever(launcherPrefs.get(LauncherPrefs.TASKBAR_PINNING_IN_DESKTOP_MODE)).thenReturn(true)
- whenever(launcherPrefs.get(LauncherPrefs.FIXED_LANDSCAPE_MODE)).thenReturn(isFixedLandscape)
- whenever(launcherPrefs.get(LauncherPrefs.HOTSEAT_COUNT)).thenReturn(-1)
- whenever(launcherPrefs.get(LauncherPrefs.DEVICE_TYPE)).thenReturn(-1)
- whenever(launcherPrefs.get(LauncherPrefs.WORKSPACE_SIZE)).thenReturn("")
- whenever(launcherPrefs.get(LauncherPrefs.DB_FILE)).thenReturn("")
- whenever(launcherPrefs.get(LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE)).thenReturn(true)
val info = spy(DisplayController.Info(context, windowManagerProxy, perDisplayBoundsCache))
whenever(displayController.info).thenReturn(info)
whenever(info.isTransientTaskbar).thenReturn(isGestureMode)
@@ -365,15 +380,13 @@
}
@LauncherAppSingleton
-@Component(modules = [AllModulesMinusWMProxy::class])
+@Component(modules = [AllModulesMinusWMProxy::class, FakePrefsModule::class])
interface AbsDPTestSandboxComponent : LauncherAppComponent {
@Component.Builder
interface Builder : LauncherAppComponent.Builder {
@BindsInstance fun bindWMProxy(proxy: WindowManagerProxy): Builder
- @BindsInstance fun bindLauncherPrefs(prefs: LauncherPrefs): Builder
-
@BindsInstance fun bindDisplayController(displayController: DisplayController): Builder
override fun build(): AbsDPTestSandboxComponent
diff --git a/tests/multivalentTests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt b/tests/multivalentTests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
index bfbdb18..060c28c 100644
--- a/tests/multivalentTests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
@@ -15,7 +15,6 @@
*/
package com.android.launcher3
-import android.content.Context
import android.graphics.PointF
import android.graphics.Rect
import android.platform.test.rule.AllowedDevices
@@ -23,10 +22,11 @@
import android.platform.test.rule.IgnoreLimit
import android.platform.test.rule.LimitDevicesRule
import android.util.SparseArray
-import androidx.test.core.app.ApplicationProvider
import com.android.launcher3.DeviceProfile.DEFAULT_DIMENSION_PROVIDER
import com.android.launcher3.DeviceProfile.DEFAULT_PROVIDER
+import com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURCE_HANDLE
import com.android.launcher3.util.DisplayController.Info
+import com.android.launcher3.util.SandboxApplication
import com.android.launcher3.util.WindowBounds
import java.io.PrintWriter
import java.io.StringWriter
@@ -46,7 +46,8 @@
@IgnoreLimit(ignoreLimit = BuildConfig.IS_STUDIO_BUILD)
abstract class FakeInvariantDeviceProfileTest {
- protected lateinit var context: Context
+ @get:Rule val context = SandboxApplication()
+
protected lateinit var inv: InvariantDeviceProfile
protected val info = mock<Info>()
protected lateinit var windowBounds: WindowBounds
@@ -59,7 +60,6 @@
@Before
open fun setUp() {
- context = ApplicationProvider.getApplicationContext()
// make sure to reset values
useTwoPanels = false
isGestureMode = true
@@ -70,6 +70,8 @@
context,
inv,
info,
+ context.appComponent.wmProxy,
+ context.appComponent.iconShape,
windowBounds,
SparseArray(),
/*isMultiWindowMode=*/ false,
@@ -107,7 +109,7 @@
transposeLayoutWithOrientation = true
inv =
- InvariantDeviceProfile().apply {
+ context.appComponent.idp.apply {
numRows = 5
numColumns = 4
numSearchContainerColumns = 4
@@ -169,6 +171,14 @@
inlineQsb = BooleanArray(4) { false }
devicePaddingId = R.xml.paddings_handhelds
+
+ isFixedLandscape = false
+ workspaceSpecsId = INVALID_RESOURCE_HANDLE
+ allAppsSpecsId = INVALID_RESOURCE_HANDLE
+ folderSpecsId = INVALID_RESOURCE_HANDLE
+ hotseatSpecsId = INVALID_RESOURCE_HANDLE
+ workspaceCellSpecsId = INVALID_RESOURCE_HANDLE
+ allAppsCellSpecsId = INVALID_RESOURCE_HANDLE
}
}
@@ -189,7 +199,7 @@
useTwoPanels = false
inv =
- InvariantDeviceProfile().apply {
+ context.appComponent.idp.apply {
numRows = 5
numColumns = 6
numSearchContainerColumns = 3
@@ -252,6 +262,14 @@
inlineQsb = booleanArrayOf(false, true, false, false)
devicePaddingId = R.xml.paddings_handhelds
+
+ isFixedLandscape = false
+ workspaceSpecsId = INVALID_RESOURCE_HANDLE
+ allAppsSpecsId = INVALID_RESOURCE_HANDLE
+ folderSpecsId = INVALID_RESOURCE_HANDLE
+ hotseatSpecsId = INVALID_RESOURCE_HANDLE
+ workspaceCellSpecsId = INVALID_RESOURCE_HANDLE
+ allAppsCellSpecsId = INVALID_RESOURCE_HANDLE
}
}
@@ -274,7 +292,7 @@
useTwoPanels = true
inv =
- InvariantDeviceProfile().apply {
+ context.appComponent.idp.apply {
numRows = rows
numColumns = cols
numSearchContainerColumns = cols
@@ -332,6 +350,14 @@
inlineQsb = booleanArrayOf(false, false, false, false)
devicePaddingId = R.xml.paddings_handhelds
+
+ isFixedLandscape = false
+ workspaceSpecsId = INVALID_RESOURCE_HANDLE
+ allAppsSpecsId = INVALID_RESOURCE_HANDLE
+ folderSpecsId = INVALID_RESOURCE_HANDLE
+ hotseatSpecsId = INVALID_RESOURCE_HANDLE
+ workspaceCellSpecsId = INVALID_RESOURCE_HANDLE
+ allAppsCellSpecsId = INVALID_RESOURCE_HANDLE
}
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefs.kt b/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefs.kt
index 5e1e548..4d01d4d 100644
--- a/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefs.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefs.kt
@@ -21,19 +21,24 @@
import android.content.SharedPreferences
import com.android.launcher3.dagger.ApplicationContext
import com.android.launcher3.dagger.LauncherAppSingleton
-import java.io.File
+import com.android.launcher3.util.DaggerSingletonTracker
+import java.util.UUID
import javax.inject.Inject
/** Emulates Launcher preferences for a test environment. */
@LauncherAppSingleton
-class FakeLauncherPrefs @Inject constructor(@ApplicationContext context: Context) :
+class FakeLauncherPrefs
+@Inject
+constructor(@ApplicationContext context: Context, lifeCycle: DaggerSingletonTracker) :
LauncherPrefs(context) {
- private val backingPrefs =
- context.getSharedPreferences(
- File.createTempFile("fake-pref", ".xml", context.filesDir),
- MODE_PRIVATE,
- )
+ private val prefName = "fake-pref-" + UUID.randomUUID().toString()
+
+ private val backingPrefs = context.getSharedPreferences(prefName, MODE_PRIVATE)
+
+ init {
+ lifeCycle.addCloseable { context.deleteSharedPreferences(prefName) }
+ }
override fun getSharedPrefs(item: Item): SharedPreferences = backingPrefs
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefsTest.kt b/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefsTest.kt
index c57c86f..0941c79 100644
--- a/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefsTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefsTest.kt
@@ -18,10 +18,14 @@
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.util.DaggerSingletonTracker
import com.android.launcher3.util.LauncherMultivalentJUnit
import com.google.common.truth.Truth.assertThat
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
private val TEST_CONSTANT_ITEM = LauncherPrefs.nonRestorableItem("TEST_BOOLEAN_ITEM", false)
@@ -36,7 +40,15 @@
@RunWith(LauncherMultivalentJUnit::class)
class FakeLauncherPrefsTest {
- private val launcherPrefs = FakeLauncherPrefs(getApplicationContext())
+
+ @Mock lateinit var lifeCycle: DaggerSingletonTracker
+ private lateinit var launcherPrefs: FakeLauncherPrefs
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ launcherPrefs = FakeLauncherPrefs(getApplicationContext(), lifeCycle)
+ }
@Test
fun testGet_constantItemNotInPrefs_returnsDefaultValue() {
diff --git a/tests/multivalentTests/src/com/android/launcher3/graphics/ThemeManagerTest.kt b/tests/multivalentTests/src/com/android/launcher3/graphics/ThemeManagerTest.kt
index 43b7b68..85c1156 100644
--- a/tests/multivalentTests/src/com/android/launcher3/graphics/ThemeManagerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/graphics/ThemeManagerTest.kt
@@ -21,11 +21,13 @@
import com.android.launcher3.FakeLauncherPrefs
import com.android.launcher3.dagger.LauncherAppComponent
import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.icons.mono.MonoIconThemeController
import com.android.launcher3.util.AllModulesForTest
import com.android.launcher3.util.Executors.MAIN_EXECUTOR
import com.android.launcher3.util.FakePrefsModule
import com.android.launcher3.util.SandboxApplication
import com.android.launcher3.util.TestUtil
+import com.google.common.truth.Truth.assertThat
import dagger.Component
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
@@ -55,12 +57,13 @@
themeManager.isMonoThemeEnabled = true
TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
assertTrue(themeManager.isMonoThemeEnabled)
- assertTrue(themeManager.iconState.isMonoTheme)
+ assertThat(themeManager.iconState.themeController)
+ .isInstanceOf(MonoIconThemeController::class.java)
themeManager.isMonoThemeEnabled = false
TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
assertFalse(themeManager.isMonoThemeEnabled)
- assertFalse(themeManager.iconState.isMonoTheme)
+ assertThat(themeManager.iconState.themeController).isNull()
}
@Test
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/ShortcutsChangedTaskTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/ShortcutsChangedTaskTest.kt
index fb6d038..c6863f4 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/ShortcutsChangedTaskTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/ShortcutsChangedTaskTest.kt
@@ -24,8 +24,12 @@
import android.content.pm.ShortcutInfo
import android.os.Process.myUserHandle
import android.os.UserHandle
+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.LauncherAppState
import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
import com.android.launcher3.icons.BitmapInfo
@@ -42,6 +46,7 @@
import java.util.function.Predicate
import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
@@ -55,6 +60,8 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class ShortcutsChangedTaskTest {
+ @get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule()
+
private lateinit var shortcutsChangedTask: ShortcutsChangedTask
private lateinit var modelHelper: LauncherModelHelper
private lateinit var context: SandboxModelContext
@@ -131,7 +138,8 @@
}
@Test
- fun `When installed unpinned shortcut is found then remove from workspace`() {
+ @DisableFlags(Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS)
+ fun `When installed unpinned shortcut is found with Flag off then remove from workspace`() {
// Given
shortcuts =
listOf(
@@ -163,6 +171,37 @@
}
@Test
+ @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS)
+ fun `When installed unpinned shortcut is found with Flag on then keep in workspace`() {
+ // Given
+ shortcuts =
+ listOf(
+ mock<ShortcutInfo>().apply {
+ whenever(isPinned).thenReturn(false)
+ whenever(id).thenReturn(expectedShortcutId)
+ }
+ )
+ val items: IntSparseArrayMap<ItemInfo> = modelHelper.bgDataModel.itemsIdMap
+ items.put(expectedWai.id, expectedWai)
+ doReturn(
+ ApplicationInfo().apply {
+ enabled = true
+ flags = flags or FLAG_INSTALLED
+ isArchived = false
+ }
+ )
+ .whenever(launcherApps)
+ .getApplicationInfo(eq(expectedPackage), any(), eq(user))
+ doReturn(shortcuts).whenever(launcherApps).getShortcuts(any(), eq(user))
+ // When
+ shortcutsChangedTask.execute(mockTaskController, modelHelper.bgDataModel, mockAllApps)
+ // Then
+ verify(mockAppState.iconCache)
+ .getShortcutIcon(eq(expectedWai), any<CacheableShortcutInfo>())
+ verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWai))
+ }
+
+ @Test
fun `When shortcut app is uninstalled then skip handling`() {
// Given
shortcuts =
@@ -192,7 +231,8 @@
}
@Test
- fun `When archived pinned shortcut is found then keep in workspace`() {
+ @DisableFlags(Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS)
+ fun `When archived pinned shortcut is found with flag off then keep in workspace`() {
// Given
shortcuts =
listOf(
@@ -222,7 +262,8 @@
}
@Test
- fun `When archived unpinned shortcut is found then keep in workspace`() {
+ @DisableFlags(Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS)
+ fun `When archived unpinned shortcut is found with flag off then keep in workspace`() {
// Given
shortcuts =
listOf(
@@ -310,4 +351,34 @@
assertThat(modelHelper.bgDataModel.deepShortcutMap).doesNotContainKey(expectedKey)
verify(mockTaskController, times(0)).bindDeepShortcuts(eq(modelHelper.bgDataModel))
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS)
+ fun `When restoring archived shortcut with flag on then skip handling`() {
+ // Given
+ shortcuts =
+ listOf(
+ mock<ShortcutInfo>().apply {
+ whenever(isPinned).thenReturn(true)
+ whenever(id).thenReturn(expectedShortcutId)
+ }
+ )
+ val items: IntSparseArrayMap<ItemInfo> = modelHelper.bgDataModel.itemsIdMap
+ items.put(expectedWai.id, expectedWai)
+ doReturn(
+ ApplicationInfo().apply {
+ enabled = true
+ flags = flags or FLAG_INSTALLED
+ isArchived = true
+ }
+ )
+ .whenever(launcherApps)
+ .getApplicationInfo(eq(expectedPackage), any(), eq(user))
+ doReturn(shortcuts).whenever(launcherApps).getShortcuts(any(), eq(user))
+ // When
+ shortcutsChangedTask.execute(mockTaskController, modelHelper.bgDataModel, mockAllApps)
+ // Then
+ verify(mockTaskController, times(0)).deleteAndBindComponentsRemoved(any(), any())
+ verify(mockTaskController, times(0)).bindUpdatedWorkspaceItems(any())
+ }
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
index da87dfc..7a403e1 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
@@ -18,16 +18,19 @@
import android.appwidget.AppWidgetProviderInfo
import android.content.ComponentName
-import android.content.Context
import android.content.Intent
+import android.content.pm.ApplicationInfo
import android.content.pm.LauncherApps
import android.content.pm.PackageInstaller
import android.content.pm.ShortcutInfo
import android.os.Process
import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
import android.util.LongSparseArray
-import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.Flags
import com.android.launcher3.LauncherAppState
import com.android.launcher3.LauncherSettings.Favorites
import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
@@ -44,9 +47,14 @@
import com.android.launcher3.model.data.LauncherAppWidgetInfo
import com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_UI_NOT_READY
import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_RESTORED_ICON
+import com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_RESTORE_STARTED
import com.android.launcher3.pm.UserCache
import com.android.launcher3.shortcuts.ShortcutKey
import com.android.launcher3.util.ComponentKey
+import com.android.launcher3.util.ContentWriter
+import com.android.launcher3.util.LauncherModelHelper
+import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
import com.android.launcher3.util.PackageManagerHelper
import com.android.launcher3.util.PackageUserKey
import com.android.launcher3.util.UserIconInfo
@@ -55,6 +63,7 @@
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
@@ -66,6 +75,7 @@
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
@@ -73,20 +83,23 @@
@RunWith(AndroidJUnit4::class)
class WorkspaceItemProcessorTest {
+ @get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule()
+
@Mock private lateinit var mockIconRequestInfo: IconRequestInfo<WorkspaceItemInfo>
@Mock private lateinit var mockWorkspaceInfo: WorkspaceItemInfo
@Mock private lateinit var mockBgDataModel: BgDataModel
- @Mock private lateinit var mockContext: Context
@Mock private lateinit var mockAppState: LauncherAppState
@Mock private lateinit var mockPmHelper: PackageManagerHelper
- @Mock private lateinit var mockLauncherApps: LauncherApps
@Mock private lateinit var mockCursor: LoaderCursor
@Mock private lateinit var mockUserCache: UserCache
@Mock private lateinit var mockUserManagerState: UserManagerState
@Mock private lateinit var mockWidgetInflater: WidgetInflater
- private var intent: Intent = Intent()
- private var mUserHandle: UserHandle = UserHandle(0)
+ lateinit var mModelHelper: LauncherModelHelper
+ lateinit var mContext: SandboxModelContext
+ lateinit var mLauncherApps: LauncherApps
+ private var mIntent: Intent = Intent()
+ private var mUserHandle: UserHandle = Process.myUserHandle()
private var mIconRequestInfos: MutableList<IconRequestInfo<WorkspaceItemInfo>> = mutableListOf()
private var mComponentName: ComponentName = ComponentName("package", "class")
private var mUnlockedUsersArray: LongSparseArray<Boolean> = LongSparseArray()
@@ -101,40 +114,35 @@
@Before
fun setup() {
- mUserHandle = UserHandle(0)
+ mModelHelper = LauncherModelHelper()
+ mContext = mModelHelper.sandboxContext
+ mLauncherApps =
+ mContext.spyService(LauncherApps::class.java).apply {
+ doReturn(true).whenever(this).isPackageEnabled("package", mUserHandle)
+ doReturn(true).whenever(this).isActivityEnabled(mComponentName, mUserHandle)
+ }
+ mUserHandle = Process.myUserHandle()
mockIconRequestInfo = mock<IconRequestInfo<WorkspaceItemInfo>>()
mockWorkspaceInfo = mock<WorkspaceItemInfo>()
mockBgDataModel = mock<BgDataModel>()
mComponentName = ComponentName("package", "class")
mUnlockedUsersArray = LongSparseArray<Boolean>(1).apply { put(101, true) }
- intent =
+ mIntent =
Intent().apply {
component = mComponentName
`package` = "pkg"
putExtra(ShortcutKey.EXTRA_SHORTCUT_ID, "")
}
- mockLauncherApps =
- mock<LauncherApps>().apply {
- whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true)
- whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(true)
- }
- mockContext =
- mock<Context>().apply {
- whenever(packageManager).thenReturn(mock())
- whenever(packageManager.getUserBadgedLabel(any(), any())).thenReturn("")
- whenever(applicationContext).thenReturn(ApplicationProvider.getApplicationContext())
- whenever(getSystemService(LauncherApps::class.java)).thenReturn(mockLauncherApps)
- }
mockAppState =
mock<LauncherAppState>().apply {
- whenever(context).thenReturn(mockContext)
+ whenever(context).thenReturn(mContext)
whenever(iconCache).thenReturn(mock())
whenever(iconCache.getShortcutIcon(any(), any(), any())).then {}
}
mockPmHelper =
mock<PackageManagerHelper>().apply {
whenever(getAppLaunchIntent(mComponentName.packageName, mUserHandle))
- .thenReturn(intent)
+ .thenReturn(mIntent)
}
mockCursor =
mock(LoaderCursor::class.java, RETURNS_DEEP_STUBS).apply {
@@ -143,9 +151,9 @@
id = 1
restoreFlag = 1
serialNumber = 101
- whenever(parseIntent()).thenReturn(intent)
+ whenever(parseIntent()).thenReturn(mIntent)
whenever(markRestored()).doAnswer { restoreFlag = 0 }
- whenever(updater().put(Favorites.INTENT, intent.toUri(0)).commit()).thenReturn(1)
+ whenever(updater().put(Favorites.INTENT, mIntent.toUri(0)).commit()).thenReturn(1)
whenever(getAppShortcutInfo(any(), any(), any(), any()))
.thenReturn(mockWorkspaceInfo)
whenever(createIconRequestInfo(any(), any())).thenReturn(mockIconRequestInfo)
@@ -177,7 +185,7 @@
memoryLogger: LoaderMemoryLogger? = null,
userCache: UserCache = mockUserCache,
userManagerState: UserManagerState = mockUserManagerState,
- launcherApps: LauncherApps = mockLauncherApps,
+ launcherApps: LauncherApps = mLauncherApps,
shortcutKeyToPinnedShortcuts: Map<ShortcutKey, ShortcutInfo> = mKeyToPinnedShortcutsMap,
app: LauncherAppState = mockAppState,
bgDataModel: BgDataModel = mockBgDataModel,
@@ -244,7 +252,7 @@
fun `When app has null target package then mark deleted`() {
// Given
- intent.apply {
+ mIntent.apply {
component = null
`package` = null
}
@@ -264,8 +272,8 @@
// Given
mComponentName = ComponentName("", "")
- intent.component = mComponentName
- intent.`package` = ""
+ mIntent.component = mComponentName
+ mIntent.`package` = ""
// When
itemProcessorUnderTest = createWorkspaceItemProcessorUnderTest()
@@ -298,15 +306,14 @@
fun `When fallback Activity found for app then mark restored`() {
// Given
- mockLauncherApps =
- mock<LauncherApps>().apply {
- whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true)
- whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(false)
- }
+ mLauncherApps.apply {
+ whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true)
+ whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(false)
+ }
mockPmHelper =
mock<PackageManagerHelper>().apply {
whenever(getAppLaunchIntent(mComponentName.packageName, mUserHandle))
- .thenReturn(intent)
+ .thenReturn(mIntent)
}
// When
@@ -317,7 +324,7 @@
assertWithMessage("item restoreFlag should be set to 0")
.that(mockCursor.restoreFlag)
.isEqualTo(0)
- verify(mockCursor.updater().put(Favorites.INTENT, intent.toUri(0))).commit()
+ verify(mockCursor.updater().put(Favorites.INTENT, mIntent.toUri(0))).commit()
assertThat(mIconRequestInfos).containsExactly(mockIconRequestInfo)
verify(mockCursor).checkAndAddItem(mockWorkspaceInfo, mockBgDataModel, null)
}
@@ -326,11 +333,10 @@
fun `When app with disabled activity and no fallback found then mark deleted`() {
// Given
- mockLauncherApps =
- mock<LauncherApps>().apply {
- whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true)
- whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(false)
- }
+ mLauncherApps.apply {
+ whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true)
+ whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(false)
+ }
mockPmHelper =
mock<PackageManagerHelper>().apply {
whenever(getAppLaunchIntent(mComponentName.packageName, mUserHandle))
@@ -358,11 +364,11 @@
@Test
fun `When valid Pinned Deep Shortcut then mark restored`() {
-
// Given
mockCursor.itemType = ITEM_TYPE_DEEP_SHORTCUT
val expectedShortcutInfo =
mock<ShortcutInfo>().apply {
+ whenever(userHandle).thenReturn(mUserHandle)
whenever(id).thenReturn("")
whenever(`package`).thenReturn("")
whenever(activity).thenReturn(mock())
@@ -372,7 +378,7 @@
whenever(disabledReason).thenReturn(0)
whenever(persons).thenReturn(EMPTY_PERSON_ARRAY)
}
- val shortcutKey = ShortcutKey.fromIntent(intent, mockCursor.user)
+ val shortcutKey = ShortcutKey.fromIntent(mIntent, mockCursor.user)
mKeyToPinnedShortcutsMap[shortcutKey] = expectedShortcutInfo
mIconRequestInfos = mutableListOf()
@@ -393,6 +399,67 @@
}
@Test
+ @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS)
+ fun `When Archived Deep Shortcut with flag on then mark restored`() {
+ // Given
+ val mockContentWriter: ContentWriter = mock()
+ val mockAppInfo: ApplicationInfo =
+ mock<ApplicationInfo>().apply {
+ isArchived = true
+ enabled = true
+ }
+ val expectedRestoreFlag = FLAG_RESTORED_ICON or FLAG_RESTORE_STARTED
+ doReturn(mockAppInfo).whenever(mLauncherApps).getApplicationInfo(any(), any(), any())
+ whenever(mockContentWriter.put(Favorites.RESTORED, expectedRestoreFlag))
+ .thenReturn(mockContentWriter)
+ whenever(mockContentWriter.commit()).thenReturn(1)
+ mockCursor.apply {
+ itemType = ITEM_TYPE_DEEP_SHORTCUT
+ restoreFlag = restoreFlag or FLAG_RESTORED_ICON
+ whenever(updater()).thenReturn(mockContentWriter)
+ }
+ mIconRequestInfos = mutableListOf()
+
+ // When
+ itemProcessorUnderTest =
+ createWorkspaceItemProcessorUnderTest(allDeepShortcuts = mAllDeepShortcuts)
+ itemProcessorUnderTest.processItem()
+
+ // Then
+ assertThat(mockCursor.restoreFlag and FLAG_RESTORED_ICON).isEqualTo(FLAG_RESTORED_ICON)
+ assertThat(mockCursor.restoreFlag and FLAG_RESTORE_STARTED).isEqualTo(FLAG_RESTORE_STARTED)
+ assertThat(mIconRequestInfos).isNotEmpty()
+ assertThat(mAllDeepShortcuts).isEmpty()
+ verify(mockContentWriter).put(Favorites.RESTORED, expectedRestoreFlag)
+ verify(mockCursor).checkAndAddItem(any(), eq(mockBgDataModel), eq(null))
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS)
+ fun `When Archived Deep Shortcut with flag off then remove`() {
+ // Given
+ mockCursor.itemType = ITEM_TYPE_DEEP_SHORTCUT
+ mIconRequestInfos = mutableListOf()
+
+ // When
+ itemProcessorUnderTest =
+ createWorkspaceItemProcessorUnderTest(allDeepShortcuts = mAllDeepShortcuts)
+ itemProcessorUnderTest.processItem()
+
+ // Then
+ assertWithMessage("item restoreFlag should be set to 0")
+ .that(mockCursor.restoreFlag)
+ .isEqualTo(0)
+ assertThat(mIconRequestInfos).isEmpty()
+ assertThat(mAllDeepShortcuts).isEmpty()
+ verify(mockCursor)
+ .markDeleted(
+ "Pinned shortcut not found from request. package=pkg, user=UserHandle{0}",
+ "shortcut_not_found",
+ )
+ }
+
+ @Test
fun `When Pinned Deep Shortcut is not stored in ShortcutManager re-query by Shortcut ID`() {
// Given
mockCursor.itemType = ITEM_TYPE_DEEP_SHORTCUT
@@ -406,8 +473,9 @@
whenever(disabledMessage).thenReturn("")
whenever(disabledReason).thenReturn(0)
whenever(persons).thenReturn(EMPTY_PERSON_ARRAY)
+ whenever(userHandle).thenReturn(mUserHandle)
}
- whenever(mockLauncherApps.getShortcuts(any(), any())).thenReturn(listOf(si))
+ doReturn(listOf(si)).whenever(mLauncherApps).getShortcuts(any(), any())
mKeyToPinnedShortcutsMap.clear()
mIconRequestInfos = mutableListOf()
@@ -417,12 +485,12 @@
itemProcessorUnderTest.processItem()
// Then
- verify(mockLauncherApps).getShortcuts(any(), any())
+ verify(mLauncherApps).getShortcuts(any(), any())
assertWithMessage("item restoreFlag should be set to 0")
.that(mockCursor.restoreFlag)
.isEqualTo(0)
verify(mockCursor).markRestored()
- verify(mockCursor).checkAndAddItem(any(), any(), anyOrNull())
+ verify(mockCursor).checkAndAddItem(any(), any(), eq(null))
}
@Test
@@ -469,11 +537,11 @@
}
mIconRequestInfos = mutableListOf()
// Make sure shortcuts map has expected key from expected package
- intent.`package` = mComponentName.packageName
- val shortcutKey = ShortcutKey.fromIntent(intent, mockCursor.user)
+ mIntent.`package` = mComponentName.packageName
+ val shortcutKey = ShortcutKey.fromIntent(mIntent, mockCursor.user)
mKeyToPinnedShortcutsMap[shortcutKey] = expectedShortcutInfo
// set intent package back to null to test scenario
- intent.`package` = null
+ mIntent.`package` = null
// When
itemProcessorUnderTest =
@@ -656,7 +724,7 @@
itemProcessorUnderTest.processItem()
// Then
- verify(mockCursor).checkAndAddItem(any(), any(), anyOrNull())
+ verify(mockCursor).checkAndAddItem(any(), eq(mockBgDataModel), eq(null))
}
@Test
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
index b92582c..6af0950 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
@@ -17,7 +17,6 @@
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.launcher3.Flags.FLAG_ENABLE_GENERATED_PREVIEWS
-import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.R
import com.android.launcher3.icons.IconCache
import com.android.launcher3.model.WidgetItem
@@ -100,7 +99,7 @@
private fun createWidgetItem() {
Executors.MODEL_EXECUTOR.submit {
- val idp = InvariantDeviceProfile()
+ val idp = context.appComponent.idp
widgetItem = WidgetItem(appWidgetProviderInfo, idp, iconCache, context)
}
.get()
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfoTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfoTest.java
index 48cf3df..b3fd0f7 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfoTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfoTest.java
@@ -15,15 +15,12 @@
*/
package com.android.launcher3.widget;
-import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
-
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import android.appwidget.AppWidgetHostView;
-import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
@@ -32,16 +29,14 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.util.SandboxApplication;
-import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
-import java.util.ArrayList;
import java.util.Collections;
-import java.util.List;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -52,12 +47,7 @@
private static final int NUM_OF_COLS = 4;
private static final int NUM_OF_ROWS = 5;
- private Context mContext;
-
- @Before
- public void setUp() {
- mContext = getApplicationContext();
- }
+ @Rule public SandboxApplication mContext = new SandboxApplication();
@Test
public void initSpans_minWidthSmallerThanCellWidth_shouldInitializeSpansToOne() {
@@ -256,8 +246,9 @@
}
private InvariantDeviceProfile createIDP() {
- DeviceProfile dp = LauncherAppState.getIDP(mContext)
- .getDeviceProfile(mContext).copy(mContext);
+ InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(mContext);
+
+ DeviceProfile dp = idp.getDeviceProfile(mContext).copy(mContext);
DeviceProfile profile = Mockito.spy(dp);
doAnswer(i -> {
((Point) i.getArgument(0)).set(CELL_SIZE, CELL_SIZE);
@@ -267,10 +258,7 @@
profile.cellLayoutBorderSpacePx = new Point(SPACE_SIZE, SPACE_SIZE);
profile.widgetPadding.setEmpty();
- InvariantDeviceProfile idp = new InvariantDeviceProfile();
- List<DeviceProfile> supportedProfiles = new ArrayList<>(idp.supportedProfiles);
- supportedProfiles.add(profile);
- idp.supportedProfiles = Collections.unmodifiableList(supportedProfiles);
+ idp.supportedProfiles = Collections.singletonList(profile);
idp.numColumns = NUM_OF_COLS;
idp.numRows = NUM_OF_ROWS;
return idp;
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java
index ac67d2b..2fbeaf1 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java
@@ -25,8 +25,6 @@
import static android.content.pm.ApplicationInfo.CATEGORY_VIDEO;
import static android.content.pm.ApplicationInfo.FLAG_INSTALLED;
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -35,11 +33,9 @@
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
-import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
import android.os.Process;
@@ -53,12 +49,14 @@
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.SandboxApplication;
import com.android.launcher3.util.WidgetUtils;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.google.common.collect.ImmutableMap;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -92,22 +90,22 @@
private final ApplicationInfo mTestAppInfo = ApplicationInfoBuilder.newBuilder().setPackageName(
TEST_PACKAGE).setName(TEST_APP_NAME).build();
- private Context mContext;
+
+ @Rule public SandboxApplication mContext = new SandboxApplication();
@Mock
private IconCache mIconCache;
private WidgetItem mTestWidgetItem;
- @Mock
+
private LauncherApps mLauncherApps;
private InvariantDeviceProfile mTestProfile;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mContext = spy(getInstrumentation().getTargetContext());
- doReturn(mLauncherApps).when(mContext).getSystemService(LauncherApps.class);
+ mLauncherApps = mContext.spyService(LauncherApps.class);
mTestAppInfo.flags = FLAG_INSTALLED;
- mTestProfile = new InvariantDeviceProfile();
+ mTestProfile = InvariantDeviceProfile.INSTANCE.get(mContext);
mTestProfile.numRows = 5;
mTestProfile.numColumns = 5;
createTestWidgetItem();
@@ -128,10 +126,10 @@
testCategories.entrySet()) {
mTestAppInfo.category = testCategory.getKey();
- when(mLauncherApps.getApplicationInfo(/*packageName=*/ eq(TEST_PACKAGE),
+ doReturn(mTestAppInfo).when(mLauncherApps).getApplicationInfo(
+ /*packageName=*/ eq(TEST_PACKAGE),
/*flags=*/ anyInt(),
- /*user=*/ eq(Process.myUserHandle())))
- .thenReturn(mTestAppInfo);
+ /*user=*/ eq(Process.myUserHandle()));
WidgetRecommendationCategory category = Executors.MODEL_EXECUTOR.submit(() ->
new WidgetRecommendationCategoryProvider().getWidgetRecommendationCategory(
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
index c9b6d4f..767ab63 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
@@ -15,8 +15,6 @@
*/
package com.android.launcher3.widget.picker;
-import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
-
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -48,11 +46,13 @@
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.util.ActivityContextWrapper;
import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.util.SandboxApplication;
import com.android.launcher3.util.WidgetUtils;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -67,6 +67,7 @@
private static final String TEST_PACKAGE = "com.google.test";
private static final String APP_NAME = "Test app";
+ @Rule public SandboxApplication app = new SandboxApplication();
private Context mContext;
private WidgetsListHeaderViewHolderBinder mViewHolderBinder;
private InvariantDeviceProfile mTestProfile;
@@ -80,9 +81,9 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
- mContext = new ActivityContextWrapper(new ContextThemeWrapper(getApplicationContext(),
- R.style.WidgetContainerTheme));
- mTestProfile = new InvariantDeviceProfile();
+ mContext = new ActivityContextWrapper(new ContextThemeWrapper(
+ app, R.style.WidgetContainerTheme));
+ mTestProfile = InvariantDeviceProfile.INSTANCE.get(app);
mTestProfile.numRows = 5;
mTestProfile.numColumns = 5;
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
index 86bbcc1..e6f13a6 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
@@ -15,7 +15,6 @@
*/
package com.android.launcher3.widget.picker;
-import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.google.common.truth.Truth.assertThat;
@@ -50,6 +49,7 @@
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.util.ActivityContextWrapper;
+import com.android.launcher3.util.SandboxApplication;
import com.android.launcher3.util.WidgetUtils;
import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
@@ -57,6 +57,7 @@
import com.android.launcher3.widget.model.WidgetsListContentEntry;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -71,6 +72,7 @@
private static final String TEST_PACKAGE = "com.google.test";
private static final String APP_NAME = "Test app";
+ @Rule public SandboxApplication app = new SandboxApplication();
private Context mContext;
private WidgetsListTableViewHolderBinder mViewHolderBinder;
private InvariantDeviceProfile mTestProfile;
@@ -85,8 +87,8 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mContext = new ActivityContextWrapper(getApplicationContext());
- mTestProfile = new InvariantDeviceProfile();
+ mContext = new ActivityContextWrapper(app);
+ mTestProfile = InvariantDeviceProfile.INSTANCE.get(app);
mTestProfile.numRows = 5;
mTestProfile.numColumns = 5;
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java
index 6088c8e..bd34de6 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java
@@ -37,10 +37,12 @@
import com.android.launcher3.icons.cache.CachedObject;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.PackageItemInfo;
+import com.android.launcher3.util.SandboxApplication;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -64,6 +66,7 @@
private final ComponentName mWidget3 = ComponentName.createRelative(PACKAGE_NAME, ".mWidget3");
private final Map<ComponentName, String> mWidgetsToLabels = new HashMap();
+ @Rule public SandboxApplication app = new SandboxApplication();
@Mock private IconCache mIconCache;
private InvariantDeviceProfile mTestProfile;
@@ -76,7 +79,7 @@
mWidgetsToLabels.put(mWidget2, "Dog");
mWidgetsToLabels.put(mWidget3, "Bird");
- mTestProfile = new InvariantDeviceProfile();
+ mTestProfile = InvariantDeviceProfile.INSTANCE.get(app);
mTestProfile.numRows = 5;
mTestProfile.numColumns = 5;
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
index 59f352b..0cdda3a 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
@@ -46,6 +46,7 @@
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.search.SearchCallback;
+import com.android.launcher3.util.SandboxApplication;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
@@ -53,6 +54,7 @@
import com.android.launcher3.widget.picker.search.WidgetsSearchBar.WidgetsSearchDataProvider;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -66,6 +68,7 @@
@RunWith(AndroidJUnit4.class)
public class SimpleWidgetsSearchAlgorithmTest {
+ @Rule public SandboxApplication app = new SandboxApplication();
@Mock private IconCache mIconCache;
private InvariantDeviceProfile mTestProfile;
@@ -90,7 +93,7 @@
CachedObject componentWithLabel = invocation.getArgument(0);
return componentWithLabel.getComponent().getShortClassName();
}).when(mIconCache).getTitleNoCache(any());
- mTestProfile = new InvariantDeviceProfile();
+ mTestProfile = InvariantDeviceProfile.INSTANCE.get(app);
mTestProfile.numRows = 5;
mTestProfile.numColumns = 5;
mContext = getApplicationContext();
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSizesTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSizesTest.kt
index 7a858e4..2452a88 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSizesTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSizesTest.kt
@@ -25,6 +25,7 @@
import com.android.launcher3.DeviceProfile
import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.LauncherAppState
+import com.android.launcher3.dagger.LauncherComponentProvider.appComponent
import com.android.launcher3.icons.IconCache
import com.android.launcher3.model.WidgetItem
import com.android.launcher3.util.ActivityContextWrapper
@@ -53,7 +54,7 @@
context = ActivityContextWrapper(ApplicationProvider.getApplicationContext())
testInvariantProfile = LauncherAppState.getIDP(context)
widgetItemInvariantProfile =
- InvariantDeviceProfile().apply {
+ context.appComponent.idp.apply {
numRows = TEST_GRID_SIZE
numColumns = TEST_GRID_SIZE
}
@@ -143,13 +144,13 @@
widgetSize: Point,
context: Context,
invariantDeviceProfile: InvariantDeviceProfile,
- iconCache: IconCache
+ iconCache: IconCache,
): WidgetItem {
val providerInfo =
createAppWidgetProviderInfo(
ComponentName.createRelative(
TEST_PACKAGE,
- /*cls=*/ ".WidgetProvider_" + widgetSize.x + "x" + widgetSize.y
+ /*cls=*/ ".WidgetProvider_" + widgetSize.x + "x" + widgetSize.y,
)
)
val widgetInfo =
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java
index 2f5fcfe..a17e472 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java
@@ -15,8 +15,6 @@
*/
package com.android.launcher3.widget.picker.util;
-import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
-
import static com.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo;
import static com.google.common.truth.Truth.assertThat;
@@ -44,10 +42,12 @@
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.pm.ShortcutConfigActivityInfo;
import com.android.launcher3.util.ActivityContextWrapper;
+import com.android.launcher3.util.SandboxApplication;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.util.WidgetsTableUtils;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -68,6 +68,8 @@
private static final int NUM_OF_COLS = 5;
private static final int NUM_OF_ROWS = 5;
+ @Rule public SandboxApplication app = new SandboxApplication();
+
@Mock
private IconCache mIconCache;
@@ -89,9 +91,8 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
- mContext = new ActivityContextWrapper(getApplicationContext());
-
- mTestInvariantProfile = new InvariantDeviceProfile();
+ mContext = new ActivityContextWrapper(app);
+ mTestInvariantProfile = InvariantDeviceProfile.INSTANCE.get(app);
mTestInvariantProfile.numColumns = NUM_OF_COLS;
mTestInvariantProfile.numRows = NUM_OF_ROWS;
diff --git a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
index cdb45fc..8f64e84 100644
--- a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
+++ b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
@@ -15,7 +15,6 @@
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.launcher3.Flags
-import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.LauncherAppState
import com.android.launcher3.LauncherModel
import com.android.launcher3.LauncherModel.LoaderTransaction
@@ -120,12 +119,11 @@
.mockStatic(FirstScreenBroadcastHelper::class.java)
.startMocking()
val idp =
- InvariantDeviceProfile().apply {
+ context.appComponent.idp.apply {
numRows = 5
numColumns = 6
numDatabaseHotseatIcons = 5
}
- context.putObject(InvariantDeviceProfile.INSTANCE, idp)
context.putObject(LauncherAppState.INSTANCE, app)
doReturn(TestViewHelpers.findWidgetProvider(false))
diff --git a/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt b/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
index 2e2b6cd..05cf926 100644
--- a/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
+++ b/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
@@ -19,7 +19,6 @@
import com.android.launcher3.AbstractDeviceProfileTest
import com.android.launcher3.DeviceProfile
import com.android.launcher3.Flags
-import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.util.rule.setFlags
import org.junit.Before
import org.junit.Test
@@ -46,7 +45,7 @@
@Test
fun dumpPortraitGesture() {
initializeDevice(instance.deviceName, isGestureMode = true, isLandscape = false)
- val dp = getDeviceProfileForGrid(instance.gridName)
+ val dp = context.appComponent.idp.getDeviceProfile(context)
dp.isTaskbarPresentInApps = instance.isTaskbarPresentInApps
assertDump(dp, instance.filename("Portrait"))
@@ -55,7 +54,7 @@
@Test
fun dumpPortrait3Button() {
initializeDevice(instance.deviceName, isGestureMode = false, isLandscape = false)
- val dp = getDeviceProfileForGrid(instance.gridName)
+ val dp = context.appComponent.idp.getDeviceProfile(context)
dp.isTaskbarPresentInApps = instance.isTaskbarPresentInApps
assertDump(dp, instance.filename("Portrait3Button"))
@@ -64,7 +63,7 @@
@Test
fun dumpLandscapeGesture() {
initializeDevice(instance.deviceName, isGestureMode = true, isLandscape = true)
- val dp = getDeviceProfileForGrid(instance.gridName)
+ val dp = context.appComponent.idp.getDeviceProfile(context)
dp.isTaskbarPresentInApps = instance.isTaskbarPresentInApps
val testName =
@@ -79,7 +78,7 @@
@Test
fun dumpLandscape3Button() {
initializeDevice(instance.deviceName, isGestureMode = false, isLandscape = true)
- val dp = getDeviceProfileForGrid(instance.gridName)
+ val dp = context.appComponent.idp.getDeviceProfile(context)
dp.isTaskbarPresentInApps = instance.isTaskbarPresentInApps
val testName =
@@ -101,26 +100,25 @@
deviceSpecFolded = deviceSpecs["twopanel-phone"]!!,
isLandscape = isLandscape,
isGestureMode = isGestureMode,
+ gridName = instance.gridName,
)
"tablet" ->
initializeVarsForTablet(
deviceSpec = deviceSpec,
isLandscape = isLandscape,
isGestureMode = isGestureMode,
+ gridName = instance.gridName,
)
else ->
initializeVarsForPhone(
deviceSpec = deviceSpec,
isVerticalBar = isLandscape,
isGestureMode = isGestureMode,
+ gridName = instance.gridName,
)
}
}
- private fun getDeviceProfileForGrid(gridName: String): DeviceProfile {
- return InvariantDeviceProfile(context, gridName).getDeviceProfile(context)
- }
-
private fun assertDump(dp: DeviceProfile, filename: String) {
assertDump(dp, folderName, filename)
}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index afc0dd5..16faf14 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -1272,8 +1272,6 @@
if (getNavigationModel() == NavigationModel.ZERO_BUTTON
|| isThreeFingerTrackpadGesture) {
final Point displaySize = getRealDisplaySize();
- // TODO(b/225505986): change startY and endY back to displaySize.y / 2 once the
- // issue is solved.
int startX = isThreeFingerTrackpadGesture ? displaySize.x / 4 : 0;
int endX = isThreeFingerTrackpadGesture ? displaySize.x * 3 / 4 : displaySize.x / 2;
linearGesture(startX, displaySize.y / 4, endX, displaySize.y / 4,
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 2431ef5..1158521 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -211,6 +211,36 @@
}
/**
+ * Starts dismissing the task by swiping up, then cancels, and task springs back to start.
+ */
+ public void dismissCancel() {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+ LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "want to start dismissing an overview task then cancel")) {
+ verifyActiveContainer();
+ int taskCountBeforeDismiss = mOverview.getTaskCount();
+ mLauncher.assertNotEquals("Unable to find a task", 0, taskCountBeforeDismiss);
+
+ final Rect taskBounds = mLauncher.getVisibleBounds(mTask);
+ final int centerX = taskBounds.centerX();
+ final int centerY = taskBounds.bottom - 1;
+ final int endCenterY = centerY - (taskBounds.height() / 4);
+ mLauncher.executeAndWaitForLauncherEvent(
+ // Set slowDown to true so we do not fling the task at the end of the drag, as
+ // we want it to cancel and return back to the origin. We use 30 steps to
+ // perform the gesture slowly as well, to avoid flinging.
+ () -> mLauncher.linearGesture(centerX, centerY, centerX, endCenterY,
+ /* steps= */ 30, /* slowDown= */ true,
+ LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER),
+ event -> TestProtocol.DISMISS_ANIMATION_ENDS_MESSAGE.equals(
+ event.getClassName()),
+ () -> "Canceling swipe to dismiss did not end with task at origin.",
+ "cancel swiping to dismiss");
+
+ }
+ }
+
+ /**
* Clicks the task.
*/
public LaunchedAppState open() {