Merge "Apply bubble bar background alpha to stroke paint" into main
diff --git a/Android.bp b/Android.bp
index 4e1c9d3..9d7aa73 100644
--- a/Android.bp
+++ b/Android.bp
@@ -64,11 +64,74 @@
filegroup {
name: "launcher-quickstep-src",
srcs: [
- "quickstep/src/**/*.java",
"quickstep/src/**/*.kt",
+ "quickstep/src/**/*.java",
+ ":launcher-quickstep-processed-protolog-src",
],
}
+// Launcher ProtoLog support
+filegroup {
+ name: "launcher-quickstep-unprocessed-protolog-src",
+ srcs: [
+ "quickstep/src_protolog/**/*.java",
+ ],
+}
+
+java_library {
+ name: "launcher-quickstep_protolog-groups",
+ srcs: [
+ "quickstep/src_protolog/**/*.java",
+ ],
+ static_libs: [
+ "protolog-group",
+ "androidx.annotation_annotation",
+ "com_android_launcher3_flags_lib",
+ ],
+}
+
+genrule {
+ name: "launcher-quickstep-processed-protolog-src",
+ srcs: [
+ ":protolog-impl",
+ ":launcher-quickstep-unprocessed-protolog-src",
+ ":launcher-quickstep_protolog-groups",
+ ],
+ tools: ["protologtool"],
+ cmd: "$(location protologtool) transform-protolog-calls " +
+ "--protolog-class com.android.internal.protolog.common.ProtoLog " +
+ "--loggroups-class com.android.quickstep.util.QuickstepProtoLogGroup " +
+ "--loggroups-jar $(location :launcher-quickstep_protolog-groups) " +
+ "--viewer-config-file-path /system_ext/etc/launcher.quickstep.protolog.pb " +
+ "--output-srcjar $(out) " +
+ "$(locations :launcher-quickstep-unprocessed-protolog-src)",
+ out: ["launcher.quickstep.protolog.srcjar"],
+}
+
+genrule {
+ name: "gen-launcher.quickstep.protolog.pb",
+ srcs: [
+ ":launcher-quickstep-unprocessed-protolog-src",
+ ":launcher-quickstep_protolog-groups",
+ ],
+ tools: ["protologtool"],
+ cmd: "$(location protologtool) generate-viewer-config " +
+ "--protolog-class com.android.internal.protolog.common.ProtoLog " +
+ "--loggroups-class com.android.quickstep.util.QuickstepProtoLogGroup " +
+ "--loggroups-jar $(location :launcher-quickstep_protolog-groups) " +
+ "--viewer-config-type proto " +
+ "--viewer-config $(out) " +
+ "$(locations :launcher-quickstep-unprocessed-protolog-src)",
+ out: ["launcher.quickstep.protolog.pb"],
+}
+
+prebuilt_etc {
+ name: "launcher.quickstep.protolog.pb",
+ system_ext_specific: true,
+ src: ":gen-launcher.quickstep.protolog.pb",
+ filename_from_src: true,
+}
+
// Source code for quickstep dagger
filegroup {
name: "launcher-quickstep-dagger",
@@ -405,6 +468,7 @@
"SystemUISharedLib",
"SettingsLibSettingsTheme",
"dagger2",
+ "protolog-group",
],
manifest: "quickstep/AndroidManifest.xml",
min_sdk_version: "current",
@@ -503,7 +567,10 @@
"Launcher2",
"Launcher3",
],
- required: ["privapp_whitelist_com.android.launcher3"],
+ required: [
+ "privapp_whitelist_com.android.launcher3",
+ "launcher.quickstep.protolog.pb",
+ ],
resource_dirs: ["quickstep/res"],
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 4d6c7ab..9fb5b7b 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -426,3 +426,21 @@
description: "Show recent apps in the taskbar overflow."
bug: "368119679"
}
+
+flag {
+ name: "enable_active_gesture_proto_log"
+ namespace: "launcher"
+ description: "Enables tracking active gesture logs in ProtoLog"
+ bug: "293182501"
+}
+
+
+flag {
+ name: "coordinate_workspace_scale"
+ namespace: "launcher"
+ description: "Ensure that the workspace and hotseat scale doesn't conflict and transitions smoothly between launching and closing apps"
+ bug: "366403487"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java b/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
index 0fb9718..8c2f5d5 100644
--- a/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
+++ b/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
@@ -149,8 +149,7 @@
// Disable Overview Actions for Work Profile apps
boolean isManagedProfileTask =
UserManager.get(mApplicationContext).isManagedProfile(task.key.userId);
- boolean isAllowedByPolicy = mTaskContainer.getThumbnailViewDeprecated().isRealSnapshot()
- && !isManagedProfileTask;
+ boolean isAllowedByPolicy = isRealSnapshot() && !isManagedProfileTask;
getActionsView().setCallbacks(new OverlayUICallbacksGoImpl(isAllowedByPolicy, task));
mTaskPackageName = task.key.getPackageName();
mSharedPreferences = LauncherPrefs.getPrefs(mApplicationContext);
diff --git a/proguard.flags b/proguard.flags
index 31edd8d..da00c00 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -57,3 +57,7 @@
-keep class com.android.quickstep.** {
*;
}
+
+-keep class com.android.internal.protolog.** {
+ *;
+}
diff --git a/quickstep/res/layout/task_thumbnail.xml b/quickstep/res/layout/task_thumbnail.xml
index d90d916..afbcdb5 100644
--- a/quickstep/res/layout/task_thumbnail.xml
+++ b/quickstep/res/layout/task_thumbnail.xml
@@ -16,6 +16,7 @@
<com.android.quickstep.task.thumbnail.TaskThumbnailView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/snapshot"
android:layout_width="match_parent"
android:layout_height="match_parent" >
diff --git a/quickstep/res/values-am/strings.xml b/quickstep/res/values-am/strings.xml
index 28bd69a..0848ddd 100644
--- a/quickstep/res/values-am/strings.xml
+++ b/quickstep/res/values-am/strings.xml
@@ -139,8 +139,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"ሁልጊዜ የተግባር አሞሌ ያሳዩ"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"የአሰሳ ሁነታን ይለውጡ"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"የተግባር አሞሌ አካፋይ"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"የተግባር አሞሌ ትርፍ ፍሰት"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ወደ ላይ/ግራ ይውሰዱ"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ወደ ታች/ቀኝ ይውሰዱ"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{ተጨማሪ መተግበሪያ}one{ተጨማሪ መተግበሪያ}other{ተጨማሪ መተግበሪያዎች}}"</string>
diff --git a/quickstep/res/values-b+sr+Latn/strings.xml b/quickstep/res/values-b+sr+Latn/strings.xml
index f08cf83..aa16f3c 100644
--- a/quickstep/res/values-b+sr+Latn/strings.xml
+++ b/quickstep/res/values-b+sr+Latn/strings.xml
@@ -139,8 +139,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Uvek prikazuj traku zadataka"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Promeni režim navigacije"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Razdelnik trake zadataka"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Preklopna traka zadataka"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Premesti gore levo"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Premesti dole desno"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{dodatna aplikacija}one{dodatna aplikacija}few{dodatne aplikacije}other{dodatnih aplikacija}}"</string>
diff --git a/quickstep/res/values-be/strings.xml b/quickstep/res/values-be/strings.xml
index fb5b556..4dcfe62 100644
--- a/quickstep/res/values-be/strings.xml
+++ b/quickstep/res/values-be/strings.xml
@@ -139,8 +139,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Заўсёды паказваць панэль задач"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Змяніць рэжым навігацыі"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Раздзяляльнік панэлі задач"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Меню з пашырэннем панэлі задач"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Перамясціць уверх/улева"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Перамясціць уніз/управа"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{даступная праграма}one{даступная праграма}few{даступныя праграмы}many{даступных праграм}other{даступнай праграмы}}"</string>
diff --git a/quickstep/res/values-bs/strings.xml b/quickstep/res/values-bs/strings.xml
index 6588dd0..3357a6e 100644
--- a/quickstep/res/values-bs/strings.xml
+++ b/quickstep/res/values-bs/strings.xml
@@ -139,8 +139,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Uvijek prikaži traku zadataka"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Promijeni način navigacije"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Razdjelnik trake zadataka"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Dodatni izbornik trake sa zadacima"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Premjesti gore lijevo"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Premjesti dolje desno"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{dodatna aplikacija}one{dodatna aplikacija}few{dodatne aplikacije}other{dodatnih aplikacija}}"</string>
diff --git a/quickstep/res/values-en-rGB/strings.xml b/quickstep/res/values-en-rGB/strings.xml
index a74a17b..04b04dd 100644
--- a/quickstep/res/values-en-rGB/strings.xml
+++ b/quickstep/res/values-en-rGB/strings.xml
@@ -139,8 +139,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Always show Taskbar"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Change navigation mode"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Taskbar divider"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Taskbar overflow"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Move to top/left"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Move to bottom/right"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{more app}other{more apps}}"</string>
diff --git a/quickstep/res/values-es/strings.xml b/quickstep/res/values-es/strings.xml
index 527bdc3..d8bbc55 100644
--- a/quickstep/res/values-es/strings.xml
+++ b/quickstep/res/values-es/strings.xml
@@ -139,8 +139,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Barra de tareas visible"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Cambiar el modo de navegación"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Divisor de Barra de Tareas"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Barra de tareas ampliada"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Mover arriba/a la izquierda"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mover abajo/a la derecha"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{aplicación más}other{aplicaciones más}}"</string>
diff --git a/quickstep/res/values-hr/strings.xml b/quickstep/res/values-hr/strings.xml
index a2bf691..2514aa1 100644
--- a/quickstep/res/values-hr/strings.xml
+++ b/quickstep/res/values-hr/strings.xml
@@ -139,8 +139,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Uvijek prikaži traku zadataka"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Promijeni način navigacije"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Razdjelnik trake sa zadacima"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Dodatni izbornik trake sa zadacima"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Premjesti gore/lijevo"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Premjesti dolje/desno"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{dodatna aplikacija}one{dodatna aplikacija}few{dodatne aplikacije}other{dodatnih aplikacija}}"</string>
diff --git a/quickstep/res/values-is/strings.xml b/quickstep/res/values-is/strings.xml
index 274e1da..3aec0ce 100644
--- a/quickstep/res/values-is/strings.xml
+++ b/quickstep/res/values-is/strings.xml
@@ -139,8 +139,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Alltaf sýna forritastiku"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Breyta leiðsagnarstillingu"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Skipting forritastiku"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Yfirflæði á forritastiku"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Færa efst/til vinstri"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Færa neðst/til hægri"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{forrit til viðbótar}one{forrit til viðbótar}other{forrit til viðbótar}}"</string>
diff --git a/quickstep/res/values-ja/strings.xml b/quickstep/res/values-ja/strings.xml
index 0cff4c1..28b7746 100644
--- a/quickstep/res/values-ja/strings.xml
+++ b/quickstep/res/values-ja/strings.xml
@@ -139,8 +139,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"常にタスクバーを表示する"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"ナビゲーション モードを変更"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"タスクバーの区切り"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"タスクバーのオーバフロー"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"上 / 左に移動"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"下 / 右に移動"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{個のその他のアプリ}other{個のその他のアプリ}}"</string>
diff --git a/quickstep/res/values-ka/strings.xml b/quickstep/res/values-ka/strings.xml
index efff980..d84d53e 100644
--- a/quickstep/res/values-ka/strings.xml
+++ b/quickstep/res/values-ka/strings.xml
@@ -139,8 +139,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"ამოცანათა ზოლის მუდამ ჩვენება"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"შეცვალეთ ნავიგაციის რეჟიმი"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"ამოცანათა ზოლის გამყოფი"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"ამოცანათა ზოლის გადავსება"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ზემოთ/მარცხნივ გადატანა"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ქვემოთ/მარჯვნივ გადატანა"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{სხვა აპი}other{სხვა აპი}}"</string>
diff --git a/quickstep/res/values-kk/strings.xml b/quickstep/res/values-kk/strings.xml
index 193b606..4cdbfc4 100644
--- a/quickstep/res/values-kk/strings.xml
+++ b/quickstep/res/values-kk/strings.xml
@@ -139,8 +139,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Тапсырма жолағын үнемі көрсету"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Навигация режимін өзгерту"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Тапсырмалар жолағын бөлгіш"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"\"Тапсырмалар жолағы\" қосымша мәзірі"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Жоғары/солға жылжыту"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Төмен/оңға жылжыту"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{қосымша қолданба}other{қосымша қолданба}}"</string>
diff --git a/quickstep/res/values-km/strings.xml b/quickstep/res/values-km/strings.xml
index aadfd67..5cf1b92 100644
--- a/quickstep/res/values-km/strings.xml
+++ b/quickstep/res/values-km/strings.xml
@@ -139,8 +139,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"បង្ហាញរបារកិច្ចការជានិច្ច"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"ប្ដូរមុខងាររុករក"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"បន្ទាត់ខណ្ឌចែករបារកិច្ចការ"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"ម៉ឺនុយបន្ថែមរបារកិច្ចការ"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ផ្លាស់ទីទៅខាងលើ/ឆ្វេង"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ផ្លាស់ទីទៅខាងក្រោម/ស្ដាំ"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{កម្មវិធីច្រើនទៀត}other{កម្មវិធីច្រើនទៀត}}"</string>
diff --git a/quickstep/res/values-ky/strings.xml b/quickstep/res/values-ky/strings.xml
index 8eba2a5..faf5675 100644
--- a/quickstep/res/values-ky/strings.xml
+++ b/quickstep/res/values-ky/strings.xml
@@ -139,8 +139,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Такта ар дайым көрүнсүн"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Өтүү режимин өзгөртүү"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Тапшырмалар панелин бөлгүч"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"\"Тапшырмалар панели\" кошумча менюсу"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Жогорку/сол бурчка жылдыруу"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Төмөнкү/оң бурчка жылдыруу"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{колдонмо бар}other{колдонмо бар}}"</string>
diff --git a/quickstep/res/values-land/dimens.xml b/quickstep/res/values-land/dimens.xml
index 2239f8b..efdc7de 100644
--- a/quickstep/res/values-land/dimens.xml
+++ b/quickstep/res/values-land/dimens.xml
@@ -81,7 +81,7 @@
<dimen name="taskbar_contextual_button_suw_margin">48dp</dimen>
<dimen name="taskbar_contextual_button_suw_height">48dp</dimen>
<dimen name="taskbar_suw_frame">96dp</dimen>
- <dimen name="taskbar_suw_insets">24dp</dimen>
+ <dimen name="taskbar_suw_insets">48dp</dimen>
<!-- Keyboard Quick Switch -->
<dimen name="keyboard_quick_switch_taskview_width">217.6dp</dimen>
diff --git a/quickstep/res/values-lt/strings.xml b/quickstep/res/values-lt/strings.xml
index 1c20015..a95249b 100644
--- a/quickstep/res/values-lt/strings.xml
+++ b/quickstep/res/values-lt/strings.xml
@@ -139,8 +139,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Visada rodyti užduočių juostą"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Keisti naršymo režimą"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Užduočių juostos daliklis"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Užduočių juostos perpildymas"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Perkelti aukštyn, kairėn"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Perkelti žemyn, dešinėn"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{papildoma programa}one{papildoma programa}few{papildomos programos}many{papildomos programos}other{papildomų programų}}"</string>
diff --git a/quickstep/res/values-ml/strings.xml b/quickstep/res/values-ml/strings.xml
index bb5c014..c15a241 100644
--- a/quickstep/res/values-ml/strings.xml
+++ b/quickstep/res/values-ml/strings.xml
@@ -139,8 +139,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"ടാസ്ക്ബാർ എപ്പോഴും കാണിക്കൂ"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"നാവിഗേഷൻ മോഡ് മാറ്റുക"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"ടാസ്ക്ബാർ ഡിവൈഡർ"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"ടാസ്ക്ബാർ ഓവർഫ്ലോ"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"മുകളിലേക്കോ ഇടത്തേക്കോ നീക്കുക"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"താഴേക്കോ വലത്തേക്കോ നീക്കുക"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{കൂടുതൽ ആപ്പ്}other{കൂടുതൽ ആപ്പുകൾ}}"</string>
diff --git a/quickstep/res/values-ms/strings.xml b/quickstep/res/values-ms/strings.xml
index 9bdc973..a1f19a9 100644
--- a/quickstep/res/values-ms/strings.xml
+++ b/quickstep/res/values-ms/strings.xml
@@ -139,8 +139,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Papar Bar Tugas selalu"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Tukar mod navigasi"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Pembahagi Bar Tugas"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Limpahan Bar Tugas"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Alihkan ke atas/kiri"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Alihkan ke bawah/kanan"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{apl lagi}other{apl lagi}}"</string>
diff --git a/quickstep/res/values-ne/strings.xml b/quickstep/res/values-ne/strings.xml
index 4c9747f..a2d4d32 100644
--- a/quickstep/res/values-ne/strings.xml
+++ b/quickstep/res/values-ne/strings.xml
@@ -139,8 +139,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"टास्कबार सधैँ देखाउनुहोस्"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"नेभिगेसन मोड बदल्नुहोस्"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"टास्कबार डिभाइडर"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"टास्कबार ओभरफ्लो"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"सिरान/बायाँतिर सार्नुहोस्"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"फेद/दायाँतिर सार्नुहोस्"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{थप एप}other{थप एपहरू}}"</string>
diff --git a/quickstep/res/values-pt-rPT/strings.xml b/quickstep/res/values-pt-rPT/strings.xml
index 5ad32af..2167875 100644
--- a/quickstep/res/values-pt-rPT/strings.xml
+++ b/quickstep/res/values-pt-rPT/strings.xml
@@ -139,8 +139,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Ver sempre Barra de tarefas"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Alterar modo de navegação"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Divisor da Barra de tarefas"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Menu adicional da Barra de tarefas"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Mover para a parte superior esquerda"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mover para a part superior direita"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{outra app}other{outras apps}}"</string>
diff --git a/quickstep/res/values-pt/strings.xml b/quickstep/res/values-pt/strings.xml
index 28b3414..9309810 100644
--- a/quickstep/res/values-pt/strings.xml
+++ b/quickstep/res/values-pt/strings.xml
@@ -139,8 +139,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Sempre mostrar a Barra de tarefas"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Mudar o modo de navegação"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Separador da Barra de tarefas"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Barra de tarefas flutuante"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Mover para cima/para a esquerda"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mover para baixo/para a direita"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{outro app}one{outro app}other{outros apps}}"</string>
diff --git a/quickstep/res/values-ru/strings.xml b/quickstep/res/values-ru/strings.xml
index 20f579d..76c4e1f 100644
--- a/quickstep/res/values-ru/strings.xml
+++ b/quickstep/res/values-ru/strings.xml
@@ -139,8 +139,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Всегда показывать панель задач"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Изменить режим навигации"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Разделитель панели задач"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Дополнительное меню панели задач"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Переместить вверх или влево"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Переместить вниз или вправо"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{дополнительное приложение}one{дополнительное приложение}few{дополнительных приложения}many{дополнительных приложений}other{дополнительного приложения}}"</string>
diff --git a/quickstep/res/values-si/strings.xml b/quickstep/res/values-si/strings.xml
index dd9739c..0953b38 100644
--- a/quickstep/res/values-si/strings.xml
+++ b/quickstep/res/values-si/strings.xml
@@ -139,8 +139,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"සෑම විටම කාර්ය තීරුව පෙන්වන්න"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"සංචාලන ප්රකාරය වෙනස් කරන්න"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"කාර්ය තීරු බෙදනය"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"කාර්ය තීරුව පිටාර යාම"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ඉහළ/වම වෙත ගෙන යන්න"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"පහළ/දකුණ වෙත ගෙන යන්න"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{තව යෙදුම}one{තවත් යෙදුම්}other{තවත් යෙදුම්}}"</string>
diff --git a/quickstep/res/values-sl/strings.xml b/quickstep/res/values-sl/strings.xml
index 9394a5a..94de1e05 100644
--- a/quickstep/res/values-sl/strings.xml
+++ b/quickstep/res/values-sl/strings.xml
@@ -139,8 +139,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Stalen prikaz oprav. vrstice"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Spreminjanje načina navigacije"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Razdelilnik opravilne vrstice"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Oblaček opravilne vrstice z dodatnimi elementi"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Premakni na vrh/levo"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Premakni na dno/desno"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{dodatna aplikacija}one{dodatna aplikacija}two{dodatni aplikaciji}few{dodatne aplikacije}other{dodatnih aplikacij}}"</string>
diff --git a/quickstep/res/values-sr/strings.xml b/quickstep/res/values-sr/strings.xml
index a7760b9..d6e5d03 100644
--- a/quickstep/res/values-sr/strings.xml
+++ b/quickstep/res/values-sr/strings.xml
@@ -139,8 +139,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Увек приказуј траку задатака"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Промени режим навигације"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Разделник траке задатака"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Преклопна трака задатака"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Премести горе лево"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Премести доле десно"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{додатна апликација}one{додатна апликација}few{додатне апликације}other{додатних апликација}}"</string>
diff --git a/quickstep/res/values-sv/strings.xml b/quickstep/res/values-sv/strings.xml
index d4c3a69..bba98c6 100644
--- a/quickstep/res/values-sv/strings.xml
+++ b/quickstep/res/values-sv/strings.xml
@@ -139,8 +139,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Visa alltid aktivitetsfältet"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Ändra navigeringsläge"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Avdelare för aktivitetsfältet"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Fler alternativ för aktivitetsfältet"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Flytta högst upp/till vänster"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Flytta längst ned/till höger"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{app till}other{appar till}}"</string>
diff --git a/quickstep/res/values-sw600dp-land/dimens.xml b/quickstep/res/values-sw600dp-land/dimens.xml
index 5e9a177..49239aa 100644
--- a/quickstep/res/values-sw600dp-land/dimens.xml
+++ b/quickstep/res/values-sw600dp-land/dimens.xml
@@ -33,4 +33,6 @@
<!-- The bottom margin above the bottom row of tasks in grid only overview -->
<dimen name="overview_bottom_margin_grid_only">40dp</dimen>
+ <dimen name="taskbar_suw_insets">24dp</dimen>
+
</resources>
diff --git a/quickstep/res/values-te/strings.xml b/quickstep/res/values-te/strings.xml
index 86161b4..91ef846 100644
--- a/quickstep/res/values-te/strings.xml
+++ b/quickstep/res/values-te/strings.xml
@@ -139,8 +139,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"టాస్క్బార్ను నిరంతరం చూపండి"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"నావిగేషన్ మోడ్ను మార్చండి"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"టాస్క్బార్ డివైడర్"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"టాస్క్బార్ ఓవర్ఫ్లో"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ఎగువ/ఎడమ వైపునకు తరలించండి"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"దిగువ/కుడి వైపునకు తరలించండి"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{మరో యాప్}other{మరిన్ని యాప్లు}}"</string>
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index a64936d..18337d3 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -116,6 +116,7 @@
import androidx.annotation.Nullable;
import androidx.core.graphics.ColorUtils;
+import com.android.app.animation.Animations;
import com.android.internal.jank.Cuj;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.LauncherAnimationRunner.RemoteAnimationFactory;
@@ -574,23 +575,45 @@
} else {
List<View> viewsToAnimate = new ArrayList<>();
Workspace<?> workspace = mLauncher.getWorkspace();
- workspace.forEachVisiblePage(
- view -> viewsToAnimate.add(((CellLayout) view).getShortcutsAndWidgets()));
+ if (Flags.coordinateWorkspaceScale()) {
+ viewsToAnimate.add(workspace);
+ } else {
+ workspace.forEachVisiblePage(
+ view -> viewsToAnimate.add(((CellLayout) view).getShortcutsAndWidgets()));
+ }
+ Hotseat hotseat = mLauncher.getHotseat();
// Do not scale hotseat as a whole when taskbar is present, and scale QSB only if it's
// not inline.
if (mDeviceProfile.isTaskbarPresent) {
if (!mDeviceProfile.isQsbInline) {
- viewsToAnimate.add(mLauncher.getHotseat().getQsb());
+ viewsToAnimate.add(hotseat.getQsb());
}
} else {
- viewsToAnimate.add(mLauncher.getHotseat());
+ viewsToAnimate.add(hotseat);
}
viewsToAnimate.forEach(view -> {
view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- ObjectAnimator scaleAnim = ObjectAnimator.ofFloat(view, SCALE_PROPERTY, scales)
+ float[] scale = scales;
+ if (Flags.coordinateWorkspaceScale()) {
+ // Start the animation from the current value, instead of assuming the views are
+ // in their resting state, so interrupted animations merge seamlessly.
+ // TODO(b/367591368): ideally these animations would be refactored to be
+ // controlled centrally so each instances doesn't need to care about this
+ // coordination.
+ scale = new float[]{view.getScaleX(), scales[1]};
+
+ // Cancel any ongoing animations. This is necessary to avoid a conflict between
+ // e.g. the unfinished animation triggered when closing an app back to Home and
+ // this animation caused by a launch.
+ Animations.Companion.cancelOngoingAnimation(view);
+ // Make sure to cache the current animation, so it can be properly interrupted.
+ Animations.Companion.setOngoingAnimation(view, launcherAnimator);
+ }
+
+ ObjectAnimator scaleAnim = ObjectAnimator.ofFloat(view, SCALE_PROPERTY, scale)
.setDuration(CONTENT_SCALE_DURATION);
scaleAnim.setInterpolator(DECELERATE_1_5);
launcherAnimator.play(scaleAnim);
@@ -600,6 +623,11 @@
viewsToAnimate.forEach(view -> {
SCALE_PROPERTY.set(view, 1f);
view.setLayerType(View.LAYER_TYPE_NONE, null);
+
+ if (Flags.coordinateWorkspaceScale()) {
+ // Reset the cached animation.
+ Animations.Companion.setOngoingAnimation(view, null /* animation */);
+ }
});
mLauncher.resumeExpensiveViewUpdates();
};
@@ -1353,8 +1381,13 @@
? null
: mLauncher.getTaskbarUIController().findMatchingView(launcherView),
true /* hideOriginal */, targetRect, false /* isOpening */);
- isInHotseat = launcherView.getTag() instanceof ItemInfo
- && ((ItemInfo) launcherView.getTag()).isInHotseat();
+ if (launcherView.getTag() instanceof ItemInfo itemInfo) {
+ isInHotseat = itemInfo.isInHotseat();
+ if (isInHotseat) {
+ int dx = mLauncher.getHotseatItemTranslationX(itemInfo);
+ targetRect.offset(dx, 0);
+ }
+ }
} else {
targetRect.set(getDefaultWindowTargetRect());
}
diff --git a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
index 7a8b58e..32fda48 100644
--- a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
@@ -31,6 +31,7 @@
import android.view.accessibility.AccessibilityManager;
import androidx.annotation.ColorInt;
+import androidx.annotation.VisibleForTesting;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -253,4 +254,9 @@
public View getFocusedChild() {
return null;
}
+
+ @VisibleForTesting
+ public DividerType getDividerType() {
+ return mDividerType;
+ }
}
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
index f7da34a..8b3a032 100644
--- a/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
@@ -20,6 +20,7 @@
import android.os.RemoteException
import android.util.Log
import android.view.SurfaceControl
+import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.IRemoteTransitionFinishedCallback
import android.window.RemoteTransition
import android.window.RemoteTransitionStub
@@ -30,6 +31,7 @@
import com.android.quickstep.SystemUiProxy
import com.android.quickstep.TaskViewUtils
import com.android.quickstep.views.DesktopTaskView
+import com.android.window.flags.Flags
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
import java.util.function.Consumer
@@ -38,14 +40,14 @@
private val stateManager: StateManager<*, *>,
private val systemUiProxy: SystemUiProxy,
private val appThread: IApplicationThread,
- private val depthController: DepthController?
+ private val depthController: DepthController?,
) {
/** Launch desktop tasks from recents view */
fun launchDesktopFromRecents(
desktopTaskView: DesktopTaskView,
animated: Boolean,
- callback: Consumer<Boolean>? = null
+ callback: Consumer<Boolean>? = null,
) {
val animRunner =
RemoteDesktopLaunchTransitionRunner(
@@ -53,7 +55,7 @@
animated,
stateManager,
depthController,
- callback
+ callback,
)
val transition = RemoteTransition(animRunner, appThread, "RecentsToDesktop")
systemUiProxy.showDesktopApps(desktopTaskView.display.displayId, transition)
@@ -69,14 +71,14 @@
private val animated: Boolean,
private val stateManager: StateManager<*, *>,
private val depthController: DepthController?,
- private val successCallback: Consumer<Boolean>?
+ private val successCallback: Consumer<Boolean>?,
) : RemoteTransitionStub() {
override fun startAnimation(
token: IBinder,
info: TransitionInfo,
t: SurfaceControl.Transaction,
- finishCallback: IRemoteTransitionFinishedCallback
+ finishCallback: IRemoteTransitionFinishedCallback,
) {
val errorHandlingFinishCallback = Runnable {
try {
@@ -86,6 +88,9 @@
}
}
+ if (Flags.enableDesktopWindowingPersistence()) {
+ handleAnimationAfterReboot(info)
+ }
MAIN_EXECUTOR.execute {
val animator =
TaskViewUtils.composeRecentsDesktopLaunchAnimator(
@@ -93,7 +98,7 @@
stateManager,
depthController,
info,
- t
+ t,
) {
errorHandlingFinishCallback.run()
successCallback?.accept(true)
@@ -104,6 +109,26 @@
animator.start()
}
}
+
+ /**
+ * Upon reboot the start bounds of a task is set to fullscreen with the recents transition.
+ * Check this case and set the start bounds to the end bounds so that the window doesn't
+ * jump from start bounds to end bounds during the animation. Tasks in desktop cannot
+ * normally have top bound as 0 due to status bar so this is a good indicator to identify
+ * reboot case.
+ */
+ private fun handleAnimationAfterReboot(info: TransitionInfo) {
+ info.changes.forEach { change ->
+ if (
+ change.mode == TRANSIT_TO_FRONT &&
+ change.taskInfo?.isFreeform == true &&
+ change.startAbsBounds.top == 0 &&
+ change.startAbsBounds.left == 0
+ ) {
+ change.setStartAbsBounds(change.endAbsBounds)
+ }
+ }
+ }
}
companion object {
diff --git a/quickstep/src/com/android/launcher3/proxy/ProxyActivityStarter.java b/quickstep/src/com/android/launcher3/proxy/ProxyActivityStarter.java
index 212a5ff..4293ccd 100644
--- a/quickstep/src/com/android/launcher3/proxy/ProxyActivityStarter.java
+++ b/quickstep/src/com/android/launcher3/proxy/ProxyActivityStarter.java
@@ -61,6 +61,7 @@
}
} catch (NullPointerException | ActivityNotFoundException | SecurityException
| SendIntentException e) {
+ Log.w(TAG, "Proxy activity starter could not start activity: ", e);
mParams.deliverResult(this, RESULT_CANCELED, null);
}
finishAndRemoveTask();
diff --git a/quickstep/src/com/android/launcher3/taskbar/BarsLocationAnimatorHelper.kt b/quickstep/src/com/android/launcher3/taskbar/BarsLocationAnimatorHelper.kt
new file mode 100644
index 0000000..b8060e1
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/BarsLocationAnimatorHelper.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar
+
+import android.animation.Animator
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
+import android.animation.ValueAnimator
+import android.view.View
+import androidx.dynamicanimation.animation.SpringForce
+import com.android.app.animation.Interpolators
+import com.android.launcher3.LauncherAnimUtils
+import com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X
+import com.android.launcher3.anim.SpringAnimationBuilder
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation
+
+/** Animator helper that creates bars animators. */
+object BarsLocationAnimatorHelper {
+
+ private const val FADE_OUT_ANIM_ALPHA_DURATION_MS: Long = 50L
+ private const val FADE_OUT_ANIM_ALPHA_DELAY_MS: Long = 50L
+ private const val FADE_OUT_ANIM_POSITION_DURATION_MS: Long = 100L
+ private const val FADE_IN_ANIM_ALPHA_DURATION_MS: Long = 100L
+
+ // Use STIFFNESS_MEDIUMLOW which is not defined in the API constants
+ private const val FADE_IN_ANIM_POSITION_SPRING_STIFFNESS: Float = 400f
+
+ // During fade out animation we shift the bubble bar 1/80th of the screen width
+ private const val FADE_OUT_ANIM_POSITION_SHIFT: Float = 1 / 80f
+
+ // During fade in animation we shift the bubble bar 1/60th of the screen width
+ private const val FADE_IN_ANIM_POSITION_SHIFT: Float = 1 / 60f
+
+ private val View.screenWidth: Int
+ get() = resources.displayMetrics.widthPixels
+
+ private val View.outShift: Float
+ get() = screenWidth * FADE_OUT_ANIM_POSITION_SHIFT
+
+ private val View.inShiftX: Float
+ get() = screenWidth * FADE_IN_ANIM_POSITION_SHIFT
+
+ /**
+ * Creates out animation for targetView that animates it finalTx and plays targetViewAlphaAnim
+ * to its final value.
+ */
+ private fun createLocationOutAnimator(
+ finalTx: Float,
+ targetViewAlphaAnim: ObjectAnimator,
+ targetView: View,
+ ): Animator {
+ val positionAnim =
+ ObjectAnimator.ofFloat(targetView, VIEW_TRANSLATE_X, finalTx)
+ .setDuration(FADE_OUT_ANIM_POSITION_DURATION_MS)
+ positionAnim.interpolator = Interpolators.EMPHASIZED_ACCELERATE
+
+ targetViewAlphaAnim.setDuration(FADE_OUT_ANIM_ALPHA_DURATION_MS)
+ targetViewAlphaAnim.startDelay = FADE_OUT_ANIM_ALPHA_DELAY_MS
+
+ val animatorSet = AnimatorSet()
+ animatorSet.playTogether(positionAnim, targetViewAlphaAnim)
+ return animatorSet
+ }
+
+ /**
+ * Creates in animation for targetView that animates it from startTx to finalTx and plays
+ * targetViewAlphaAnim to its final value.
+ */
+ private fun createLocationInAnimator(
+ startTx: Float,
+ finalTx: Float,
+ targetViewAlphaAnim: ObjectAnimator,
+ targetView: View,
+ ): Animator {
+ targetViewAlphaAnim.setDuration(FADE_IN_ANIM_ALPHA_DURATION_MS)
+ val positionAnim: ValueAnimator =
+ SpringAnimationBuilder(targetView.context)
+ .setStartValue(startTx)
+ .setEndValue(finalTx)
+ .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+ .setStiffness(FADE_IN_ANIM_POSITION_SPRING_STIFFNESS)
+ .build(targetView, VIEW_TRANSLATE_X)
+ val animatorSet = AnimatorSet()
+ animatorSet.playTogether(positionAnim, targetViewAlphaAnim)
+ return animatorSet
+ }
+
+ /** Creates an animator for the bubble bar view in part. */
+ @JvmStatic
+ fun getBubbleBarLocationInAnimator(
+ newLocation: BubbleBarLocation,
+ currentLocation: BubbleBarLocation,
+ distanceFromOtherSide: Float,
+ targetViewAlphaAnim: ObjectAnimator,
+ bubbleBarView: View,
+ ): Animator {
+ val shift: Float = bubbleBarView.outShift
+
+ val onLeft = newLocation.isOnLeft(bubbleBarView.isLayoutRtl)
+ val startTx: Float
+ val finalTx =
+ if (newLocation == currentLocation) {
+ // Animated location matches layout location.
+ 0f
+ } else {
+ // We are animating in to a transient location, need to move the bar
+ // accordingly.
+ distanceFromOtherSide * (if (onLeft) -1 else 1)
+ }
+ startTx =
+ if (onLeft) {
+ // Bar will be shown on the left side. Start point is shifted right.
+ finalTx + shift
+ } else {
+ // Bar will be shown on the right side. Start point is shifted left.
+ finalTx - shift
+ }
+ return createLocationInAnimator(startTx, finalTx, targetViewAlphaAnim, bubbleBarView)
+ }
+
+ /** Creates an animator for the bubble bar view out part. */
+ @JvmStatic
+ fun getBubbleBarLocationOutAnimator(
+ bubbleBarView: View,
+ bubbleBarLocation: BubbleBarLocation,
+ targetViewAlphaAnim: ObjectAnimator,
+ ): Animator {
+ val onLeft = bubbleBarLocation.isOnLeft(bubbleBarView.isLayoutRtl)
+ val shift = bubbleBarView.outShift
+ val finalTx = bubbleBarView.translationX + (if (onLeft) -shift else shift)
+ return this.createLocationOutAnimator(finalTx, targetViewAlphaAnim, bubbleBarView)
+ }
+
+ /** Creates a teleport animator for the navigation buttons view. */
+ @JvmStatic
+ fun getTeleportAnimatorForNavButtons(
+ location: BubbleBarLocation,
+ navButtonsView: View,
+ navBarTargetTranslationX: Float,
+ ): Animator {
+ val outShift: Float = navButtonsView.outShift
+ val isNavBarOnRight: Boolean = location.isOnLeft(navButtonsView.isLayoutRtl)
+ val finalOutTx =
+ navButtonsView.translationX + (if (isNavBarOnRight) outShift else -outShift)
+ val fadeout: Animator =
+ createLocationOutAnimator(
+ finalOutTx,
+ ObjectAnimator.ofFloat(navButtonsView, LauncherAnimUtils.VIEW_ALPHA, 0f),
+ navButtonsView,
+ )
+ val inShift: Float = navButtonsView.inShiftX
+ val inStartX = navBarTargetTranslationX + (if (isNavBarOnRight) -inShift else inShift)
+ val fadeIn: Animator =
+ createLocationInAnimator(
+ inStartX,
+ navBarTargetTranslationX,
+ ObjectAnimator.ofFloat(navButtonsView, LauncherAnimUtils.VIEW_ALPHA, 1f),
+ navButtonsView,
+ )
+ val teleportAnimator = AnimatorSet()
+ teleportAnimator.play(fadeout).before(fadeIn)
+ return teleportAnimator
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
index ea432f3..9912c6c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
@@ -17,13 +17,17 @@
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
+import android.view.MotionEvent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import com.android.launcher3.Flags;
import com.android.launcher3.R;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
+import com.android.launcher3.taskbar.overlay.TaskbarOverlayDragLayer;
+import com.android.launcher3.util.TouchController;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.util.DesktopTask;
import com.android.quickstep.util.GroupTask;
@@ -36,6 +40,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -43,7 +48,7 @@
* Handles initialization of the {@link KeyboardQuickSwitchViewController}.
*/
public final class KeyboardQuickSwitchController implements
- TaskbarControllers.LoggableTaskbarController {
+ TaskbarControllers.LoggableTaskbarController, TouchController {
@VisibleForTesting
public static final int MAX_TASKS = 6;
@@ -64,6 +69,7 @@
private TaskbarControllers mControllers;
@Nullable private KeyboardQuickSwitchViewController mQuickSwitchViewController;
+ @Nullable private TaskbarOverlayContext mOverlayContext;
private boolean mHasDesktopTask = false;
private boolean mWasDesktopTaskFilteredOut = false;
@@ -95,7 +101,21 @@
openQuickSwitchView(-1);
}
+ /**
+ * Opens the view with a filtered list of tasks.
+ * @param taskIdsToExclude A list of tasks to exclude in the opened view.
+ */
+ void openQuickSwitchView(@NonNull Set<Integer> taskIdsToExclude) {
+ openQuickSwitchView(-1, taskIdsToExclude, true);
+ }
+
private void openQuickSwitchView(int currentFocusedIndex) {
+ openQuickSwitchView(currentFocusedIndex, Collections.emptySet(), false);
+ }
+
+ private void openQuickSwitchView(int currentFocusedIndex,
+ @NonNull Set<Integer> taskIdsToExclude,
+ boolean wasOpenedFromTaskbar) {
if (mQuickSwitchViewController != null) {
if (!mQuickSwitchViewController.isCloseAnimationRunning()) {
return;
@@ -103,21 +123,25 @@
// Allow the KQS to be reopened during the close animation to make it more responsive
closeQuickSwitchView(false);
}
- TaskbarOverlayContext overlayContext =
- mControllers.taskbarOverlayController.requestWindow();
+ mOverlayContext = mControllers.taskbarOverlayController.requestWindow();
+ if (Flags.taskbarOverflow()) {
+ mOverlayContext.getDragLayer().addTouchController(this);
+ }
KeyboardQuickSwitchView keyboardQuickSwitchView =
- (KeyboardQuickSwitchView) overlayContext.getLayoutInflater()
+ (KeyboardQuickSwitchView) mOverlayContext.getLayoutInflater()
.inflate(
R.layout.keyboard_quick_switch_view,
- overlayContext.getDragLayer(),
+ mOverlayContext.getDragLayer(),
/* attachToRoot= */ false);
mQuickSwitchViewController = new KeyboardQuickSwitchViewController(
- mControllers, overlayContext, keyboardQuickSwitchView, mControllerCallbacks);
+ mControllers, mOverlayContext, keyboardQuickSwitchView, mControllerCallbacks);
final boolean onDesktop =
mControllers.taskbarDesktopModeController.getAreDesktopTasksVisible();
- if (mModel.isTaskListValid(mTaskListChangeId)) {
+ // TODO(b/368119679) For now we will re-process the task list every time, but this can be
+ // optimized if we have the same set of task ids to exclude.
+ if (mModel.isTaskListValid(mTaskListChangeId) && !Flags.taskbarOverflow()) {
// When we are opening the KQS with no focus override, check if the first task is
// running. If not, focus that first task.
mQuickSwitchViewController.openQuickSwitchView(
@@ -128,7 +152,8 @@
? 0 : currentFocusedIndex,
onDesktop,
mHasDesktopTask,
- mWasDesktopTaskFilteredOut);
+ mWasDesktopTaskFilteredOut,
+ wasOpenedFromTaskbar);
return;
}
@@ -136,9 +161,9 @@
mHasDesktopTask = false;
mWasDesktopTaskFilteredOut = false;
if (onDesktop) {
- processLoadedTasksOnDesktop(tasks);
+ processLoadedTasksOnDesktop(tasks, taskIdsToExclude);
} else {
- processLoadedTasks(tasks);
+ processLoadedTasks(tasks, taskIdsToExclude);
}
// Check if the first task is running after the recents model has updated so that we use
// the correct index.
@@ -150,15 +175,21 @@
? 0 : currentFocusedIndex,
onDesktop,
mHasDesktopTask,
- mWasDesktopTaskFilteredOut);
+ mWasDesktopTaskFilteredOut,
+ wasOpenedFromTaskbar);
});
}
- private void processLoadedTasks(List<GroupTask> tasks) {
+ private boolean shouldExcludeTask(GroupTask task, Set<Integer> taskIdsToExclude) {
+ return Flags.taskbarOverflow() && taskIdsToExclude.contains(task.task1.key.id);
+ }
+
+ private void processLoadedTasks(List<GroupTask> tasks, Set<Integer> taskIdsToExclude) {
// Only store MAX_TASK tasks, from most to least recent
Collections.reverse(tasks);
mTasks = tasks.stream()
- .filter(task -> !(task instanceof DesktopTask))
+ .filter(task -> !(task instanceof DesktopTask)
+ && !shouldExcludeTask(task, taskIdsToExclude))
.limit(MAX_TASKS)
.collect(Collectors.toList());
@@ -176,12 +207,15 @@
tasks.size() - (mWasDesktopTaskFilteredOut ? 1 : 0) - MAX_TASKS);
}
- private void processLoadedTasksOnDesktop(List<GroupTask> tasks) {
+ 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);
if (desktopTask != null) {
- mTasks = desktopTask.tasks.stream().map(GroupTask::new).collect(Collectors.toList());
+ mTasks = desktopTask.tasks.stream()
+ .map(GroupTask::new)
+ .filter(task -> !shouldExcludeTask(task, taskIdsToExclude))
+ .collect(Collectors.toList());
// All other tasks, apart from the grouped desktop task, are hidden
mNumHiddenTasks = Math.max(0, tasks.size() - 1);
} else {
@@ -220,6 +254,27 @@
? -1 : mQuickSwitchViewController.launchFocusedTask();
}
+ @Override
+ public boolean onControllerTouchEvent(MotionEvent ev) {
+ return false;
+ }
+
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ if (mQuickSwitchViewController == null
+ || mOverlayContext == null
+ || !Flags.taskbarOverflow()) {
+ return false;
+ }
+
+ TaskbarOverlayDragLayer dragLayer = mOverlayContext.getDragLayer();
+ if (ev.getAction() == MotionEvent.ACTION_DOWN
+ && !mQuickSwitchViewController.isEventOverKeyboardQuickSwitch(dragLayer, ev)) {
+ closeQuickSwitchView(true);
+ }
+ return false;
+ }
+
void onDestroy() {
if (mQuickSwitchViewController != null) {
mQuickSwitchViewController.onDestroy();
@@ -279,6 +334,11 @@
}
void onCloseComplete() {
+ if (Flags.taskbarOverflow() && mOverlayContext != null) {
+ mOverlayContext.getDragLayer()
+ .removeTouchController(KeyboardQuickSwitchController.this);
+ }
+ mOverlayContext = null;
mQuickSwitchViewController = null;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
index 40e77e2..1c8a094 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
@@ -19,7 +19,9 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.view.Gravity;
import android.view.KeyEvent;
+import android.view.MotionEvent;
import android.view.animation.AnimationUtils;
import android.window.RemoteTransition;
@@ -30,6 +32,8 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
+import com.android.launcher3.taskbar.overlay.TaskbarOverlayDragLayer;
+import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.SlideInRemoteTransition;
@@ -83,7 +87,9 @@
int currentFocusIndexOverride,
boolean onDesktop,
boolean hasDesktopTask,
- boolean wasDesktopTaskFilteredOut) {
+ boolean wasDesktopTaskFilteredOut,
+ boolean wasOpenedFromTaskbar) {
+ positionView(wasOpenedFromTaskbar);
mOverlayContext.getDragLayer().addView(mKeyboardQuickSwitchView);
mOnDesktop = onDesktop;
mWasDesktopTaskFilteredOut = wasDesktopTaskFilteredOut;
@@ -98,6 +104,19 @@
/* useDesktopTaskView= */ !onDesktop && hasDesktopTask);
}
+ protected void positionView(boolean wasOpenedFromTaskbar) {
+ if (!wasOpenedFromTaskbar) {
+ // Keep the default positioning.
+ return;
+ }
+
+ BaseDragLayer.LayoutParams lp = new BaseDragLayer.LayoutParams(
+ mKeyboardQuickSwitchView.getLayoutParams());
+ lp.width = BaseDragLayer.LayoutParams.WRAP_CONTENT;
+ lp.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
+ mKeyboardQuickSwitchView.setLayoutParams(lp);
+ }
+
boolean isCloseAnimationRunning() {
return mCloseAnimation != null;
}
@@ -219,6 +238,13 @@
pw.println(prefix + "\tmWasDesktopTaskFilteredOut=" + mWasDesktopTaskFilteredOut);
}
+ /**
+ * @return True if the MotionEvent is over the {@link KeyboardQuickSwitchView}.
+ */
+ protected boolean isEventOverKeyboardQuickSwitch(TaskbarOverlayDragLayer dl, MotionEvent ev) {
+ return dl.isEventOverView(mKeyboardQuickSwitchView, ev);
+ }
+
class ViewCallbacks {
boolean onKeyUp(int keyCode, KeyEvent event, boolean isRTL, boolean allowTraversal) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 477f90c..10ff9ac 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -50,6 +50,7 @@
import com.android.quickstep.util.TISBindHelper;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import java.io.PrintWriter;
@@ -469,6 +470,18 @@
}
@Override
+ public void onBubbleBarLocationAnimated(BubbleBarLocation location) {
+ mTaskbarLauncherStateController.onBubbleBarLocationChanged(location, /* animate = */ true);
+ mLauncher.setBubbleBarLocation(location);
+ }
+
+ @Override
+ public void onBubbleBarLocationUpdated(BubbleBarLocation location) {
+ mTaskbarLauncherStateController.onBubbleBarLocationChanged(location, /* animate = */ false);
+ mLauncher.setBubbleBarLocation(location);
+ }
+
+ @Override
public void onSwipeToUnstashTaskbar() {
// Once taskbar is unstashed, the user cannot return back to the overlay. We can
// clear it here to set the expected state once the user goes home.
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 2ac5793..895535e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -23,6 +23,7 @@
import static com.android.launcher3.LauncherAnimUtils.ROTATION_DRAWABLE_PERCENT;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
+import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
import static com.android.launcher3.taskbar.LauncherTaskbarUIController.SYSUI_SURFACE_PROGRESS_INDEX;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y;
@@ -48,7 +49,9 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SHORTCUT_HELPER_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
+import static com.android.wm.shell.Flags.enableBubbleBarInPersistentTaskBar;
+import android.animation.Animator;
import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.annotation.DrawableRes;
@@ -175,6 +178,9 @@
/** Color to use for navigation bar buttons, if they are on on a Taskbar surface background. */
private final int mOnBackgroundIconColor;
+ private @Nullable Animator mNavBarLocationAnimator;
+ private @Nullable BubbleBarLocation mBubbleBarTargetLocation;
+
private final AnimatedFloat mTaskbarNavButtonTranslationY = new AnimatedFloat(
this::updateNavButtonTranslationY);
private final AnimatedFloat mTaskbarNavButtonTranslationYForInAppDisplay = new AnimatedFloat(
@@ -1174,14 +1180,30 @@
/** Adjusts navigation buttons layout accordingly to the bubble bar position. */
@Override
public void onBubbleBarLocationUpdated(BubbleBarLocation location) {
+ cancelExistingNavBarAnimation();
+ mBubbleBarTargetLocation = location;
mNavButtonContainer.setTranslationX(getNavBarTranslationX(location));
+ mNavButtonContainer.setAlpha(1);
}
/** Animates navigation buttons accordingly to the bubble bar position. */
@Override
public void onBubbleBarLocationAnimated(BubbleBarLocation location) {
- // TODO(b/346381754) add the teleport animation similarly to the bubble bar
- mNavButtonContainer.setTranslationX(getNavBarTranslationX(location));
+ cancelExistingNavBarAnimation();
+ mBubbleBarTargetLocation = location;
+ int finalX = getNavBarTranslationX(location);
+ Animator teleportAnimator = BarsLocationAnimatorHelper
+ .getTeleportAnimatorForNavButtons(location, mNavButtonContainer, finalX);
+ teleportAnimator.addListener(forEndCallback(() -> mNavBarLocationAnimator = null));
+ mNavBarLocationAnimator = teleportAnimator;
+ mNavBarLocationAnimator.start();
+ }
+
+ private void cancelExistingNavBarAnimation() {
+ if (mNavBarLocationAnimator != null) {
+ mNavBarLocationAnimator.cancel();
+ mNavBarLocationAnimator = null;
+ }
}
private int getNavBarTranslationX(BubbleBarLocation location) {
@@ -1218,12 +1240,16 @@
}
/** Adjusts the navigation buttons layout position according to the bubble bar location. */
- public void onTaskbarLayoutChange() {
- if (com.android.wm.shell.Flags.enableBubbleBarInPersistentTaskBar()
+ public void onTaskbarLayoutChanged() {
+ if (mControllers.taskbarViewController.getIconLayoutBounds().isEmpty()) return;
+ if (enableBubbleBarInPersistentTaskBar()
&& mControllers.bubbleControllers.isPresent()) {
- BubbleBarLocation bubblesLocation = mControllers.bubbleControllers.get()
- .bubbleBarViewController.getBubbleBarLocation();
- onBubbleBarLocationUpdated(bubblesLocation);
+ if (mBubbleBarTargetLocation == null) {
+ // only set bubble bar location if it was not set before, e.g. at device boot
+ mBubbleBarTargetLocation = mControllers.bubbleControllers.get()
+ .bubbleBarViewController.getBubbleBarLocation();
+ }
+ onBubbleBarLocationUpdated(mBubbleBarTargetLocation);
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index 56fd2bb..5974675 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -26,6 +26,7 @@
import com.android.launcher3.taskbar.bubbles.BubbleControllers;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayController;
import com.android.systemui.shared.rotation.RotationButtonController;
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -219,7 +220,11 @@
uiController = newUiController;
uiController.init(this);
uiController.updateStateForSysuiFlags(mSharedState.sysuiStateFlags);
-
+ bubbleControllers.ifPresent(bubbleControllers -> {
+ BubbleBarLocation location =
+ bubbleControllers.bubbleBarViewController.getBubbleBarLocation();
+ uiController.onBubbleBarLocationUpdated(location);
+ });
// Notify that the ui controller has changed
navbarButtonsViewController.onUiControllerChanged();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 876221b..8f722f4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -19,11 +19,15 @@
import static com.android.launcher3.Hotseat.ALPHA_CHANNEL_TASKBAR_ALIGNMENT;
import static com.android.launcher3.Hotseat.ALPHA_CHANNEL_TASKBAR_STASH;
import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
+import static com.android.launcher3.Utilities.isRtl;
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_OVERVIEW;
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE;
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_FOR_BUBBLES;
+import static com.android.launcher3.taskbar.TaskbarStashController.UNLOCK_TRANSITION_MEMOIZATION_MS;
import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_HOME;
+import static com.android.launcher3.taskbar.bubbles.BubbleBarView.FADE_IN_ANIM_ALPHA_DURATION_MS;
+import static com.android.launcher3.taskbar.bubbles.BubbleBarView.FADE_OUT_ANIM_POSITION_DURATION_MS;
import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_AWAKE;
@@ -42,8 +46,10 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Hotseat;
import com.android.launcher3.Hotseat.HotseatQsbAlphaId;
import com.android.launcher3.LauncherState;
import com.android.launcher3.QuickstepTransitionManager;
@@ -52,6 +58,7 @@
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.BubbleLauncherState;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
@@ -62,6 +69,7 @@
import com.android.systemui.animation.ViewRootSync;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
import java.io.PrintWriter;
import java.util.HashMap;
@@ -151,6 +159,7 @@
private AnimatedFloat mTaskbarAlpha;
private AnimatedFloat mTaskbarCornerRoundness;
private MultiProperty mTaskbarAlphaForHome;
+ private @Nullable Animator mHotseatTranslationXAnimation;
private QuickstepLauncher mLauncher;
private boolean mIsDestroyed = false;
@@ -160,7 +169,12 @@
private boolean mSkipNextRecentsAnimEnd;
// Time when FLAG_TASKBAR_HIDDEN was last cleared, SystemClock.elapsedRealtime (milliseconds).
- private long mLastUnlockTimeMs = 0;
+ private long mLastRemoveTaskbarHiddenTimeMs = 0;
+ /**
+ * Time when FLAG_DEVICE_LOCKED was last cleared, plus
+ * {@link TaskbarStashController#UNLOCK_TRANSITION_MEMOIZATION_MS}
+ */
+ private long mLastUnlockTransitionTimeout;
private @Nullable TaskBarRecentsAnimationListener mTaskBarRecentsAnimationListener;
@@ -168,6 +182,8 @@
private boolean mShouldDelayLauncherStateAnim;
+ private @Nullable BubbleBarLocation mBubbleBarLocation;
+
// We skip any view synchronizations during init/destroy.
private boolean mCanSyncViews;
@@ -185,6 +201,8 @@
mIsQsbInline = dp.isQsbInline;
TaskbarLauncherStateController.this.updateIconAlphaForHome(
mTaskbarAlphaForHome.getValue(), ALPHA_CHANNEL_TASKBAR_ALIGNMENT);
+ TaskbarLauncherStateController.this.onBubbleBarLocationChanged(
+ mBubbleBarLocation, /* animate = */ false);
}
};
@@ -462,8 +480,12 @@
boolean onOverview = mLauncherState == LauncherState.OVERVIEW;
boolean hotseatIconsVisible = isInLauncher && mLauncherState.areElementsVisible(
mLauncher, HOTSEAT_ICONS);
- controllers.bubbleStashController.setBubblesShowingOnHome(hotseatIconsVisible);
- controllers.bubbleStashController.setBubblesShowingOnOverview(onOverview);
+ BubbleLauncherState state = onOverview
+ ? BubbleLauncherState.OVERVIEW
+ : hotseatIconsVisible
+ ? BubbleLauncherState.HOME
+ : BubbleLauncherState.IN_APP;
+ controllers.bubbleStashController.setLauncherState(state);
});
TaskbarStashController stashController = mControllers.taskbarStashController;
@@ -524,7 +546,7 @@
if (hasAnyFlag(changedFlags, FLAG_TASKBAR_HIDDEN) && !hasAnyFlag(FLAG_TASKBAR_HIDDEN)) {
// Take note of the current time, as the taskbar is made visible again.
- mLastUnlockTimeMs = SystemClock.elapsedRealtime();
+ mLastRemoveTaskbarHiddenTimeMs = SystemClock.elapsedRealtime();
}
boolean isHidden = hasAnyFlag(FLAG_TASKBAR_HIDDEN);
@@ -550,7 +572,8 @@
// with a fingerprint reader. This should only be done when the device was woken
// up via fingerprint reader, however since this information is currently not
// available, opting to always delay the fade-in a bit.
- long durationSinceLastUnlockMs = SystemClock.elapsedRealtime() - mLastUnlockTimeMs;
+ long durationSinceLastUnlockMs = SystemClock.elapsedRealtime()
+ - mLastRemoveTaskbarHiddenTimeMs;
taskbarVisibility.setStartDelay(
Math.max(0, TASKBAR_SHOW_DELAY_MS - durationSinceLastUnlockMs));
}
@@ -620,6 +643,15 @@
boolean isUnlockTransition =
hasAnyFlag(changedFlags, FLAG_DEVICE_LOCKED) && !hasAnyFlag(FLAG_DEVICE_LOCKED);
if (isUnlockTransition) {
+ // the launcher might not be resumed at the time the device is considered
+ // unlocked (when the keyguard goes away), but possibly shortly afterwards.
+ // To play the unlock transition at the time the unstash animation actually happens,
+ // this memoizes the state transition for UNLOCK_TRANSITION_MEMOIZATION_MS.
+ mLastUnlockTransitionTimeout =
+ SystemClock.elapsedRealtime() + UNLOCK_TRANSITION_MEMOIZATION_MS;
+ }
+ boolean isInUnlockTimeout = SystemClock.elapsedRealtime() < mLastUnlockTransitionTimeout;
+ if (isUnlockTransition || isInUnlockTimeout) {
// When transitioning to unlocked, ensure the hotseat is fully visible from the
// beginning. The hotseat itself is animated by LauncherUnlockAnimationController.
mIconAlignment.cancelAnimation();
@@ -833,6 +865,74 @@
}
}
+ /** Updates launcher home screen appearance accordingly to the bubble bar location. */
+ public void onBubbleBarLocationChanged(BubbleBarLocation location, boolean animate) {
+ DeviceProfile deviceProfile = mLauncher.getDeviceProfile();
+ if (mBubbleBarLocation == location) return;
+ mBubbleBarLocation = location;
+ if (!deviceProfile.shouldAdjustHotseatOnBubblesLocationUpdate(
+ mControllers.taskbarActivityContext)) {
+ return;
+ }
+ int targetX = 0;
+ if (mBubbleBarLocation != null) {
+ boolean isBubblesOnLeft = location.isOnLeft(isRtl(mLauncher.getResources()));
+ targetX = deviceProfile.getHotseatTranslationXForBubbleBar(/* isNavbarOnRight= */
+ isBubblesOnLeft);
+ }
+ updateHotseatAndQsbTranslationX(targetX, animate);
+ }
+
+ private void updateHotseatAndQsbTranslationX(float targetValue, boolean animate) {
+ // cancel existing animation
+ if (mHotseatTranslationXAnimation != null) {
+ mHotseatTranslationXAnimation.cancel();
+ }
+ Runnable alignTaskbar = new Runnable() {
+ @Override
+ public void run() {
+ // We only need to align the task bar when on launcher home screen
+ if (mControllers.taskbarStashController.isOnHome()) {
+ DeviceProfile dp = mLauncher.getDeviceProfile();
+ mControllers.taskbarViewController
+ .setLauncherIconAlignment(/* alignmentRatio = */ 1, dp);
+ }
+ }
+ };
+ Hotseat hotseat = mLauncher.getHotseat();
+ AnimatorSet translationXAnimation = new AnimatorSet();
+ MultiProperty iconsTranslationX = hotseat.getIconsTranslationX(
+ Hotseat.ICONS_TRANSLATION_X_NAV_BAR_ALIGNMENT);
+ if (animate) {
+ translationXAnimation.playTogether(iconsTranslationX.animateToValue(targetValue));
+ } else {
+ iconsTranslationX.setValue(targetValue);
+ }
+ float qsbTargetX = 0;
+ if (mIsQsbInline) {
+ qsbTargetX = targetValue;
+ }
+ MultiProperty qsbTranslationX = hotseat.getQsbTranslationX();
+ if (qsbTranslationX != null) {
+ if (animate) {
+ translationXAnimation.playTogether(qsbTranslationX.animateToValue(qsbTargetX));
+ } else {
+ qsbTranslationX.setValue(qsbTargetX);
+ }
+ }
+ if (!animate) {
+ alignTaskbar.run();
+ return;
+ }
+ mHotseatTranslationXAnimation = translationXAnimation;
+ translationXAnimation.setStartDelay(FADE_OUT_ANIM_POSITION_DURATION_MS);
+ translationXAnimation.setDuration(FADE_IN_ANIM_ALPHA_DURATION_MS);
+ translationXAnimation.setInterpolator(Interpolators.EMPHASIZED);
+ translationXAnimation.addListener(AnimatorListeners.forEndCallback(alignTaskbar));
+ translationXAnimation.start();
+ }
+
+
private final class TaskBarRecentsAnimationListener implements
RecentsAnimationCallbacks.RecentsAnimationListener {
private final RecentsAnimationCallbacks mCallbacks;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java
index 751a42a..bf086b4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java
@@ -27,6 +27,8 @@
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+import androidx.annotation.VisibleForTesting;
+
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.taskbar.bubbles.BubbleControllers;
import com.android.launcher3.util.DisplayController;
@@ -77,7 +79,7 @@
public void onTaskbarVisibilityChanged(int visibility) {
mTaskbarVisible = visibility == VISIBLE;
if (shouldShowScrim()) {
- showScrim(true, getScrimAlpha(), false /* skipAnim */);
+ showScrim(true, computeScrimAlpha(), false /* skipAnim */);
} else if (mScrimView.getScrimAlpha() > 0f) {
showScrim(false, 0, false /* skipAnim */);
}
@@ -96,7 +98,7 @@
return;
}
mSysUiStateFlags = stateFlags;
- showScrim(shouldShowScrim(), getScrimAlpha(), skipAnim);
+ showScrim(shouldShowScrim(), computeScrimAlpha(), skipAnim);
}
private boolean shouldShowScrim() {
@@ -119,7 +121,7 @@
&& !mControllers.taskbarStashController.isHiddenForBubbles();
}
- private float getScrimAlpha() {
+ private float computeScrimAlpha() {
final boolean isPersistentTaskBarVisible =
mTaskbarVisible && !DisplayController.isTransientTaskbar(mScrimView.getContext());
final boolean manageMenuExpanded =
@@ -140,7 +142,7 @@
mScrimView.setOnClickListener(showScrim ? (view) -> onClick() : null);
mScrimView.setClickable(showScrim);
if (skipAnim) {
- mScrimView.setScrimAlpha(alpha);
+ mScrimAlpha.updateValue(alpha);
} else {
ObjectAnimator anim = mScrimAlpha.animateToValue(showScrim ? alpha : 0);
anim.setInterpolator(showScrim ? SCRIM_ALPHA_IN : SCRIM_ALPHA_OUT);
@@ -167,4 +169,14 @@
pw.println(prefix + "\tmScrimAlpha.value=" + mScrimAlpha.value);
}
+
+ @VisibleForTesting
+ TaskbarScrimView getScrimView() {
+ return mScrimView;
+ }
+
+ @VisibleForTesting
+ float getScrimAlpha() {
+ return mScrimAlpha.value;
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 266f384..8991965 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -61,6 +61,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatedFloat;
+import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
@@ -81,6 +82,8 @@
private static final String TAG = "TaskbarStashController";
private static final boolean DEBUG = false;
+ private static boolean sEnableSoftwareImeForTests = false;
+
/**
* Def. value for @param shouldBubblesFollow in
* {@link #updateAndAnimateTransientTaskbar(boolean)} */
@@ -130,19 +133,22 @@
*
* Use {@link #getStashDuration()} to query duration
*/
- private static final long TASKBAR_STASH_DURATION = InsetsController.ANIMATION_DURATION_RESIZE;
+ @VisibleForTesting
+ static final long TASKBAR_STASH_DURATION = InsetsController.ANIMATION_DURATION_RESIZE;
/**
* How long to stash/unstash transient taskbar.
*
* Use {@link #getStashDuration()} to query duration.
*/
- private static final long TRANSIENT_TASKBAR_STASH_DURATION = 417;
+ @VisibleForTesting
+ static final long TRANSIENT_TASKBAR_STASH_DURATION = 417;
/**
* How long to stash/unstash when keyboard is appearing/disappearing.
*/
- private static final long TASKBAR_STASH_DURATION_FOR_IME = 80;
+ @VisibleForTesting
+ static final long TASKBAR_STASH_DURATION_FOR_IME = 80;
/**
* The scale TaskbarView animates to when being stashed.
@@ -163,7 +169,7 @@
/**
* How long the icon/stash handle alpha animation plays.
*/
- public static final long TASKBAR_STASH_ALPHA_DURATION = 50;
+ public static final long TRANSIENT_TASKBAR_STASH_ALPHA_DURATION = 50;
/**
* How long to delay the icon/stash handle alpha for the home to app taskbar animation.
@@ -185,7 +191,7 @@
// Duration for which an unlock event is considered "current", as other events are received
// asynchronously.
- private static final long UNLOCK_TRANSITION_MEMOIZATION_MS = 200;
+ public static final long UNLOCK_TRANSITION_MEMOIZATION_MS = 200;
/**
* The default stash animation, morphing the taskbar into the navbar.
@@ -252,7 +258,7 @@
private boolean mEnableBlockingTimeoutDuringTests = false;
private Animator mTaskbarBackgroundAlphaAnimator;
- private long mTaskbarBackgroundDuration;
+ private final long mTaskbarBackgroundDuration;
private boolean mUserIsNotGoingHome = false;
// Evaluate whether the handle should be stashed
@@ -444,6 +450,11 @@
return hasAnyFlag(FLAG_IN_OVERVIEW);
}
+ /** Returns whether the taskbar is currently on launcher home screen. */
+ public boolean isOnHome() {
+ return !isInOverview() && !isInApp();
+ }
+
/** Returns whether taskbar is hidden for bubbles. */
public boolean isHiddenForBubbles() {
return hasAnyFlag(FLAG_STASHED_FOR_BUBBLES);
@@ -799,14 +810,14 @@
if (animationType == TRANSITION_HANDLE_FADE) {
// When fading, the handle fades in/out at the beginning of the transition with
// TASKBAR_STASH_ALPHA_DURATION.
- backgroundAndHandleAlphaDuration = TASKBAR_STASH_ALPHA_DURATION;
+ backgroundAndHandleAlphaDuration = TRANSIENT_TASKBAR_STASH_ALPHA_DURATION;
// The iconAlphaDuration must be set to duration for the skippable interpolators
// below to work.
iconAlphaDuration = duration;
} else {
iconAlphaStartDelay = TASKBAR_STASH_ALPHA_START_DELAY;
- iconAlphaDuration = TASKBAR_STASH_ALPHA_DURATION;
- backgroundAndHandleAlphaDuration = TASKBAR_STASH_ALPHA_DURATION;
+ iconAlphaDuration = TRANSIENT_TASKBAR_STASH_ALPHA_DURATION;
+ backgroundAndHandleAlphaDuration = TRANSIENT_TASKBAR_STASH_ALPHA_DURATION;
if (isStashed) {
if (animationType == TRANSITION_HOME_TO_APP) {
@@ -951,7 +962,7 @@
}
int action = expanding ? InteractionJankMonitor.CUJ_TASKBAR_EXPAND :
InteractionJankMonitor.CUJ_TASKBAR_COLLAPSE;
- animator.addListener(new AnimatorListenerAdapter() {
+ animator.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationStart(@NonNull Animator animation) {
final Configuration.Builder builder =
@@ -963,9 +974,16 @@
}
@Override
- public void onAnimationEnd(@NonNull Animator animation) {
+ public void onAnimationSuccess(@NonNull Animator animator) {
InteractionJankMonitor.getInstance().end(action);
}
+
+ @Override
+ public void onAnimationCancel(@NonNull Animator animation) {
+ super.onAnimationCancel(animation);
+
+ InteractionJankMonitor.getInstance().cancel(action);
+ }
});
}
@@ -1070,7 +1088,8 @@
/**
* When hiding the IME, delay the unstash animation to align with the end of the transition.
*/
- private long getTaskbarStashStartDelayForIme() {
+ @VisibleForTesting
+ long getTaskbarStashStartDelayForIme() {
if (mIsImeShowing) {
// Only delay when IME is exiting, not entering.
return 0;
@@ -1126,13 +1145,13 @@
}
// Do not stash if pinned taskbar, hardware keyboard is attached and no IME is docked
- if (mActivity.isHardwareKeyboard() && DisplayController.isPinnedTaskbar(mActivity)
+ if (isHardwareKeyboard() && DisplayController.isPinnedTaskbar(mActivity)
&& !mActivity.isImeDocked()) {
return false;
}
// Do not stash if hardware keyboard is attached, in 3 button nav and desktop windowing mode
- if (mActivity.isHardwareKeyboard()
+ if (isHardwareKeyboard()
&& mActivity.isThreeButtonNav()
&& mControllers.taskbarDesktopModeController.getAreDesktopTasksVisible()) {
return false;
@@ -1146,6 +1165,21 @@
return mIsImeShowing || mIsImeSwitcherShowing;
}
+ private boolean isHardwareKeyboard() {
+ return mActivity.isHardwareKeyboard() && !sEnableSoftwareImeForTests;
+ }
+
+ /**
+ * Overrides {@link #isHardwareKeyboard()} to {@code false} for testing, if enabled.
+ * <p>
+ * Virtual devices are sometimes in hardware keyboard mode, leading to an inconsistent
+ * testing environment.
+ */
+ @VisibleForTesting
+ static void enableSoftwareImeForTests(boolean enable) {
+ sEnableSoftwareImeForTests = enable;
+ }
+
/**
* Updates the proper flag to indicate whether the task bar should be stashed.
*
@@ -1271,7 +1305,7 @@
/**
* Attempts to start timer to auto hide the taskbar based on time.
*/
- public void tryStartTaskbarTimeout() {
+ private void tryStartTaskbarTimeout() {
if (!DisplayController.isTransientTaskbar(mActivity)
|| mIsStashed
|| mEnableBlockingTimeoutDuringTests) {
@@ -1299,6 +1333,11 @@
updateAndAnimateTransientTaskbarForTimeout();
}
+ @VisibleForTesting
+ Alarm getTimeoutAlarm() {
+ return mTimeoutAlarm;
+ }
+
@Override
public void dumpLogs(String prefix, PrintWriter pw) {
pw.println(prefix + "TaskbarStashController:");
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 9c8c2a9..b80aaf8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -36,6 +36,7 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.taskbar.bubbles.BubbleBarController;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.quickstep.OverviewCommandHelper;
@@ -47,6 +48,7 @@
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
import java.io.PrintWriter;
import java.util.Collections;
@@ -55,7 +57,7 @@
/**
* Base class for providing different taskbar UI
*/
-public class TaskbarUIController {
+public class TaskbarUIController implements BubbleBarController.BubbleBarLocationListener {
public static final TaskbarUIController DEFAULT = new TaskbarUIController();
// Initialized in init.
@@ -433,6 +435,14 @@
public void stashHotseat(boolean stash) {
}
+ @Override
+ public void onBubbleBarLocationAnimated(BubbleBarLocation location) {
+ }
+
+ @Override
+ public void onBubbleBarLocationUpdated(BubbleBarLocation location) {
+ }
+
/** Un-stash the hotseat instantly */
public void unStashHotseatInstantly() {
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 2734137..8763509 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -120,6 +120,8 @@
private boolean mShouldTryStartAlign;
+ private final int mMaxNumIcons;
+
public TaskbarView(@NonNull Context context) {
this(context, null);
}
@@ -185,6 +187,18 @@
}
// TODO: Disable touch events on QSB otherwise it can crash.
mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
+
+ mMaxNumIcons = calculateMaxNumIcons();
+ }
+
+ /**
+ // @return the maximum number of 'icons' that can fit in the taskbar.
+ // TODO(368119679): Assumes that they are all the same size.
+ */
+ private int calculateMaxNumIcons() {
+ int availableWidth = mActivityContext.getDeviceProfile().widthPx
+ - (mActivityContext.getDeviceProfile().edgeMarginPx * 2);
+ return Math.floorDiv(availableWidth, mIconTouchSize);
}
@Override
@@ -473,6 +487,9 @@
addView(mTaskbarDividerContainer, mIsRtl ? (getChildCount() - 1) : 1);
}
}
+
+ updateRecentAppsToFit();
+
if (mActivityContext.getDeviceProfile().isQsbInline) {
addView(mQsb, mIsRtl ? getChildCount() : 0);
// Always set QSB to invisible after re-adding.
@@ -480,6 +497,45 @@
}
}
+ /**
+ * Updates the recent apps portion of the taskbar by:
+ * - Removing overflow affordance if overflow is not needed.
+ * - Removing any recent apps that do not fit.
+ */
+ private void updateRecentAppsToFit() {
+ if (!Flags.taskbarOverflow()) {
+ return;
+ }
+ int indexOfFirstRecentApp = -1;
+ int size = getChildCount();
+ boolean removeOverflowView = true;
+
+ for (int i = 0; i < size; ++i) {
+ if (getChildAt(i).getTag() instanceof GroupTask) {
+ indexOfFirstRecentApp = i;
+ removeOverflowView = false;
+ break;
+ }
+ }
+
+ if (indexOfFirstRecentApp != -1) {
+ // We pre-maturely added the overflow icon, so we can take it out of the count.
+ int numRecentAppsToRemove = Math.max(0, getChildCount() - mMaxNumIcons + 1);
+ if (numRecentAppsToRemove <= 1) {
+ // We can fit all of the recent apps if we remove the overflow icon.
+ removeOverflowView = true;
+ } else {
+ for (int i = 0; i < numRecentAppsToRemove; ++i) {
+ removeAndRecycle(getChildAt(indexOfFirstRecentApp));
+ }
+ }
+ }
+
+ if (removeOverflowView) {
+ removeView(mTaskbarOverflowView);
+ }
+ }
+
/** Binds the GroupTask to the BubbleTextView to be ready to present to the user. */
public void applyGroupTaskToBubbleTextView(BubbleTextView btv, GroupTask groupTask) {
// TODO(b/343289567): support app pairs.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
index d108d8c..176be1c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
@@ -140,7 +140,8 @@
return new View.OnClickListener() {
@Override
public void onClick(View v) {
- mControllers.keyboardQuickSwitchController.openQuickSwitchView();
+ mControllers.keyboardQuickSwitchController.openQuickSwitchView(
+ mControllers.taskbarViewController.getTaskIdsForPinnedApps());
}
};
}
@@ -150,7 +151,8 @@
return new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
- mControllers.keyboardQuickSwitchController.openQuickSwitchView();
+ mControllers.keyboardQuickSwitchController.openQuickSwitchView(
+ mControllers.taskbarViewController.getTaskIdsForPinnedApps());
return true;
}
};
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index b207b37..e522035 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -18,6 +18,7 @@
import static com.android.app.animation.Interpolators.FINAL_FRAME;
import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
+import static com.android.launcher3.Flags.taskbarOverflow;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
@@ -81,6 +82,8 @@
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.Set;
import java.util.function.Predicate;
@@ -156,7 +159,7 @@
private final View.OnLayoutChangeListener mTaskbarViewLayoutChangeListener =
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
updateTaskbarIconTranslationXForPinning();
- mControllers.navbarButtonsViewController.onTaskbarLayoutChange();
+ mControllers.navbarButtonsViewController.onTaskbarLayoutChanged();
};
// Animation to align icons with Launcher, created lazily. This allows the controller to be
@@ -326,7 +329,8 @@
*/
public void setRecentsButtonDisabled(boolean isDisabled) {
// TODO: check TaskbarStashController#supportsStashing(), to stash instead of setting alpha.
- mTaskbarIconAlpha.get(ALPHA_INDEX_RECENTS_DISABLED).setValue(isDisabled ? 0 : 1);
+ mTaskbarIconAlpha.get(ALPHA_INDEX_RECENTS_DISABLED).animateToValue(isDisabled ? 0 : 1)
+ .start();
}
/**
@@ -629,6 +633,24 @@
}
}
+ /**
+ * @return A set of Task ids of running apps that are pinned in the taskbar.
+ */
+ protected Set<Integer> getTaskIdsForPinnedApps() {
+ if (!taskbarOverflow()) {
+ return Collections.emptySet();
+ }
+
+ Set<Integer> pinnedAppsWithTasks = new HashSet<>();
+ for (View iconView : getIconViews()) {
+ if (iconView instanceof BubbleTextView btv
+ && btv.getTag() instanceof TaskItemInfo itemInfo) {
+ pinnedAppsWithTasks.add(itemInfo.getTaskId());
+ }
+ }
+ return pinnedAppsWithTasks;
+ }
+
private BubbleTextView.RunningAppState getRunningAppState(
BubbleTextView btv,
Set<Integer> runningTaskIds,
@@ -812,6 +834,14 @@
: mPersistentTaskbarDp.taskbarBottomMargin;
int firstRecentTaskIndex = -1;
+ int hotseatNavBarTranslationX = 0;
+ if (mCurrentBubbleBarLocation != null
+ && taskbarDp.shouldAdjustHotseatOnBubblesLocationUpdate(mActivity)) {
+ boolean isBubblesOnLeft = mCurrentBubbleBarLocation.isOnLeft(
+ mTaskbarView.isLayoutRtl());
+ hotseatNavBarTranslationX = taskbarDp
+ .getHotseatTranslationXForBubbleBar(/* isNavbarOnRight = */ isBubblesOnLeft);
+ }
for (int i = 0; i < mTaskbarView.getChildCount(); i++) {
View child = mTaskbarView.getChildAt(i);
boolean isAllAppsButton = child == mTaskbarView.getAllAppsButtonContainer();
@@ -847,16 +877,20 @@
: Interpolators.clampToProgress(LINEAR, 0.72f, 0.84f));
}
}
-
if (child == mTaskbarView.getQsb()) {
boolean isRtl = Utilities.isRtl(child.getResources());
float hotseatIconCenter = isRtl
? launcherDp.widthPx - hotseatPadding.right + borderSpacing
+ launcherDp.hotseatQsbWidth / 2f
: hotseatPadding.left - borderSpacing - launcherDp.hotseatQsbWidth / 2f;
+ if (taskbarDp.isQsbInline) {
+ hotseatIconCenter += hotseatNavBarTranslationX;
+ }
float childCenter = (child.getLeft() + child.getRight()) / 2f;
- childCenter += ((Reorderable) child).getTranslateDelegate().getTranslationX(
- INDEX_TASKBAR_PINNING_ANIM).getValue();
+ if (child instanceof Reorderable reorderableChild) {
+ childCenter += reorderableChild.getTranslateDelegate().getTranslationX(
+ INDEX_TASKBAR_PINNING_ANIM).getValue();
+ }
float halfQsbIconWidthDiff =
(launcherDp.hotseatQsbWidth - taskbarDp.taskbarIconSize) / 2f;
float scale = ((float) taskbarDp.taskbarIconSize)
@@ -865,8 +899,8 @@
float fromX = isRtl ? -halfQsbIconWidthDiff : halfQsbIconWidthDiff;
float toX = hotseatIconCenter - childCenter;
- if (child instanceof Reorderable) {
- MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate();
+ if (child instanceof Reorderable reorderableChild) {
+ MultiTranslateDelegate mtd = reorderableChild.getTranslateDelegate();
setter.addFloat(mtd.getTranslationX(INDEX_TASKBAR_ALIGNMENT_ANIM),
MULTI_PROPERTY_VALUE, fromX, toX, interpolator);
@@ -913,6 +947,7 @@
+ (hotseatCellSize + borderSpacing) * positionInHotseat
+ hotseatCellSize / 2f;
}
+ hotseatIconCenter += hotseatNavBarTranslationX;
float childCenter = (child.getLeft() + child.getRight()) / 2f;
childCenter += ((Reorderable) child).getTranslateDelegate().getTranslationX(
INDEX_TASKBAR_PINNING_ANIM).getValue();
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index 6860004..51e09ab 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -196,7 +196,8 @@
mBubbleBarViewController.setUpdateSelectedBubbleAfterCollapse(
key -> setSelectedBubbleInternal(mBubbles.get(key)));
mBubbleBarViewController.setBoundsChangeListener(this::onBubbleBarBoundsChanged);
-
+ mBubbleBarLocationListener.onBubbleBarLocationUpdated(
+ mBubbleBarViewController.getBubbleBarLocation());
if (sBubbleBarEnabled) {
mSystemUiProxy.setBubblesListener(this);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarSwipeController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarSwipeController.kt
index a831fd7..bc562a6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarSwipeController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarSwipeController.kt
@@ -21,10 +21,15 @@
import androidx.annotation.VisibleForTesting
import androidx.core.animation.doOnEnd
import androidx.dynamicanimation.animation.SpringForce
+import com.android.launcher3.R
import com.android.launcher3.anim.AnimatedFloat
import com.android.launcher3.anim.SpringAnimationBuilder
import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.launcher3.taskbar.TaskbarThresholdUtils
+import com.android.launcher3.taskbar.bubbles.BubbleBarSwipeController.StartState.COLLAPSED
+import com.android.launcher3.taskbar.bubbles.BubbleBarSwipeController.StartState.EXPANDED
+import com.android.launcher3.taskbar.bubbles.BubbleBarSwipeController.StartState.STASHED
+import com.android.launcher3.taskbar.bubbles.BubbleBarSwipeController.StartState.UNKNOWN
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController
import com.android.launcher3.touch.OverScroll
@@ -34,8 +39,8 @@
private val context: Context
private var bubbleStashedHandleViewController: BubbleStashedHandleViewController? = null
- private var bubbleBarViewController: BubbleBarViewController? = null
- private var bubbleStashController: BubbleStashController? = null
+ private lateinit var bubbleBarViewController: BubbleBarViewController
+ private lateinit var bubbleStashController: BubbleStashController
private var springAnimation: ValueAnimator? = null
private val animatedSwipeTranslation = AnimatedFloat(this::onSwipeUpdate)
@@ -43,6 +48,7 @@
private val unstashThreshold: Int
private val expandThreshold: Int
private val maxOverscroll: Int
+ private val stashThreshold: Int
private var swipeState: SwipeState = SwipeState()
@@ -54,6 +60,7 @@
unstashThreshold = dimensionProvider.unstashThreshold
expandThreshold = dimensionProvider.expandThreshold
maxOverscroll = dimensionProvider.maxOverscroll
+ stashThreshold = dimensionProvider.stashThreshold
}
fun init(bubbleControllers: BubbleControllers) {
@@ -66,23 +73,21 @@
/** Start tracking a new swipe gesture */
fun start() {
if (springAnimation != null) reset()
- val stashed = bubbleStashController?.isStashed ?: false
- val barVisible = bubbleStashController?.isBubbleBarVisible() ?: false
- val expanded = bubbleBarViewController?.isExpanded ?: false
-
- swipeState =
- SwipeState(
- stashedOnStart = stashed,
- collapsedOnStart = !stashed && barVisible && !expanded,
- expandedOnStart = expanded,
- )
+ val startState =
+ when {
+ bubbleStashController.isStashed -> STASHED
+ bubbleBarViewController.isExpanded -> EXPANDED
+ bubbleStashController.isBubbleBarVisible() -> COLLAPSED
+ else -> UNKNOWN
+ }
+ swipeState = SwipeState(startState = startState)
}
/** Update swipe distance to [dy] */
fun swipeTo(dy: Float) {
- // Only handle swipe up and stashed or collapsed bar
- if (dy > 0 || swipeState.expandedOnStart) return
-
+ if (!canHandleSwipe(dy)) {
+ return
+ }
animatedSwipeTranslation.updateValue(dy)
val prevState = swipeState
@@ -90,38 +95,63 @@
val passedUnstashThreshold = isUnstash(dy) || prevState.passedUnstashThreshold
// Expand happens at the end of the gesture, always keep the current value
val passedExpandThreshold = isExpand(dy)
+ // Stash happens at the end of the gesture, always keep the current value
+ val passedStashThreshold = isStash(dy)
if (
passedUnstashThreshold != prevState.passedUnstashThreshold ||
- passedExpandThreshold != prevState.passedExpandThreshold
+ passedExpandThreshold != prevState.passedExpandThreshold ||
+ passedStashThreshold != prevState.passedStashThreshold
) {
swipeState =
swipeState.copy(
passedUnstashThreshold = passedUnstashThreshold,
passedExpandThreshold = passedExpandThreshold,
+ passedStashThreshold = passedStashThreshold,
)
}
if (
- swipeState.stashedOnStart &&
+ swipeState.startState == STASHED &&
swipeState.passedUnstashThreshold &&
!prevState.passedUnstashThreshold
) {
- bubbleStashController?.showBubbleBar(expandBubbles = false)
+ bubbleStashController.showBubbleBar(expandBubbles = false)
}
}
/** Finish tracking swipe gesture. Animate views back to resting state */
fun finish() {
- if (swipeState.passedExpandThreshold) {
- bubbleStashController?.showBubbleBar(expandBubbles = true)
+ when {
+ swipeState.passedExpandThreshold &&
+ swipeState.startState in setOf(STASHED, COLLAPSED) -> {
+ bubbleStashController.showBubbleBar(expandBubbles = true)
+ }
+ swipeState.passedStashThreshold && swipeState.startState == COLLAPSED -> {
+ bubbleStashController.stashBubbleBar()
+ }
}
- springToRest()
+ if (animatedSwipeTranslation.value == 0f) {
+ reset()
+ } else {
+ springToRest()
+ }
}
/** Returns `true` if we are tracking a swipe gesture */
fun isSwipeGesture(): Boolean {
- return swipeState.passedUnstashThreshold || swipeState.passedExpandThreshold
+ return swipeState.passedUnstashThreshold ||
+ swipeState.passedExpandThreshold ||
+ swipeState.passedStashThreshold
+ }
+
+ private fun canHandleSwipe(dy: Float): Boolean {
+ return when (swipeState.startState) {
+ STASHED -> dy < 0 // stashed bar only handles swipe up
+ COLLAPSED -> true // collapsed bar can be swiped in either direction
+ UNKNOWN,
+ EXPANDED -> false // expanded bar can't be swiped
+ }
}
private fun isUnstash(dy: Float): Boolean {
@@ -132,6 +162,10 @@
return dy < -expandThreshold
}
+ private fun isStash(dy: Float): Boolean {
+ return dy > stashThreshold
+ }
+
private fun reset() {
springAnimation?.let {
if (it.isRunning) {
@@ -147,7 +181,7 @@
private fun onSwipeUpdate(value: Float) {
val dampedSwipe = -OverScroll.dampedScroll(-value, maxOverscroll).toFloat()
bubbleStashedHandleViewController?.setTranslationYForSwipe(dampedSwipe)
- bubbleBarViewController?.setTranslationYForSwipe(dampedSwipe)
+ bubbleBarViewController.setTranslationYForSwipe(dampedSwipe)
}
private fun springToRest() {
@@ -163,19 +197,26 @@
}
internal data class SwipeState(
- val stashedOnStart: Boolean = false,
- val collapsedOnStart: Boolean = false,
- val expandedOnStart: Boolean = false,
+ val startState: StartState = UNKNOWN,
val passedUnstashThreshold: Boolean = false,
val passedExpandThreshold: Boolean = false,
+ val passedStashThreshold: Boolean = false,
)
+ internal enum class StartState {
+ UNKNOWN,
+ STASHED,
+ COLLAPSED,
+ EXPANDED,
+ }
+
/** Allows overriding the dimension provider for testing */
@VisibleForTesting
interface DimensionProvider {
val unstashThreshold: Int
val expandThreshold: Int
val maxOverscroll: Int
+ val stashThreshold: Int
}
private class DefaultDimensionProvider(taskbarActivityContext: TaskbarActivityContext) :
@@ -183,6 +224,7 @@
override val unstashThreshold: Int
override val expandThreshold: Int
override val maxOverscroll: Int
+ override val stashThreshold: Int
init {
val resources = taskbarActivityContext.resources
@@ -198,6 +240,7 @@
taskbarActivityContext.deviceProfile,
)
maxOverscroll = taskbarActivityContext.deviceProfile.heightPx - unstashThreshold
+ stashThreshold = resources.getDimensionPixelSize(R.dimen.taskbar_to_nav_threshold)
}
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index d454fd7..7fed381 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -15,13 +15,9 @@
*/
package com.android.launcher3.taskbar.bubbles;
-import static com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE;
import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
@@ -42,10 +38,9 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
-import androidx.dynamicanimation.animation.SpringForce;
-
import com.android.launcher3.R;
-import com.android.launcher3.anim.SpringAnimationBuilder;
+import com.android.launcher3.anim.AnimatorListeners;
+import com.android.launcher3.taskbar.BarsLocationAnimatorHelper;
import com.android.launcher3.taskbar.bubbles.animation.BubbleAnimator;
import com.android.launcher3.util.DisplayController;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
@@ -83,8 +78,9 @@
*/
public class BubbleBarView extends FrameLayout {
+ public static final long FADE_OUT_ANIM_POSITION_DURATION_MS = 100L;
+ public static final long FADE_IN_ANIM_ALPHA_DURATION_MS = 100L;
private static final String TAG = "BubbleBarView";
-
// TODO: (b/273594744) calculate the amount of space we have and base the max on that
// if it's smaller than 5.
private static final int MAX_BUBBLES = 5;
@@ -93,18 +89,6 @@
private static final int WIDTH_ANIMATION_DURATION_MS = 200;
private static final int SCALE_ANIMATION_DURATION_MS = 200;
- private static final long FADE_OUT_ANIM_ALPHA_DURATION_MS = 50L;
- private static final long FADE_OUT_ANIM_ALPHA_DELAY_MS = 50L;
- public static final long FADE_OUT_ANIM_POSITION_DURATION_MS = 100L;
- // During fade out animation we shift the bubble bar 1/80th of the screen width
- private static final float FADE_OUT_ANIM_POSITION_SHIFT = 1 / 80f;
-
- public static final long FADE_IN_ANIM_ALPHA_DURATION_MS = 100L;
- // Use STIFFNESS_MEDIUMLOW which is not defined in the API constants
- private static final float FADE_IN_ANIM_POSITION_SPRING_STIFFNESS = 400f;
- // During fade in animation we shift the bubble bar 1/60th of the screen width
- private static final float FADE_IN_ANIM_POSITION_SHIFT = 1 / 60f;
-
/**
* Custom property to set alpha value for the bar view while a bubble is being dragged.
* Skips applying alpha to the dragged bubble.
@@ -578,77 +562,30 @@
// First animator hides the bar.
// After it completes, bubble positions in the bar and arrow position is updated.
// Second animator is started to show the bar.
- mBubbleBarLocationAnimator = getLocationUpdateFadeOutAnimator(bubbleBarLocation);
- mBubbleBarLocationAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- updateBubblesLayoutProperties(bubbleBarLocation);
- mBubbleBarBackground.setAnchorLeft(bubbleBarLocation.isOnLeft(isLayoutRtl()));
+ ObjectAnimator alphaOutAnim = ObjectAnimator.ofFloat(
+ this, getLocationAnimAlphaProperty(), 0f);
+ mBubbleBarLocationAnimator = BarsLocationAnimatorHelper.getBubbleBarLocationOutAnimator(
+ this,
+ bubbleBarLocation,
+ alphaOutAnim);
+ mBubbleBarLocationAnimator.addListener(AnimatorListeners.forEndCallback(() -> {
+ updateBubblesLayoutProperties(bubbleBarLocation);
+ mBubbleBarBackground.setAnchorLeft(bubbleBarLocation.isOnLeft(isLayoutRtl()));
+ ObjectAnimator alphaInAnim = ObjectAnimator.ofFloat(BubbleBarView.this,
+ getLocationAnimAlphaProperty(), 1f);
- // Animate it in
- mBubbleBarLocationAnimator = getLocationUpdateFadeInAnimator(bubbleBarLocation);
- mBubbleBarLocationAnimator.start();
- }
- });
+ // Animate it in
+ mBubbleBarLocationAnimator = BarsLocationAnimatorHelper.getBubbleBarLocationInAnimator(
+ bubbleBarLocation,
+ mBubbleBarLocation,
+ getDistanceFromOtherSide(),
+ alphaInAnim,
+ BubbleBarView.this);
+ mBubbleBarLocationAnimator.start();
+ }));
mBubbleBarLocationAnimator.start();
}
- private Animator getLocationUpdateFadeOutAnimator(BubbleBarLocation newLocation) {
- final float shift =
- getResources().getDisplayMetrics().widthPixels * FADE_OUT_ANIM_POSITION_SHIFT;
- final boolean onLeft = newLocation.isOnLeft(isLayoutRtl());
- final float tx = getTranslationX() + (onLeft ? -shift : shift);
-
- ObjectAnimator positionAnim = ObjectAnimator.ofFloat(this, VIEW_TRANSLATE_X, tx)
- .setDuration(FADE_OUT_ANIM_POSITION_DURATION_MS);
- positionAnim.setInterpolator(EMPHASIZED_ACCELERATE);
-
- ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(this, getLocationAnimAlphaProperty(), 0f)
- .setDuration(FADE_OUT_ANIM_ALPHA_DURATION_MS);
- alphaAnim.setStartDelay(FADE_OUT_ANIM_ALPHA_DELAY_MS);
-
- AnimatorSet animatorSet = new AnimatorSet();
- animatorSet.playTogether(positionAnim, alphaAnim);
- return animatorSet;
- }
-
- private Animator getLocationUpdateFadeInAnimator(BubbleBarLocation newLocation) {
- final float shift =
- getResources().getDisplayMetrics().widthPixels * FADE_IN_ANIM_POSITION_SHIFT;
-
- final boolean onLeft = newLocation.isOnLeft(isLayoutRtl());
- final float startTx;
- final float finalTx;
- if (newLocation == mBubbleBarLocation) {
- // Animated location matches layout location.
- finalTx = 0;
- } else {
- // We are animating in to a transient location, need to move the bar accordingly.
- finalTx = getDistanceFromOtherSide() * (onLeft ? -1 : 1);
- }
- if (onLeft) {
- // Bar will be shown on the left side. Start point is shifted right.
- startTx = finalTx + shift;
- } else {
- // Bar will be shown on the right side. Start point is shifted left.
- startTx = finalTx - shift;
- }
-
- ValueAnimator positionAnim = new SpringAnimationBuilder(getContext())
- .setStartValue(startTx)
- .setEndValue(finalTx)
- .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
- .setStiffness(FADE_IN_ANIM_POSITION_SPRING_STIFFNESS)
- .build(this, VIEW_TRANSLATE_X);
-
- ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(this, getLocationAnimAlphaProperty(), 1f)
- .setDuration(FADE_IN_ANIM_ALPHA_DURATION_MS);
-
- AnimatorSet animatorSet = new AnimatorSet();
- animatorSet.playTogether(positionAnim, alphaAnim);
- return animatorSet;
- }
-
/**
* Get property that can be used to animate the alpha value for the bar.
* When a bubble is being dragged, uses {@link #BUBBLE_DRAG_ALPHA}.
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 025c038..ba180a6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -675,7 +675,8 @@
// if the animation is suppressed, immediately stash or show the bubble bar to
// ensure they've been initialized.
if (mTaskbarStashController.isInApp()
- && mBubbleStashController.isTransientTaskBar()) {
+ && mBubbleStashController.isTransientTaskBar()
+ && mTaskbarStashController.isStashed()) {
mBubbleStashController.stashBubbleBarImmediate();
} else {
mBubbleStashController.showBubbleBarImmediate();
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
index 8230f42..b5d94bd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
@@ -22,6 +22,7 @@
import com.android.launcher3.taskbar.TaskbarControllers;
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController.TaskbarViewPropertiesProvider;
+import com.android.launcher3.taskbar.bubbles.stashing.BubbleBarLocationOnDemandListener;
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.RunnableList;
@@ -79,11 +80,11 @@
* in constructors for now, as some controllers may still be waiting for init().
*/
public void init(TaskbarControllers taskbarControllers) {
- // TODO(b/346381754) add TaskbarLauncherStateController implementation to adjust the hotseat
BubbleBarLocationCompositeListener bubbleBarLocationListeners =
new BubbleBarLocationCompositeListener(
taskbarControllers.navbarButtonsViewController,
- taskbarControllers.taskbarViewController
+ taskbarControllers.taskbarViewController,
+ new BubbleBarLocationOnDemandListener(() -> taskbarControllers.uiController)
);
bubbleBarController.init(this,
bubbleBarLocationListeners,
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutController.kt
index 4939c99..49760ff 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutController.kt
@@ -19,6 +19,7 @@
import android.view.Gravity
import android.view.ViewGroup
import android.widget.FrameLayout
+import androidx.core.animation.ValueAnimator
import com.android.launcher3.R
/** Creates and manages the visibility of the [BubbleBarFlyoutView]. */
@@ -33,7 +34,7 @@
fun setUpFlyout(message: BubbleBarFlyoutMessage) {
flyout?.let(container::removeView)
- val flyout = BubbleBarFlyoutView(container.context, onLeft = positioner.isOnLeft)
+ val flyout = BubbleBarFlyoutView(container.context, positioner)
flyout.translationY = positioner.targetTy
@@ -47,7 +48,11 @@
lp.marginEnd = horizontalMargin
container.addView(flyout, lp)
- flyout.setData(message)
+ val animator = ValueAnimator.ofFloat(0f, 1f)
+ animator.addUpdateListener { _ ->
+ flyout.updateExpansionProgress(animator.animatedValue as Float)
+ }
+ flyout.showFromCollapsed(message) { animator.start() }
this.flyout = flyout
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutPositioner.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutPositioner.kt
index deed1f5..aa2555e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutPositioner.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutPositioner.kt
@@ -16,6 +16,8 @@
package com.android.launcher3.taskbar.bubbles.flyout
+import android.graphics.PointF
+
/** Provides positioning data to the flyout view. */
interface BubbleBarFlyoutPositioner {
@@ -24,4 +26,26 @@
/** The target translation Y that the flyout view should have when displayed. */
val targetTy: Float
+
+ /**
+ * The distance between the expanded position of the flyout and the collapsed position.
+ *
+ * The distance is calculated between the bottom corner which is aligned with the bubble bar.
+ */
+ val distanceToCollapsedPosition: PointF
+
+ /** The size of the flyout when collapsed. */
+ val collapsedSize: Float
+
+ /** The color of the flyout when collapsed. */
+ val collapsedColor: Int
+
+ /** The elevation of the flyout when collapsed. */
+ val collapsedElevation: Float
+
+ /**
+ * The distance the flyout must pass from its collapsed position until it can start revealing
+ * the triangle.
+ */
+ val distanceToRevealTriangle: Float
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutView.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutView.kt
index 4b91f46..8d84ddf 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutView.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutView.kt
@@ -20,19 +20,30 @@
import android.content.res.Configuration
import android.graphics.Canvas
import android.graphics.Color
+import android.graphics.Outline
import android.graphics.Paint
import android.graphics.Path
+import android.graphics.PointF
+import android.graphics.RectF
import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewOutlineProvider
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.animation.ArgbEvaluator
import com.android.launcher3.R
import com.android.launcher3.popup.RoundedArrowDrawable
/** The flyout view used to notify the user of a new bubble notification. */
-class BubbleBarFlyoutView(context: Context, private val onLeft: Boolean) :
+class BubbleBarFlyoutView(context: Context, private val positioner: BubbleBarFlyoutPositioner) :
ConstraintLayout(context) {
+ private companion object {
+ // the minimum progress of the expansion animation before the content starts fading in.
+ const val MIN_EXPANSION_PROGRESS_FOR_CONTENT_ALPHA = 0.75f
+ }
+
private val sender: TextView by
lazy(LazyThreadSafetyMode.NONE) { findViewById(R.id.bubble_flyout_name) }
@@ -79,9 +90,43 @@
context.resources.getDimensionPixelSize(R.dimen.bubblebar_flyout_max_width)
}
+ private val flyoutElevation by
+ lazy(LazyThreadSafetyMode.NONE) {
+ context.resources.getDimensionPixelSize(R.dimen.bubblebar_flyout_elevation).toFloat()
+ }
+
+ /** The bounds of the background rect. */
+ private val backgroundRect = RectF()
private val cornerRadius: Float
private val triangle: Path = Path()
+ private val triangleOutline = Outline()
private var backgroundColor = Color.BLACK
+ /** Represents the progress of the expansion animation. 0 when collapsed. 1 when expanded. */
+ private var expansionProgress = 0f
+ /** Translation x-y values to move the flyout to its collapsed position. */
+ private var translationToCollapsedPosition = PointF(0f, 0f)
+ /** The size of the flyout when it's collapsed. */
+ private var collapsedSize = 0f
+ /** The corner radius of the flyout when it's collapsed. */
+ private var collapsedCornerRadius = 0f
+ /** The color of the flyout when collapsed. */
+ private var collapsedColor = 0
+ /** The elevation of the flyout when collapsed. */
+ private var collapsedElevation = 0f
+ /** The minimum progress of the expansion animation before the triangle is made visible. */
+ private var minExpansionProgressForTriangle = 0f
+
+ /** The corner radius of the background according to the progress of the animation. */
+ private val currentCornerRadius
+ get() = collapsedCornerRadius + (cornerRadius - collapsedCornerRadius) * expansionProgress
+
+ /** Translation X of the background. */
+ private val backgroundRectTx
+ get() = translationToCollapsedPosition.x * (1 - expansionProgress)
+
+ /** Translation Y of the background. */
+ private val backgroundRectTy
+ get() = translationToCollapsedPosition.y * (1 - expansionProgress)
/**
* The paint used to draw the background, whose color changes as the flyout transitions to the
@@ -97,14 +142,13 @@
ta.recycle()
setWillNotDraw(false)
- clipChildren = false
+ clipChildren = true
clipToPadding = false
val padding = context.resources.getDimensionPixelSize(R.dimen.bubblebar_flyout_padding)
// add extra padding to the bottom of the view to include the triangle
setPadding(padding, padding, padding, padding + triangleHeight - triangleOverlap)
- translationZ =
- context.resources.getDimensionPixelSize(R.dimen.bubblebar_flyout_elevation).toFloat()
+ translationZ = flyoutElevation
RoundedArrowDrawable.addDownPointingRoundedTriangleToPath(
triangleWidth.toFloat(),
@@ -112,11 +156,51 @@
triangleRadius.toFloat(),
triangle,
)
+ triangleOutline.setPath(triangle)
+
+ outlineProvider =
+ object : ViewOutlineProvider() {
+ override fun getOutline(view: View, outline: Outline) {
+ this@BubbleBarFlyoutView.getOutline(outline)
+ }
+ }
+ clipToOutline = true
applyConfigurationColors(resources.configuration)
}
- fun setData(flyoutMessage: BubbleBarFlyoutMessage) {
+ /** Sets the data for the flyout and starts playing the expand animation. */
+ fun showFromCollapsed(flyoutMessage: BubbleBarFlyoutMessage, expandAnimation: () -> Unit) {
+ avatar.alpha = 0f
+ sender.alpha = 0f
+ message.alpha = 0f
+ setData(flyoutMessage)
+ val txToCollapsedPosition =
+ if (positioner.isOnLeft) {
+ positioner.distanceToCollapsedPosition.x
+ } else {
+ -positioner.distanceToCollapsedPosition.x
+ }
+ val tyToCollapsedPosition =
+ positioner.distanceToCollapsedPosition.y + triangleHeight - triangleOverlap
+ translationToCollapsedPosition = PointF(txToCollapsedPosition, tyToCollapsedPosition)
+
+ collapsedSize = positioner.collapsedSize
+ collapsedCornerRadius = collapsedSize / 2
+ collapsedColor = positioner.collapsedColor
+ collapsedElevation = positioner.collapsedElevation
+
+ // calculate the expansion progress required before we start showing the triangle as part of
+ // the expansion animation
+ minExpansionProgressForTriangle =
+ positioner.distanceToRevealTriangle / tyToCollapsedPosition
+
+ // post the request to start the expand animation to the looper so the view can measure
+ // itself
+ post(expandAnimation)
+ }
+
+ private fun setData(flyoutMessage: BubbleBarFlyoutMessage) {
// the avatar is only displayed in group chat messages
if (flyoutMessage.senderAvatar != null && flyoutMessage.isGroupChat) {
avatar.visibility = VISIBLE
@@ -151,28 +235,125 @@
message.text = flyoutMessage.message
}
+ /** Updates the flyout view with the progress of the animation. */
+ fun updateExpansionProgress(fraction: Float) {
+ expansionProgress = fraction
+
+ updateTranslationForAnimation(message)
+ updateTranslationForAnimation(sender)
+ updateTranslationForAnimation(avatar)
+
+ // start fading in the content only after we're past the threshold
+ val alpha =
+ ((expansionProgress - MIN_EXPANSION_PROGRESS_FOR_CONTENT_ALPHA) /
+ (1f - MIN_EXPANSION_PROGRESS_FOR_CONTENT_ALPHA))
+ .coerceIn(0f, 1f)
+ sender.alpha = alpha
+ message.alpha = alpha
+ avatar.alpha = alpha
+
+ translationZ =
+ collapsedElevation + (flyoutElevation - collapsedElevation) * expansionProgress
+
+ invalidate()
+ }
+
override fun onDraw(canvas: Canvas) {
- canvas.drawRoundRect(
- 0f,
- 0f,
- width.toFloat(),
+ // interpolate the width, height, corner radius and translation based on the progress of the
+ // animation.
+ // the background is drawn from the bottom left corner to the top right corner if we're
+ // positioned on the left, and from the bottom right corner to the top left if we're
+ // positioned on the right.
+
+ // the current width of the background rect according to the progress of the animation
+ val currentWidth = collapsedSize + (width - collapsedSize) * expansionProgress
+ val rectBottom = height - triangleHeight + triangleOverlap
+ val currentHeight = collapsedSize + (rectBottom - collapsedSize) * expansionProgress
+
+ backgroundRect.set(
+ if (positioner.isOnLeft) 0f else width.toFloat() - currentWidth,
+ height.toFloat() - triangleHeight + triangleOverlap - currentHeight,
+ if (positioner.isOnLeft) currentWidth else width.toFloat(),
height.toFloat() - triangleHeight + triangleOverlap,
- cornerRadius,
- cornerRadius,
+ )
+
+ backgroundPaint.color =
+ ArgbEvaluator.getInstance().evaluate(expansionProgress, collapsedColor, backgroundColor)
+
+ canvas.save()
+ canvas.translate(backgroundRectTx, backgroundRectTy)
+ // draw the background starting from the bottom left if we're positioned left, or the bottom
+ // right if we're positioned right.
+ canvas.drawRoundRect(
+ backgroundRect,
+ currentCornerRadius,
+ currentCornerRadius,
backgroundPaint,
)
- drawTriangle(canvas)
+ if (expansionProgress >= minExpansionProgressForTriangle) {
+ drawTriangle(canvas)
+ }
+ canvas.restore()
+ invalidateOutline()
super.onDraw(canvas)
}
private fun drawTriangle(canvas: Canvas) {
canvas.save()
- val triangleX = if (onLeft) cornerRadius else width - cornerRadius - triangleWidth
- canvas.translate(triangleX, (height - triangleHeight).toFloat())
+ val triangleX =
+ if (positioner.isOnLeft) {
+ currentCornerRadius
+ } else {
+ width - currentCornerRadius - triangleWidth
+ }
+ // instead of scaling the triangle, increasingly reveal it from the background. this has the
+ // effect of the triangle scaling.
+
+ // the translation y of the triangle before we start revealing it. align its bottom with the
+ // bottom of the rect
+ val triangleYCollapsed = height - triangleHeight - (triangleHeight - triangleOverlap)
+ // the translation y of the triangle when it's fully revealed
+ val triangleYExpanded = height - triangleHeight
+ val interpolatedExpansion =
+ ((expansionProgress - minExpansionProgressForTriangle) /
+ (1 - minExpansionProgressForTriangle))
+ .coerceIn(0f, 1f)
+ val triangleY =
+ triangleYCollapsed + (triangleYExpanded - triangleYCollapsed) * interpolatedExpansion
+ canvas.translate(triangleX, triangleY)
canvas.drawPath(triangle, backgroundPaint)
+ triangleOutline.setPath(triangle)
+ triangleOutline.offset(triangleX.toInt(), triangleY.toInt())
canvas.restore()
}
+ private fun getOutline(outline: Outline) {
+ val path = Path()
+ path.addRoundRect(
+ backgroundRect,
+ currentCornerRadius,
+ currentCornerRadius,
+ Path.Direction.CW,
+ )
+ if (expansionProgress >= minExpansionProgressForTriangle) {
+ path.addPath(triangleOutline.mPath)
+ }
+ outline.setPath(path)
+ outline.offset(backgroundRectTx.toInt(), backgroundRectTy.toInt())
+ }
+
+ private fun updateTranslationForAnimation(view: View) {
+ val tx =
+ if (positioner.isOnLeft) {
+ translationToCollapsedPosition.x - view.left
+ } else {
+ width - view.left - translationToCollapsedPosition.x
+ }
+ val ty = height - view.top + translationToCollapsedPosition.y
+ view.translationX = tx * (1f - expansionProgress)
+ view.translationY = ty * (1f - expansionProgress)
+ }
+
private fun applyConfigurationColors(configuration: Configuration) {
val nightModeFlags = configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
val isNightModeOn = nightModeFlags == Configuration.UI_MODE_NIGHT_YES
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleBarLocationOnDemandListener.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleBarLocationOnDemandListener.kt
new file mode 100644
index 0000000..ffe7c44
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleBarLocationOnDemandListener.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.bubbles.stashing
+
+import com.android.launcher3.taskbar.bubbles.BubbleBarController.BubbleBarLocationListener
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation
+
+/** On demand implementation of [BubbleBarLocationListener]. */
+class BubbleBarLocationOnDemandListener(
+ private val listenerProvider: () -> BubbleBarLocationListener
+) : BubbleBarLocationListener {
+
+ override fun onBubbleBarLocationAnimated(location: BubbleBarLocation) {
+ listenerProvider().onBubbleBarLocationAnimated(location)
+ }
+
+ override fun onBubbleBarLocationUpdated(location: BubbleBarLocation) {
+ listenerProvider().onBubbleBarLocationUpdated(location)
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt
index 9721792..a78890b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt
@@ -56,14 +56,29 @@
fun runAfterInit(action: Runnable)
}
+ /** Launcher states bubbles cares about */
+ enum class BubbleLauncherState {
+ /* When launcher is in overview */
+ OVERVIEW,
+ /* When launcher is on home */
+ HOME,
+ /* We're in an app */
+ IN_APP,
+ }
+
+ /** The current launcher state */
+ var launcherState: BubbleLauncherState
+
/** Whether bubble bar is currently stashed */
val isStashed: Boolean
/** Whether launcher enters or exits the home page. */
- var isBubblesShowingOnHome: Boolean
+ val isBubblesShowingOnHome: Boolean
+ get() = launcherState == BubbleLauncherState.HOME
/** Whether launcher enters or exits the overview page. */
- var isBubblesShowingOnOverview: Boolean
+ val isBubblesShowingOnOverview: Boolean
+ get() = launcherState == BubbleLauncherState.OVERVIEW
/** Updated when sysui locked state changes, when locked, bubble bar is not shown. */
var isSysuiLocked: Boolean
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashController.kt
index 7d6f7ad..722dfe7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashController.kt
@@ -26,6 +26,7 @@
import com.android.launcher3.taskbar.TaskbarInsetsController
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController
import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController
+import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.BubbleLauncherState
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.BAR_STASH_DURATION
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.BAR_TRANSLATION_DURATION
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.ControllersAfterInitAction
@@ -45,29 +46,21 @@
private lateinit var bubbleBarScaleAnimator: AnimatedFloat
private lateinit var controllersAfterInitAction: ControllersAfterInitAction
- override var isBubblesShowingOnHome: Boolean = false
- set(onHome) {
- if (field == onHome) return
- field = onHome
+ override var launcherState: BubbleLauncherState = BubbleLauncherState.IN_APP
+ set(state) {
+ if (field == state) return
+ val transitionFromHome = field == BubbleLauncherState.HOME
+ field = state
if (!bubbleBarViewController.hasBubbles()) {
// if there are no bubbles, there's nothing to show, so just return.
return
}
- if (onHome) {
- // When transition to home we should show collapse the bubble bar
- updateExpandedState(expand = false)
- }
- animateBubbleBarY()
- bubbleBarViewController.onBubbleBarConfigurationChanged(/* animate= */ true)
- }
-
- override var isBubblesShowingOnOverview: Boolean = false
- set(onOverview) {
- if (field == onOverview) return
- field = onOverview
- if (!onOverview) {
- // When transition from overview we should show collapse the bubble bar
- updateExpandedState(expand = false)
+ // If we're transitioning anywhere, bubble bar should be collapsed
+ updateExpandedState(expand = false)
+ if (transitionFromHome || field == BubbleLauncherState.HOME) {
+ // If we're transitioning to or from home, animate the Y because we're in hotseat
+ // on home but in persistent taskbar elsewhere so the position is different.
+ animateBubbleBarY()
}
bubbleBarViewController.onBubbleBarConfigurationChanged(/* animate= */ true)
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
index 4f0337d..fe3db30 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
@@ -32,10 +32,11 @@
import com.android.launcher3.anim.AnimatedFloat
import com.android.launcher3.anim.SpringAnimationBuilder
import com.android.launcher3.taskbar.TaskbarInsetsController
-import com.android.launcher3.taskbar.TaskbarStashController.TASKBAR_STASH_ALPHA_DURATION
import com.android.launcher3.taskbar.TaskbarStashController.TASKBAR_STASH_ALPHA_START_DELAY
+import com.android.launcher3.taskbar.TaskbarStashController.TRANSIENT_TASKBAR_STASH_ALPHA_DURATION
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController
import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController
+import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.BubbleLauncherState
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.BAR_STASH_DURATION
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.BAR_TRANSLATION_DURATION
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.ControllersAfterInitAction
@@ -81,36 +82,26 @@
override var isStashed: Boolean = false
@VisibleForTesting set
- override var isBubblesShowingOnHome: Boolean = false
- set(onHome) {
- if (field == onHome) return
- field = onHome
+ override var launcherState: BubbleLauncherState = BubbleLauncherState.IN_APP
+ set(state) {
+ if (field == state) return
+ field = state
if (!bubbleBarViewController.hasBubbles()) {
// if there are no bubbles, there's nothing to show, so just return.
return
}
- if (onHome) {
- updateStashedAndExpandedState(stash = false, expand = false)
- // When transitioning from app to home we need to animate the bubble bar
+ if (field == BubbleLauncherState.HOME) {
+ // When to home we need to animate the bubble bar
// here to align with hotseat center.
animateBubbleBarYToHotseat()
- } else if (!bubbleBarViewController.isExpanded) {
- updateStashedAndExpandedState(stash = true, expand = false)
- }
- bubbleBarViewController.onBubbleBarConfigurationChanged(/* animate= */ true)
- }
-
- override var isBubblesShowingOnOverview: Boolean = false
- set(onOverview) {
- if (field == onOverview) return
- field = onOverview
- if (onOverview) {
+ } else if (field == BubbleLauncherState.OVERVIEW) {
// When transitioning to overview we need to animate the bubble bar to align with
// the taskbar bottom.
animateBubbleBarYToTaskbar()
- } else {
- updateStashedAndExpandedState(stash = true, expand = false)
}
+ // Only stash if we're in an app, otherwise we're in home or overview where we should
+ // be un-stashed
+ updateStashedAndExpandedState(field == BubbleLauncherState.IN_APP, expand = false)
bubbleBarViewController.onBubbleBarConfigurationChanged(/* animate= */ true)
}
@@ -305,7 +296,8 @@
animatorSet.play(
createBackgroundAlphaAnimator(isStashed).apply {
- val alphaDuration = if (isStashed) duration else TASKBAR_STASH_ALPHA_DURATION
+ val alphaDuration =
+ if (isStashed) duration else TRANSIENT_TASKBAR_STASH_ALPHA_DURATION
val alphaDelay = if (isStashed) TASKBAR_STASH_ALPHA_START_DELAY else 0L
this.duration = max(0L, alphaDuration - alphaDelay)
this.startDelay = alphaDelay
@@ -317,7 +309,7 @@
bubbleBarBubbleAlpha
.animateToValue(getBarAlphaStart(isStashed), getBarAlphaEnd(isStashed))
.apply {
- this.duration = TASKBAR_STASH_ALPHA_DURATION
+ this.duration = TRANSIENT_TASKBAR_STASH_ALPHA_DURATION
this.startDelay = TASKBAR_STASH_ALPHA_START_DELAY
this.interpolator = LINEAR
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 14d391b..721c831 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -21,6 +21,7 @@
import static com.android.app.animation.Interpolators.FINAL_FRAME;
import static com.android.app.animation.Interpolators.INSTANT;
import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.launcher3.Flags.enableLargeDesktopWindowingTile;
import static com.android.launcher3.LauncherState.QUICK_SWITCH_FROM_HOME;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_HOME;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
@@ -31,6 +32,7 @@
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
+import static com.android.quickstep.views.RecentsView.DESKTOP_CAROUSEL_DETACH_PROGRESS;
import static com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS;
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
@@ -76,6 +78,10 @@
RECENTS_GRID_PROGRESS.set(mRecentsView,
state.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()) ? 1f : 0f);
TASK_THUMBNAIL_SPLASH_ALPHA.set(mRecentsView, state.showTaskThumbnailSplash() ? 1f : 0f);
+ if (enableLargeDesktopWindowingTile()) {
+ DESKTOP_CAROUSEL_DETACH_PROGRESS.set(mRecentsView,
+ state.detachDesktopCarousel() ? 1f : 0f);
+ }
}
@Override
@@ -86,7 +92,7 @@
}
setStateWithAnimationInternal(toState, config, builder);
builder.addEndListener(success -> {
- if (!success) {
+ if (!success && !toState.isRecentsViewVisible) {
mRecentsView.reset();
}
});
@@ -142,6 +148,12 @@
setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS,
toState.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()) ? 1f : 0f,
getOverviewInterpolator(fromState, toState));
+
+ if (enableLargeDesktopWindowingTile()) {
+ setter.setFloat(mRecentsView, DESKTOP_CAROUSEL_DETACH_PROGRESS,
+ toState.detachDesktopCarousel() ? 1f : 0f,
+ getOverviewInterpolator(fromState, toState));
+ }
}
private Interpolator getOverviewInterpolator(LauncherState fromState, LauncherState toState) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 5082c11..bc0ace2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -37,6 +37,7 @@
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
+import static com.android.launcher3.Utilities.isRtl;
import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
import static com.android.launcher3.config.FeatureFlags.enableSplitContextually;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
@@ -60,12 +61,12 @@
import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.QUICK_SWITCH_FROM_HOME_FAILED;
-import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.QUICK_SWITCH_FROM_HOME_FALLBACK;
import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback;
import static com.android.quickstep.util.SplitAnimationTimings.TABLET_HOME_TO_SPLIT;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50;
+import static com.android.wm.shell.Flags.enableBubbleAnything;
+import static com.android.wm.shell.Flags.enableBubbleBarInPersistentTaskBar;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -172,7 +173,7 @@
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.TouchInteractionService.TISBinder;
-import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.ActiveGestureProtoLogProxy;
import com.android.quickstep.util.AsyncClockEventDelegate;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.LauncherUnfoldAnimationController;
@@ -198,6 +199,7 @@
import com.android.systemui.unfold.dagger.UnfoldMain;
import com.android.systemui.unfold.progress.RemoteUnfoldTransitionReceiver;
import com.android.systemui.unfold.updates.RotationChangeProvider;
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import java.io.FileDescriptor;
@@ -239,6 +241,7 @@
private SplitSelectStateController mSplitSelectStateController;
private SplitWithKeyboardShortcutController mSplitWithKeyboardShortcutController;
private SplitToWorkspaceController mSplitToWorkspaceController;
+ private BubbleBarLocation mBubbleBarLocation;
/**
* If Launcher restarted while in the middle of an Overview split select, it needs this data to
@@ -463,7 +466,7 @@
if (Flags.enablePrivateSpace()) {
shortcuts.add(UNINSTALL_APP);
}
- if (com.android.wm.shell.Flags.enableBubbleAnything()) {
+ if (enableBubbleAnything()) {
shortcuts.add(BUBBLE_SHORTCUT);
}
return shortcuts.stream();
@@ -595,11 +598,8 @@
TaskView taskToLaunch = currentPageTask;
if (currentPageTask == null) {
taskToLaunch = fallbackTask;
- ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
- "Quick switch from home fallback case: The TaskView at index ")
- .append(rv.getCurrentPage())
- .append(" is missing."),
- QUICK_SWITCH_FROM_HOME_FALLBACK);
+ ActiveGestureProtoLogProxy.logQuickSwitchFromHomeFallback(
+ rv.getCurrentPage());
}
taskToLaunch.launchWithoutAnimation(success -> {
if (!success) {
@@ -610,11 +610,7 @@
return Unit.INSTANCE;
});
} else {
- ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
- "Quick switch from home failed: TaskViews at indices ")
- .append(rv.getCurrentPage())
- .append(" and 0 are missing."),
- QUICK_SWITCH_FROM_HOME_FAILED);
+ ActiveGestureProtoLogProxy.logQuickSwitchFromHomeFailed(rv.getCurrentPage());
getStateManager().goToState(NORMAL);
}
break;
@@ -1101,6 +1097,29 @@
return mTaskbarUIController;
}
+ /** Provides the translation X for the hotseat item. */
+ public int getHotseatItemTranslationX(ItemInfo itemInfo) {
+ int translationX = 0;
+ if (isBubbleBarEnabled()
+ && enableBubbleBarInPersistentTaskBar()
+ && mBubbleBarLocation != null) {
+ boolean isBubblesOnLeft = mBubbleBarLocation.isOnLeft(isRtl(getResources()));
+ translationX += mDeviceProfile
+ .getHotseatTranslationXForBubbleBar(/* isNavbarOnRight = */ isBubblesOnLeft);
+ }
+ if (isBubbleBarEnabled() && hasBubbles()) {
+ // TODO(368379159) : create a class to reuse computation logic
+ float adjustedBorderSpace =
+ mDeviceProfile.getHotseatAdjustedBorderSpaceForBubbleBar(this);
+ if (Float.compare(adjustedBorderSpace, 0f) != 0) {
+ float borderSpaceDelta = adjustedBorderSpace - mDeviceProfile.hotseatBorderSpace;
+ translationX +=
+ (int) (mDeviceProfile.iconSizePx + itemInfo.cellX * borderSpaceDelta);
+ }
+ }
+ return translationX;
+ }
+
public SplitToWorkspaceController getSplitToWorkspaceController() {
return mSplitToWorkspaceController;
}
@@ -1341,7 +1360,7 @@
/* callback= */ success -> mSplitSelectStateController.resetState(),
/* freezeTaskList= */ false,
groupTask.mSplitBounds == null
- ? SNAP_TO_50_50
+ ? SNAP_TO_2_50_50
: groupTask.mSplitBounds.snapPosition,
remoteTransition);
}
@@ -1406,6 +1425,11 @@
SystemUiProxy.INSTANCE.get(this).showAppBubble(intent);
}
+ /** Sets the location of the bubble bar */
+ public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
+ mBubbleBarLocation = bubbleBarLocation;
+ }
+
private static final class LauncherTaskViewController extends
TaskViewTouchController<QuickstepLauncher> {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 235ec7b..111069f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -155,6 +155,9 @@
0,
timings.getGridSlideSecondaryInterpolator());
+ mRecentsView.handleDesktopTaskInSplitSelectState(builder,
+ timings.getDesktopTaskFadeInterpolator());
+
if (!animate) {
AnimatorSet as = builder.buildAnim();
as.start();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index 18d717f..e87ac2f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -90,6 +90,11 @@
}
@Override
+ public boolean detachDesktopCarousel() {
+ return true;
+ }
+
+ @Override
protected float getDepthUnchecked(Context context) {
if (Launcher.getLauncher(context).areDesktopTasksVisible()) {
// Don't blur the background while desktop tasks are visible
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index b165cdd..c48ba4f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -166,6 +166,11 @@
}
@Override
+ public boolean detachDesktopCarousel() {
+ return false;
+ }
+
+ @Override
public boolean disallowTaskbarGlobalDrag() {
// Disable global drag in overview
return true;
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index fe1d015..dc7ed24 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -56,11 +56,7 @@
import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_STARTED;
import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
-import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.CANCEL_RECENTS_ANIMATION;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.EXPECTING_TASK_APPEARED;
-import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.INVALID_VELOCITY_ON_SWIPE_UP;
-import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.LAUNCHER_DESTROYED;
-import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_SETTLED_ON_END_TARGET;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
@@ -128,6 +124,7 @@
import com.android.quickstep.fallback.window.RecentsWindowManager;
import com.android.quickstep.util.ActiveGestureErrorDetector;
import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.ActiveGestureProtoLogProxy;
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.InputConsumerProxy;
@@ -203,7 +200,7 @@
private boolean mRecentsViewScrollLinked = false;
private final Runnable mLauncherOnDestroyCallback = () -> {
- ActiveGestureLog.INSTANCE.addLog("Launcher destroyed", LAUNCHER_DESTROYED);
+ ActiveGestureProtoLogProxy.logLauncherDestroyed();
mRecentsView = null;
mContainer = null;
mStateCallback.clearState(STATE_LAUNCHER_PRESENT);
@@ -984,9 +981,7 @@
@Override
public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
- ActiveGestureLog.INSTANCE.addLog(
- /* event= */ "cancelRecentsAnimation",
- /* gestureEvent= */ CANCEL_RECENTS_ANIMATION);
+ ActiveGestureProtoLogProxy.logAbsSwipeUpHandlerOnRecentsAnimationCanceled();
mActivityInitListener.unregister("AbsSwipeUpHandler.onRecentsAnimationCanceled");
mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
// Defer clearing the controller and the targets until after we've updated the state
@@ -1154,12 +1149,18 @@
if (endTarget != NEW_TASK) {
InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_QUICK_SWITCH);
+ } else {
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_QUICK_SWITCH);
}
if (endTarget != HOME) {
InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME);
+ } else {
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME);
}
if (endTarget != RECENTS) {
InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS);
+ } else {
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS);
}
switch (endTarget) {
@@ -1190,10 +1191,7 @@
// Resets this value as the gesture is now complete.
mContainerInterface.getTaskbarController().setUserIsNotGoingHome(false);
}
- ActiveGestureLog.INSTANCE.addLog(
- new ActiveGestureLog.CompoundString("onSettledOnEndTarget ")
- .append(endTarget.name()),
- /* gestureEvent= */ ON_SETTLED_ON_END_TARGET);
+ ActiveGestureProtoLogProxy.logOnSettledOnEndTarget(endTarget.name());
}
/** @return Whether this was the task we were waiting to appear, and thus handled it. */
@@ -1209,11 +1207,9 @@
failureReason.append("STATE_START_NEW_TASK was never set");
} else {
TaskInfo taskInfo = appearedTaskTargets[0].taskInfo;
- failureReason.append("Unexpected task appeared")
- .append(" id=")
- .append(taskInfo.taskId)
- .append(" pkg=")
- .append(taskInfo.baseIntent.getComponent().getPackageName());
+ failureReason.append("Unexpected task appeared id=%d, pkg=%s",
+ taskInfo.taskId,
+ taskInfo.baseIntent.getComponent().getPackageName());
}
return false;
}
@@ -1227,19 +1223,10 @@
private GestureEndTarget calculateEndTarget(
PointF velocityPxPerMs, float endVelocityPxPerMs, boolean isFlingY, boolean isCancel) {
-
- ActiveGestureErrorDetector.GestureEvent gestureEvent =
- velocityPxPerMs.x == 0 && velocityPxPerMs.y == 0
- ? INVALID_VELOCITY_ON_SWIPE_UP
- : null;
- ActiveGestureLog.INSTANCE.addLog(
- new ActiveGestureLog.CompoundString("calculateEndTarget: velocities=(x=")
- .append(dpiFromPx(velocityPxPerMs.x))
- .append("dp/ms, y=")
- .append(dpiFromPx(velocityPxPerMs.y))
- .append("dp/ms), angle=")
- .append(Math.toDegrees(Math.atan2(
- -velocityPxPerMs.y, velocityPxPerMs.x))), gestureEvent);
+ ActiveGestureProtoLogProxy.logOnCalculateEndTarget(
+ dpiFromPx(velocityPxPerMs.x),
+ dpiFromPx(velocityPxPerMs.y),
+ Math.toDegrees(Math.atan2(-velocityPxPerMs.y, velocityPxPerMs.x)));
if (mGestureState.isHandlingAtomicEvent()) {
// Button mode, this is only used to go to recents.
@@ -1990,9 +1977,7 @@
* handler (in case of quick switch).
*/
private void cancelCurrentAnimation() {
- ActiveGestureLog.INSTANCE.addLog(
- "AbsSwipeUpHandler.cancelCurrentAnimation",
- ActiveGestureErrorDetector.GestureEvent.CANCEL_CURRENT_ANIMATION);
+ ActiveGestureProtoLogProxy.logAbsSwipeUpHandlerCancelCurrentAnimation();
mCanceled = true;
mCurrentShift.cancelAnimation();
@@ -2293,18 +2278,15 @@
TaskView nextTask = mRecentsView == null ? null : mRecentsView.getNextPageTaskView();
if (nextTask != null) {
int[] taskIds = nextTask.getTaskIds();
- ActiveGestureLog.CompoundString nextTaskLog = new ActiveGestureLog.CompoundString(
- "Launching task: ");
+ ActiveGestureLog.CompoundString nextTaskLog =
+ ActiveGestureLog.CompoundString.newEmptyString();
for (TaskContainer container : nextTask.getTaskContainers()) {
if (container == null) {
continue;
}
- nextTaskLog
- .append("[id: ")
- .append(container.getTask().key.id)
- .append(", pkg: ")
- .append(container.getTask().key.getPackageName())
- .append("] | ");
+ nextTaskLog.append("[id: %d, pkg: %s] | ",
+ container.getTask().key.id,
+ container.getTask().key.getPackageName());
}
mGestureState.updateLastStartedTaskIds(taskIds);
boolean hasTaskPreviouslyAppeared = Arrays.stream(taskIds).anyMatch(
@@ -2313,7 +2295,7 @@
if (!hasTaskPreviouslyAppeared) {
ActiveGestureLog.INSTANCE.trackEvent(EXPECTING_TASK_APPEARED);
}
- ActiveGestureLog.INSTANCE.addLog(nextTaskLog);
+ ActiveGestureProtoLogProxy.logStartNewTask(nextTaskLog);
nextTask.launchWithoutAnimation(true, success -> {
resultCallback.accept(success);
if (success) {
@@ -2391,31 +2373,28 @@
return;
}
final Runnable onFinishComplete = () -> {
- ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
- "AbsSwipeUpHandler.onTasksAppeared: ")
- .append("force finish recents animation complete; clearing state callback."));
+ ActiveGestureProtoLogProxy.logAbsSwipeUpHandlerOnTasksAppeared();
mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
};
- ActiveGestureLog.CompoundString forceFinishReason = new ActiveGestureLog.CompoundString(
- "Forcefully finishing recents animation: ");
+ ActiveGestureLog.CompoundString forceFinishReason =
+ ActiveGestureLog.CompoundString.newEmptyString();
if (!mStateCallback.hasStates(STATE_GESTURE_COMPLETED)
&& !hasStartedTaskBefore(appearedTaskTargets)) {
// This is a special case, if a task is started mid-gesture that wasn't a part of a
// previous quickswitch task launch, then cancel the animation back to the app
RemoteAnimationTarget appearedTaskTarget = appearedTaskTargets[0];
TaskInfo taskInfo = appearedTaskTarget.taskInfo;
- ActiveGestureLog.INSTANCE.addLog(forceFinishReason
- .append("Unexpected task appeared id=")
- .append(taskInfo.taskId)
- .append(" pkg=")
- .append(taskInfo.baseIntent.getComponent().getPackageName()));
+ ActiveGestureProtoLogProxy.logUnexpectedTaskAppeared(
+ taskInfo.taskId,
+ taskInfo.baseIntent.getComponent().getPackageName());
finishRecentsAnimationOnTasksAppeared(onFinishComplete);
return;
}
ActiveGestureLog.CompoundString handleTaskFailureReason =
- new ActiveGestureLog.CompoundString("handleTaskAppeared check failed: ");
+ ActiveGestureLog.CompoundString.newEmptyString();
if (!handleTaskAppeared(appearedTaskTargets, handleTaskFailureReason)) {
- ActiveGestureLog.INSTANCE.addLog(forceFinishReason.append(handleTaskFailureReason));
+ forceFinishReason.append(handleTaskFailureReason);
+ ActiveGestureProtoLogProxy.logHandleTaskAppearedFailed(forceFinishReason);
finishRecentsAnimationOnTasksAppeared(onFinishComplete);
return;
}
@@ -2423,8 +2402,8 @@
.filter(mGestureState.mLastStartedTaskIdPredicate)
.toArray(RemoteAnimationTarget[]::new);
if (taskTargets.length == 0) {
- ActiveGestureLog.INSTANCE.addLog(
- forceFinishReason.append("No appeared task matching started task id"));
+ forceFinishReason.append("No appeared task matching started task id");
+ ActiveGestureProtoLogProxy.logHandleTaskAppearedFailed(forceFinishReason);
finishRecentsAnimationOnTasksAppeared(onFinishComplete);
return;
}
@@ -2433,12 +2412,14 @@
? null : mRecentsView.getTaskViewByTaskId(taskTarget.taskId);
if (taskView == null || taskView.getTaskContainers().stream().noneMatch(
TaskContainer::getShouldShowSplashView)) {
- ActiveGestureLog.INSTANCE.addLog(forceFinishReason.append("Splash not needed"));
+ forceFinishReason.append("Splash not needed");
+ ActiveGestureProtoLogProxy.logHandleTaskAppearedFailed(forceFinishReason);
finishRecentsAnimationOnTasksAppeared(onFinishComplete);
return;
}
if (mContainer == null) {
- ActiveGestureLog.INSTANCE.addLog(forceFinishReason.append("Activity destroyed"));
+ forceFinishReason.append("Activity destroyed");
+ ActiveGestureProtoLogProxy.logHandleTaskAppearedFailed(forceFinishReason);
finishRecentsAnimationOnTasksAppeared(onFinishComplete);
return;
}
@@ -2494,7 +2475,7 @@
if (mRecentsAnimationController != null) {
mRecentsAnimationController.finish(false /* toRecents */, onFinishComplete);
}
- ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimationOnTasksAppeared");
+ ActiveGestureProtoLogProxy.logFinishRecentsAnimationOnTasksAppeared();
}
/**
diff --git a/quickstep/src/com/android/quickstep/FocusState.kt b/quickstep/src/com/android/quickstep/FocusState.kt
new file mode 100644
index 0000000..ba3991f
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/FocusState.kt
@@ -0,0 +1,63 @@
+/*
+ * 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
+
+import android.os.RemoteException
+import android.util.Log
+import android.view.Display.DEFAULT_DISPLAY
+import com.android.launcher3.util.Executors
+import com.android.wm.shell.shared.IFocusTransitionListener.Stub
+import com.android.wm.shell.shared.IShellTransitions
+
+/** Class to track focus state of displays and windows */
+class FocusState {
+
+ var focusedDisplayId = DEFAULT_DISPLAY
+ private set
+
+ private var listeners = mutableSetOf<FocusChangeListener>()
+
+ fun addListener(l: FocusChangeListener) = listeners.add(l)
+
+ fun removeListener(l: FocusChangeListener) = listeners.remove(l)
+
+ fun init(transitions: IShellTransitions?) {
+ try {
+ transitions?.setFocusTransitionListener(
+ object : Stub() {
+ override fun onFocusedDisplayChanged(displayId: Int) {
+ Executors.MAIN_EXECUTOR.execute {
+ listeners.forEach { it.onFocusedDisplayChanged(displayId) }
+ }
+ }
+ }
+ )
+ } catch (e: RemoteException) {
+ Log.w(TAG, "Failed call setFocusTransitionListener", e)
+ }
+ }
+
+ interface FocusChangeListener {
+ fun onFocusedDisplayChanged(displayId: Int)
+ }
+
+ override fun toString() = "{FocusState focusedDisplayId=$focusedDisplayId}"
+
+ companion object {
+ private const val TAG = "FocusState"
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index 22967cb..2892d2c 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -24,7 +24,6 @@
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
-import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_ALL_APPS;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_HOME;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_NEW_TASK;
@@ -42,6 +41,7 @@
import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
import com.android.quickstep.util.ActiveGestureErrorDetector;
import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.ActiveGestureProtoLogProxy;
import com.android.quickstep.views.RecentsViewContainer;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -411,10 +411,7 @@
public void setEndTarget(GestureEndTarget target, boolean isAtomic) {
mEndTarget = target;
mStateCallback.setState(STATE_END_TARGET_SET);
- ActiveGestureLog.INSTANCE.addLog(
- new ActiveGestureLog.CompoundString("setEndTarget ")
- .append(mEndTarget.name()),
- /* gestureEvent= */ SET_END_TARGET);
+ ActiveGestureProtoLogProxy.logSetEndTarget(mEndTarget.name());
switch (mEndTarget) {
case HOME:
ActiveGestureLog.INSTANCE.trackEvent(SET_END_TARGET_HOME);
diff --git a/quickstep/src/com/android/quickstep/MultiStateCallback.java b/quickstep/src/com/android/quickstep/MultiStateCallback.java
index df42efc..a9f196d 100644
--- a/quickstep/src/com/android/quickstep/MultiStateCallback.java
+++ b/quickstep/src/com/android/quickstep/MultiStateCallback.java
@@ -28,6 +28,7 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.quickstep.util.ActiveGestureErrorDetector;
import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.ActiveGestureProtoLogProxy;
import java.util.ArrayList;
import java.util.LinkedList;
@@ -114,10 +115,9 @@
if (gestureEvent == null) {
continue;
}
- if (gestureEvent.mLogEvent && gestureEvent.mTrackEvent) {
- ActiveGestureLog.INSTANCE.addLog(gestureEvent.name(), gestureEvent);
- } else if (gestureEvent.mLogEvent) {
- ActiveGestureLog.INSTANCE.addLog(gestureEvent.name());
+ if (gestureEvent.mLogEvent) {
+ ActiveGestureProtoLogProxy.logDynamicString(
+ gestureEvent.name(), gestureEvent.mTrackEvent ? gestureEvent : null);
} else if (gestureEvent.mTrackEvent) {
ActiveGestureLog.INSTANCE.trackEvent(gestureEvent);
}
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
index 66224ae..a33e5c0 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
@@ -45,6 +45,7 @@
import com.android.quickstep.OverviewCommandHelper.CommandType.SHOW
import com.android.quickstep.OverviewCommandHelper.CommandType.TOGGLE
import com.android.quickstep.util.ActiveGestureLog
+import com.android.quickstep.util.ActiveGestureProtoLogProxy
import com.android.quickstep.views.RecentsView
import com.android.quickstep.views.TaskView
import com.android.systemui.shared.recents.model.ThumbnailData
@@ -280,7 +281,7 @@
keyboardTaskFocusIndex = 0
}
HOME -> {
- ActiveGestureLog.INSTANCE.addLog("OverviewCommandHelper.executeCommand(HOME)")
+ ActiveGestureProtoLogProxy.logExecuteHomeCommand()
// Although IActivityTaskManager$Stub$Proxy.startActivity is a slow binder call,
// we should still call it on main thread because launcher is waiting for
// ActivityTaskManager to resume it. Also calling startActivity() on bg thread
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index a69b831..66112c1 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -42,7 +42,7 @@
import com.android.launcher3.Flags;
import com.android.launcher3.R;
import com.android.launcher3.util.SimpleBroadcastReceiver;
-import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.ActiveGestureProtoLogProxy;
import com.android.systemui.shared.system.PackageManagerWrapper;
import java.io.PrintWriter;
@@ -305,10 +305,11 @@
* Starts the intent for the current home activity.
*/
public static void startHomeIntentSafely(
- @NonNull Context context, @NonNull Intent homeIntent, @Nullable Bundle options,
+ @NonNull Context context,
+ @NonNull Intent homeIntent,
+ @Nullable Bundle options,
@NonNull String reason) {
- ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
- "OverviewComponentObserver.startHomeIntent: ").append(reason));
+ ActiveGestureProtoLogProxy.logStartHomeIntent(reason);
try {
context.startActivity(homeIntent, options);
} catch (NullPointerException | ActivityNotFoundException | SecurityException e) {
diff --git a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
index f4e68dc..334bead 100644
--- a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
+++ b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
@@ -25,6 +25,7 @@
import com.android.launcher3.BuildConfig;
import com.android.launcher3.MainProcessInitializer;
+import com.android.quickstep.util.QuickstepProtoLogGroup;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
@SuppressWarnings("unused")
@@ -69,5 +70,7 @@
call.descriptor + " called on main thread under " + call.activeTrace
+ " stackTrace: " + call.stackTrace));
}
+
+ QuickstepProtoLogGroup.initProtoLog();
}
}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index 0c5806b..fc11812 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -19,9 +19,6 @@
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_CANCEL_RECENTS_ANIMATION;
-import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_FINISH_RECENTS_ANIMATION;
-import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_START_RECENTS_ANIMATION;
import android.graphics.Rect;
import android.os.Bundle;
@@ -34,8 +31,7 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.util.Preconditions;
-import com.android.quickstep.util.ActiveGestureErrorDetector;
-import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.ActiveGestureProtoLogProxy;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
@@ -107,11 +103,8 @@
.filter(app -> app.mode == MODE_CLOSING)
.count();
if (appCount == 0) {
+ ActiveGestureProtoLogProxy.logOnRecentsAnimationStartCancelled();
// Edge case, if there are no closing app targets, then Launcher has nothing to handle
- ActiveGestureLog.INSTANCE.addLog(
- /* event= */ "RecentsAnimationCallbacks.onAnimationStart (canceled)",
- /* extras= */ 0,
- /* gestureEvent= */ ON_START_RECENTS_ANIMATION);
notifyAnimationCanceled();
animationController.finish(false /* toHome */, false /* sendUserLeaveHint */,
null /* finishCb */);
@@ -138,10 +131,7 @@
extras);
Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
- ActiveGestureLog.INSTANCE.addLog(
- /* event= */ "RecentsAnimationCallbacks.onAnimationStart",
- /* extras= */ targets.apps.length,
- /* gestureEvent= */ ON_START_RECENTS_ANIMATION);
+ ActiveGestureProtoLogProxy.logOnRecentsAnimationStart(targets.apps.length);
for (RecentsAnimationListener listener : getListeners()) {
listener.onRecentsAnimationStart(mController, targets);
}
@@ -153,9 +143,7 @@
@Override
public final void onAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
- ActiveGestureLog.INSTANCE.addLog(
- /* event= */ "RecentsAnimationCallbacks.onAnimationCanceled",
- /* gestureEvent= */ ON_CANCEL_RECENTS_ANIMATION);
+ ActiveGestureProtoLogProxy.logRecentsAnimationCallbacksOnAnimationCancelled();
for (RecentsAnimationListener listener : getListeners()) {
listener.onRecentsAnimationCanceled(thumbnailDatas);
}
@@ -166,8 +154,7 @@
@Override
public void onTasksAppeared(RemoteAnimationTarget[] apps) {
Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
- ActiveGestureLog.INSTANCE.addLog("RecentsAnimationCallbacks.onTasksAppeared",
- ActiveGestureErrorDetector.GestureEvent.TASK_APPEARED);
+ ActiveGestureProtoLogProxy.logRecentsAnimationCallbacksOnTasksAppeared();
for (RecentsAnimationListener listener : getListeners()) {
listener.onTasksAppeared(apps);
}
@@ -176,9 +163,7 @@
private void onAnimationFinished(RecentsAnimationController controller) {
Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
- ActiveGestureLog.INSTANCE.addLog(
- /* event= */ "RecentsAnimationCallbacks.onAnimationFinished",
- ON_FINISH_RECENTS_ANIMATION);
+ ActiveGestureProtoLogProxy.logAbsSwipeUpHandlerOnRecentsAnimationFinished();
for (RecentsAnimationListener listener : getListeners()) {
listener.onRecentsAnimationFinished(controller);
}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 190d526..dcb0108 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -17,7 +17,6 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.FINISH_RECENTS_ANIMATION;
import android.os.Bundle;
import android.os.RemoteException;
@@ -32,7 +31,7 @@
import com.android.internal.os.IResultReceiver;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.RunnableList;
-import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.ActiveGestureProtoLogProxy;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
@@ -132,10 +131,7 @@
// trigger the callback to be called immediately
return;
}
- ActiveGestureLog.INSTANCE.addLog(
- /* event= */ "finishRecentsAnimation",
- /* extras= */ toRecents,
- /* gestureEvent= */ FINISH_RECENTS_ANIMATION);
+ ActiveGestureProtoLogProxy.logFinishRecentsAnimation(toRecents);
// Finish not yet requested
mFinishRequested = true;
mFinishTargetIsLauncher = toRecents;
@@ -144,7 +140,7 @@
mController.finish(toRecents, sendUserLeaveHint, new IResultReceiver.Stub() {
@Override
public void send(int i, Bundle bundle) throws RemoteException {
- ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation-callback");
+ ActiveGestureProtoLogProxy.logFinishRecentsAnimationCallback();
MAIN_EXECUTOR.execute(() -> {
mPendingFinishCallbacks.executeAllAndDestroy();
});
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index f9b4dab..a55cf18 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -21,7 +21,6 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
-import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.RECENT_TASKS_MISSING;
import static com.android.quickstep.util.LogUtils.splitFailureMessage;
import android.app.ActivityManager;
@@ -55,6 +54,7 @@
import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
import com.android.internal.logging.InstanceId;
@@ -63,7 +63,7 @@
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.SafeCloseable;
-import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.ActiveGestureProtoLogProxy;
import com.android.quickstep.util.AssistUtils;
import com.android.quickstep.util.unfold.ProxyUnfoldTransitionProvider;
import com.android.systemui.shared.recents.ISystemUiProxy;
@@ -161,6 +161,7 @@
private IRemoteAnimationRunner mBackToLauncherRunner;
private IDragAndDrop mDragAndDrop;
private final HomeVisibilityState mHomeVisibilityState = new HomeVisibilityState();
+ private final FocusState mFocusState = new FocusState();
// Used to dedupe calls to SystemUI
private int mLastShelfHeight;
@@ -187,7 +188,8 @@
@Nullable
private final ProxyUnfoldTransitionProvider mUnfoldTransitionProvider;
- private SystemUiProxy(Context context) {
+ @VisibleForTesting
+ protected SystemUiProxy(Context context) {
mContext = context;
mAsyncHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::handleMessageAsync);
final Intent baseIntent = new Intent().setPackage(mContext.getPackageName());
@@ -299,6 +301,7 @@
registerSplitScreenListener(mSplitScreenListener);
registerSplitSelectListener(mSplitSelectListener);
mHomeVisibilityState.init(mShellTransitions);
+ mFocusState.init(mShellTransitions);
setStartingWindowListener(mStartingWindowListener);
setLauncherUnlockAnimationController(
mLauncherActivityClass, mLauncherUnlockAnimationController);
@@ -1143,6 +1146,10 @@
return mHomeVisibilityState;
}
+ public FocusState getFocusState() {
+ return mFocusState;
+ }
+
/**
* Returns a surface which can be used to attach overlays to home task or null if
* the task doesn't exist or sysui is not connected
@@ -1518,7 +1525,7 @@
public boolean startRecentsActivity(Intent intent, ActivityOptions options,
RecentsAnimationListener listener) {
if (mRecentTasks == null) {
- ActiveGestureLog.INSTANCE.addLog("Null mRecentTasks", RECENT_TASKS_MISSING);
+ ActiveGestureProtoLogProxy.logRecentTasksMissing();
return false;
}
final IRecentsAnimationRunner runner = new IRecentsAnimationRunner.Stub() {
@@ -1591,6 +1598,7 @@
pw.println("\tmOneHanded=" + mOneHanded);
pw.println("\tmShellTransitions=" + mShellTransitions);
pw.println("\tmHomeVisibilityState=" + mHomeVisibilityState);
+ pw.println("\tmFocusState=" + mFocusState);
pw.println("\tmStartingWindow=" + mStartingWindow);
pw.println("\tmStartingWindowListener=" + mStartingWindowListener);
pw.println("\tmSysuiUnlockAnimationController=" + mSysuiUnlockAnimationController);
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 4beb99a..98d7628 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -26,7 +26,6 @@
import static com.android.quickstep.GestureState.STATE_END_TARGET_ANIMATION_FINISHED;
import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_INITIALIZED;
import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_STARTED;
-import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.START_RECENTS_ANIMATION;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
@@ -49,7 +48,7 @@
import com.android.launcher3.taskbar.TaskbarUIController;
import com.android.launcher3.util.DisplayController;
import com.android.quickstep.fallback.window.RecentsWindowManager;
-import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.ActiveGestureProtoLogProxy;
import com.android.quickstep.util.SystemUiFlagUtils;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -137,9 +136,7 @@
@UiThread
public RecentsAnimationCallbacks startRecentsAnimation(@NonNull GestureState gestureState,
Intent intent, RecentsAnimationCallbacks.RecentsAnimationListener listener) {
- ActiveGestureLog.INSTANCE.addLog(
- /* event= */ "startRecentsAnimation",
- /* gestureEvent= */ START_RECENTS_ANIMATION);
+ ActiveGestureProtoLogProxy.logStartRecentsAnimation();
// Notify if recents animation is still running
if (mController != null) {
String msg = "New recents animation started before old animation completed";
@@ -169,9 +166,8 @@
public void onRecentsAnimationStart(RecentsAnimationController controller,
RecentsAnimationTargets targets) {
if (enableHandleDelayedGestureCallbacks() && mRecentsAnimationStartPending) {
- ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
- "TaskAnimationManager.startRecentsAnimation(onRecentsAnimationStart): ")
- .append("Setting mRecentsAnimationStartPending = false"));
+ ActiveGestureProtoLogProxy.logStartRecentsAnimationCallback(
+ "onRecentsAnimationStart");
mRecentsAnimationStartPending = false;
}
if (mCallbacks == null) {
@@ -215,10 +211,8 @@
@Override
public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
if (enableHandleDelayedGestureCallbacks() && mRecentsAnimationStartPending) {
- ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
- "TaskAnimationManager.startRecentsAnimation")
- .append("(onRecentsAnimationCanceled): ")
- .append("Setting mRecentsAnimationStartPending = false"));
+ ActiveGestureProtoLogProxy.logStartRecentsAnimationCallback(
+ "onRecentsAnimationCanceled");
mRecentsAnimationStartPending = false;
}
cleanUpRecentsAnimation(newCallbacks);
@@ -227,10 +221,8 @@
@Override
public void onRecentsAnimationFinished(RecentsAnimationController controller) {
if (enableHandleDelayedGestureCallbacks() && mRecentsAnimationStartPending) {
- ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
- "TaskAnimationManager.startRecentsAnimation")
- .append("(onRecentsAnimationFinished): ")
- .append("Setting mRecentsAnimationStartPending = false"));
+ ActiveGestureProtoLogProxy.logStartRecentsAnimationCallback(
+ "onRecentsAnimationFinished");
mRecentsAnimationStartPending = false;
}
cleanUpRecentsAnimation(newCallbacks);
@@ -276,16 +268,14 @@
RecentsView recentsView =
containerInterface.getCreatedContainer().getOverviewPanel();
if (recentsView != null) {
- ActiveGestureLog.INSTANCE.addLog(
- new ActiveGestureLog.CompoundString("Launching side task id=")
- .append(appearedTaskTarget.taskId));
+ ActiveGestureProtoLogProxy.logLaunchingSideTask(appearedTaskTarget.taskId);
recentsView.launchSideTaskInLiveTileMode(appearedTaskTarget.taskId,
appearedTaskTargets,
new RemoteAnimationTarget[0] /* wallpaper */,
nonAppTargets /* nonApps */);
return;
} else {
- ActiveGestureLog.INSTANCE.addLog("Unable to launch side task (no recents)");
+ ActiveGestureProtoLogProxy.logLaunchingSideTaskFailed();
}
} else if (nonAppTargets.length > 0) {
TaskViewUtils.createSplitAuxiliarySurfacesAnimator(nonAppTargets /* nonApps */,
@@ -340,10 +330,8 @@
.startRecentsActivity(intent, options, mCallbacks);
}
if (enableHandleDelayedGestureCallbacks()) {
- ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
- "TaskAnimationManager.startRecentsAnimation: ")
- .append("Setting mRecentsAnimationStartPending = ")
- .append(mRecentsAnimationStartPending));
+ ActiveGestureProtoLogProxy.logSettingRecentsAnimationStartPending(
+ mRecentsAnimationStartPending);
}
gestureState.setState(STATE_RECENTS_ANIMATION_INITIALIZED);
return mCallbacks;
@@ -353,7 +341,7 @@
* Continues the existing running recents animation for a new gesture.
*/
public RecentsAnimationCallbacks continueRecentsAnimation(GestureState gestureState) {
- ActiveGestureLog.INSTANCE.addLog(/* event= */ "continueRecentsAnimation");
+ ActiveGestureProtoLogProxy.logContinueRecentsAnimation();
mCallbacks.removeListener(mLastGestureState);
mLastGestureState = gestureState;
mCallbacks.addListener(gestureState);
@@ -435,8 +423,7 @@
public void finishRunningRecentsAnimation(boolean toHome, boolean forceFinish,
Runnable forceFinishCb) {
if (mController != null) {
- ActiveGestureLog.INSTANCE.addLog(
- /* event= */ "finishRunningRecentsAnimation", toHome);
+ ActiveGestureProtoLogProxy.logFinishRunningRecentsAnimation(toHome);
if (forceFinish) {
mController.finishController(toHome, forceFinishCb, false /* sendUserLeaveHint */,
true /* forceFinish */);
@@ -473,11 +460,10 @@
*/
private void cleanUpRecentsAnimation(RecentsAnimationCallbacks targetCallbacks) {
if (mCallbacks != targetCallbacks) {
- ActiveGestureLog.INSTANCE.addLog(
- /* event= */ "cleanUpRecentsAnimation skipped due to wrong callbacks");
+ ActiveGestureProtoLogProxy.logCleanUpRecentsAnimationSkipped();
return;
}
- ActiveGestureLog.INSTANCE.addLog(/* event= */ "cleanUpRecentsAnimation");
+ ActiveGestureProtoLogProxy.logCleanUpRecentsAnimation();
if (mLiveTileCleanUpHandler != null) {
mLiveTileCleanUpHandler.run();
mLiveTileCleanUpHandler = null;
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java
index 1f6c02c..91fa72d 100644
--- a/quickstep/src/com/android/quickstep/TaskIconCache.java
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.java
@@ -226,7 +226,7 @@
synchronized (mDefaultIcons) {
if (mDefaultIconBase == null) {
try (BaseIconFactory bif = getIconFactory()) {
- mDefaultIconBase = bif.makeDefaultIcon();
+ mDefaultIconBase = bif.makeDefaultIcon(mIconProvider);
}
}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index c5791fa..f0943dc 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -36,11 +36,6 @@
import static com.android.quickstep.GestureState.TrackpadGestureType.getTrackpadGestureType;
import static com.android.quickstep.InputConsumer.TYPE_CURSOR_HOVER;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER;
-import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_DOWN;
-import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_MOVE;
-import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_UP;
-import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.NAVIGATION_MODE_SWITCHED;
-import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.RECENTS_ANIMATION_START_PENDING;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER;
@@ -126,6 +121,7 @@
import com.android.quickstep.inputconsumers.TrackpadStatusBarInputConsumer;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.ActiveGestureLog.CompoundString;
+import com.android.quickstep.util.ActiveGestureProtoLogProxy;
import com.android.quickstep.util.AssistStateManager;
import com.android.quickstep.util.AssistUtils;
import com.android.quickstep.views.RecentsViewContainer;
@@ -655,6 +651,8 @@
@Override
public void onCreate() {
super.onCreate();
+ Log.d(TAG, "onCreate: user=" + getUserId()
+ + " instance=" + System.identityHashCode(this));
// Initialize anything here that is needed in direct boot mode.
// Everything else should be initialized in onUserUnlocked() below.
mMainChoreographer = Choreographer.getInstance();
@@ -688,7 +686,8 @@
}
private void disposeEventHandlers(String reason) {
- Log.d(TAG, "disposeEventHandlers: Reason: " + reason);
+ Log.d(TAG, "disposeEventHandlers: Reason: " + reason
+ + " instance=" + System.identityHashCode(this));
if (mInputEventReceiver != null) {
mInputEventReceiver.dispose();
mInputEventReceiver = null;
@@ -725,7 +724,8 @@
@UiThread
public void onUserUnlocked() {
- Log.d(TAG, "onUserUnlocked: userId=" + getUserId());
+ Log.d(TAG, "onUserUnlocked: userId=" + getUserId()
+ + " instance=" + System.identityHashCode(this));
mTaskAnimationManager = new TaskAnimationManager(this, mRecentsWindowManager);
mOverviewComponentObserver = new OverviewComponentObserver(this, mDeviceState);
mOverviewCommandHelper = new OverviewCommandHelper(this,
@@ -811,7 +811,8 @@
@Override
public void onDestroy() {
- Log.d(TAG, "Touch service destroyed: user=" + getUserId());
+ Log.d(TAG, "onDestroy: user=" + getUserId()
+ + " instance=" + System.identityHashCode(this));
sIsInitialized = false;
if (LockedUserState.get(this).isUserUnlocked()) {
mInputConsumer.unregisterInputConsumer();
@@ -840,7 +841,8 @@
@Override
public IBinder onBind(Intent intent) {
- Log.d(TAG, "Touch service connected: user=" + getUserId());
+ Log.d(TAG, "onBind: user=" + getUserId()
+ + " instance=" + System.identityHashCode(this));
return mTISBinder;
}
@@ -857,9 +859,7 @@
private void onInputEvent(InputEvent ev) {
if (!(ev instanceof MotionEvent)) {
- ActiveGestureLog.INSTANCE.addLog(new CompoundString("TIS.onInputEvent: ")
- .append("Cannot process input event: received unknown event ")
- .append(ev.toString()));
+ ActiveGestureProtoLogProxy.logUnknownInputEvent(ev.toString());
return;
}
MotionEvent event = (MotionEvent) ev;
@@ -868,27 +868,19 @@
TestProtocol.SEQUENCE_TIS, "TouchInteractionService.onInputEvent", event);
if (!LockedUserState.get(this).isUserUnlocked()) {
- ActiveGestureLog.INSTANCE.addLog(new CompoundString("TIS.onInputEvent: ")
- .append("Cannot process input event: user is locked"));
+ ActiveGestureProtoLogProxy.logOnInputEventUserLocked();
return;
}
NavigationMode currentNavMode = mDeviceState.getMode();
if (mGestureStartNavMode != null && mGestureStartNavMode != currentNavMode) {
- ActiveGestureLog.INSTANCE.addLog(new CompoundString("TIS.onInputEvent: ")
- .append("Navigation mode switched mid-gesture (")
- .append(mGestureStartNavMode.name())
- .append(" -> ")
- .append(currentNavMode.name())
- .append("); cancelling gesture."),
- NAVIGATION_MODE_SWITCHED);
+ ActiveGestureProtoLogProxy.logOnInputEventNavModeSwitched(
+ mGestureStartNavMode.name(), currentNavMode.name());
event.setAction(ACTION_CANCEL);
} else if (mDeviceState.isButtonNavMode()
&& !mDeviceState.supportsAssistantGestureInButtonNav()
&& !isTrackpadMotionEvent(event)) {
- ActiveGestureLog.INSTANCE.addLog(new CompoundString("TIS.onInputEvent: ")
- .append("Cannot process input event: ")
- .append("using 3-button nav and event is not a trackpad event"));
+ ActiveGestureProtoLogProxy.logOnInputEventThreeButtonNav();
return;
}
@@ -904,12 +896,7 @@
}
if (mTaskAnimationManager.shouldIgnoreMotionEvents()) {
if (action == ACTION_DOWN || isHoverActionWithoutConsumer) {
- ActiveGestureLog.INSTANCE.addLog(
- new CompoundString("TIS.onMotionEvent: A new gesture has been ")
- .append("started, but a previously-requested recents ")
- .append("animation hasn't started. Ignoring all following ")
- .append("motion events."),
- RECENTS_ANIMATION_START_PENDING);
+ ActiveGestureProtoLogProxy.logOnInputIgnoringFollowingEvents();
}
return;
}
@@ -924,7 +911,7 @@
SafeCloseable traceToken = TraceHelper.INSTANCE.allowIpcs("TIS.onInputEvent");
CompoundString reasonString = action == ACTION_DOWN
- ? new CompoundString("TIS.onMotionEvent: ") : CompoundString.NO_OP;
+ ? CompoundString.newEmptyString() : CompoundString.NO_OP;
if (action == ACTION_DOWN || isHoverActionWithoutConsumer) {
mRotationTouchHelper.setOrientationTransformIfNeeded(event);
@@ -942,22 +929,22 @@
reasonString.append("in three button mode which supports Assistant gesture");
// Consume gesture event for Assistant (all other gestures should do nothing).
if (mDeviceState.canTriggerAssistantAction(event)) {
- reasonString.append(" and event can trigger assistant action")
- .append(", consuming gesture for assistant action");
+ reasonString.append(" and event can trigger assistant action, "
+ + "consuming gesture for assistant action");
mGestureState =
createGestureState(mGestureState, getTrackpadGestureType(event));
mUncheckedConsumer = tryCreateAssistantInputConsumer(mGestureState, event);
} else {
- reasonString.append(" but event cannot trigger Assistant")
- .append(", consuming gesture as no-op");
+ reasonString.append(" but event cannot trigger Assistant, "
+ + "consuming gesture as no-op");
mUncheckedConsumer = InputConsumer.NO_OP;
}
} else if ((!isOneHandedModeActive && isInSwipeUpTouchRegion)
|| isHoverActionWithoutConsumer || isOnBubbles) {
reasonString.append(!isOneHandedModeActive && isInSwipeUpTouchRegion
- ? "one handed mode is not active and event is in swipe up region"
- : "isHoverActionWithoutConsumer == true")
- .append(", creating new input consumer");
+ ? "one handed mode is not active and event is in swipe up region, "
+ + "creating new input consumer"
+ : "isHoverActionWithoutConsumer == true, creating new input consumer");
// Clone the previous gesture state since onConsumerAboutToBeSwitched might trigger
// onConsumerInactive and wipe the previous gesture state
GestureState prevGestureState = new GestureState(mGestureState);
@@ -970,18 +957,18 @@
} else if ((mDeviceState.isFullyGesturalNavMode() || isTrackpadMultiFingerSwipe(event))
&& mDeviceState.canTriggerAssistantAction(event)) {
reasonString.append(mDeviceState.isFullyGesturalNavMode()
- ? "using fully gestural nav"
- : "event is a trackpad multi-finger swipe")
- .append(" and event can trigger assistant action")
- .append(", consuming gesture for assistant action");
+ ? "using fully gestural nav and event can trigger assistant action, "
+ + "consuming gesture for assistant action"
+ : "event is a trackpad multi-finger swipe and event can trigger assistant "
+ + "action, consuming gesture for assistant action");
mGestureState = createGestureState(mGestureState, getTrackpadGestureType(event));
// Do not change mConsumer as if there is an ongoing QuickSwitch gesture, we
// should not interrupt it. QuickSwitch assumes that interruption can only
// happen if the next gesture is also quick switch.
mUncheckedConsumer = tryCreateAssistantInputConsumer(mGestureState, event);
} else if (mDeviceState.canTriggerOneHandedAction(event)) {
- reasonString.append("event can trigger one-handed action")
- .append(", consuming gesture for one-handed action");
+ reasonString.append("event can trigger one-handed action, "
+ + "consuming gesture for one-handed action");
// Consume gesture event for triggering one handed feature.
mUncheckedConsumer = new OneHandedModeInputConsumer(this, mDeviceState,
InputConsumer.NO_OP, mInputMonitorCompat);
@@ -999,41 +986,25 @@
if (mUncheckedConsumer != InputConsumer.NO_OP) {
switch (action) {
case ACTION_DOWN:
- ActiveGestureLog.INSTANCE.addLog(reasonString);
+ ActiveGestureProtoLogProxy.logOnInputEventActionDown(reasonString);
// fall through
case ACTION_UP:
- ActiveGestureLog.INSTANCE.addLog(
- new CompoundString("onMotionEvent(")
- .append((int) event.getRawX())
- .append(", ")
- .append((int) event.getRawY())
- .append("): ")
- .append(MotionEvent.actionToString(action))
- .append(", ")
- .append(MotionEvent.classificationToString(
- event.getClassification())),
- /* gestureEvent= */ action == ACTION_DOWN
- ? MOTION_DOWN
- : MOTION_UP);
+ ActiveGestureProtoLogProxy.logOnInputEventActionUp(
+ (int) event.getRawX(),
+ (int) event.getRawY(),
+ action,
+ MotionEvent.classificationToString(event.getClassification()));
break;
case ACTION_MOVE:
- ActiveGestureLog.INSTANCE.addLog(
- new CompoundString("onMotionEvent: ")
- .append(MotionEvent.actionToString(action))
- .append(",")
- .append(MotionEvent.classificationToString(
- event.getClassification()))
- .append(", pointerCount: ")
- .append(event.getPointerCount()),
- MOTION_MOVE);
+ ActiveGestureProtoLogProxy.logOnInputEventActionMove(
+ MotionEvent.actionToString(action),
+ MotionEvent.classificationToString(event.getClassification()),
+ event.getPointerCount());
break;
default: {
- ActiveGestureLog.INSTANCE.addLog(
- new CompoundString("onMotionEvent: ")
- .append(MotionEvent.actionToString(action))
- .append(",")
- .append(MotionEvent.classificationToString(
- event.getClassification())));
+ ActiveGestureProtoLogProxy.logOnInputEventGenericAction(
+ MotionEvent.actionToString(action),
+ MotionEvent.classificationToString(event.getClassification()));
}
}
}
@@ -1088,11 +1059,11 @@
MotionEvent motionEvent,
CompoundString reasonString) {
if (mDeviceState.isGestureBlockedTask(gestureState.getRunningTask())) {
- reasonString.append(SUBSTRING_PREFIX)
- .append("is gesture-blocked task, using base input consumer");
+ reasonString.append(
+ "%sis gesture-blocked task, using base input consumer", SUBSTRING_PREFIX);
return base;
} else {
- reasonString.append(SUBSTRING_PREFIX).append("using AssistantInputConsumer");
+ reasonString.append("%susing AssistantInputConsumer", SUBSTRING_PREFIX);
return new AssistantInputConsumer(
this, gestureState, base, mInputMonitorCompat, mDeviceState, motionEvent);
}
@@ -1123,10 +1094,8 @@
gestureState.setTrackpadGestureType(trackpadGestureType);
// Log initial state for the gesture.
- ActiveGestureLog.INSTANCE.addLog(new CompoundString("Current running task package name=")
- .append(taskInfo.getPackageName()));
- ActiveGestureLog.INSTANCE.addLog(new CompoundString("Current SystemUi state flags=")
- .append(mDeviceState.getSystemUiStateString()));
+ ActiveGestureProtoLogProxy.logRunningTaskPackage(taskInfo.getPackageName());
+ ActiveGestureProtoLogProxy.logSysuiStateFlags(mDeviceState.getSystemUiStateString());
return gestureState;
}
@@ -1163,12 +1132,11 @@
// This handles apps launched in direct boot mode (e.g. dialer) as well as apps
// launched while device is locked even after exiting direct boot mode (e.g. camera).
consumer = createDeviceLockedInputConsumer(
- newGestureState, reasonString.append(SUBSTRING_PREFIX)
- .append("can start system gesture"));
+ newGestureState,
+ reasonString.append("%scan start system gesture", SUBSTRING_PREFIX));
} else {
consumer = getDefaultInputConsumer(
- reasonString.append(SUBSTRING_PREFIX)
- .append("cannot start system gesture"));
+ reasonString.append("%scannot start system gesture", SUBSTRING_PREFIX));
}
logInputConsumerSelectionReason(consumer, reasonString);
return consumer;
@@ -1180,13 +1148,12 @@
// a followup gesture and the first gesture started in a valid system state.
if (canStartSystemGesture || previousGestureState.isRecentsAnimationRunning()) {
reasonString = newCompoundString(canStartSystemGesture
- ? "can start system gesture" : "recents animation was running")
- .append(", trying to use base consumer");
+ ? "can start system gesture, trying to use base consumer"
+ : "recents animation was running, trying to use base consumer");
base = newBaseConsumer(previousGestureState, newGestureState, event, reasonString);
} else {
- reasonString = newCompoundString(
- "cannot start system gesture and recents animation was not running")
- .append(", trying to use default input consumer");
+ reasonString = newCompoundString("cannot start system gesture and recents "
+ + "animation was not running, trying to use default input consumer");
base = getDefaultInputConsumer(reasonString);
}
if (mDeviceState.isGesturalNavMode() || newGestureState.isTrackpadGesture()) {
@@ -1196,11 +1163,11 @@
String reasonPrefix =
"device is in gesture navigation mode or 3-button mode with a trackpad gesture";
if (mDeviceState.canTriggerAssistantAction(event)) {
- reasonString.append(NEWLINE_PREFIX)
- .append(reasonPrefix)
- .append(SUBSTRING_PREFIX)
- .append("gesture can trigger the assistant")
- .append(", trying to use assistant input consumer");
+ reasonString.append("%s%s%sgesture can trigger the assistant, "
+ + "trying to use assistant input consumer",
+ NEWLINE_PREFIX,
+ reasonPrefix,
+ SUBSTRING_PREFIX);
base = tryCreateAssistantInputConsumer(base, newGestureState, event, reasonString);
}
@@ -1211,11 +1178,11 @@
&& !tac.isPhoneMode()
&& !tac.isInStashedLauncherState();
if (canStartSystemGesture && useTaskbarConsumer) {
- reasonString.append(NEWLINE_PREFIX)
- .append(reasonPrefix)
- .append(SUBSTRING_PREFIX)
- .append("TaskbarActivityContext != null, ")
- .append("using TaskbarUnstashInputConsumer");
+ reasonString.append("%s%s%sTaskbarActivityContext != null, "
+ + "using TaskbarUnstashInputConsumer",
+ NEWLINE_PREFIX,
+ reasonPrefix,
+ SUBSTRING_PREFIX);
base = new TaskbarUnstashInputConsumer(this, base, mInputMonitorCompat, tac,
mOverviewCommandHelper, mGestureState);
}
@@ -1224,9 +1191,9 @@
// Create bubbles input consumer before NavHandleLongPressInputConsumer.
// This allows for nav handle to fall back to bubbles.
if (mDeviceState.isBubblesExpanded()) {
- reasonString = newCompoundString(reasonPrefix)
- .append(SUBSTRING_PREFIX)
- .append("bubbles expanded, trying to use default input consumer");
+ reasonString = newCompoundString(reasonPrefix).append(
+ "%sbubbles expanded, trying to use default input consumer",
+ SUBSTRING_PREFIX);
// Bubbles can handle home gesture itself.
base = getDefaultInputConsumer(reasonString);
}
@@ -1237,10 +1204,10 @@
if (canStartSystemGesture && !previousGestureState.isRecentsAnimationRunning()
&& navHandle.canNavHandleBeLongPressed()
&& !ignoreThreeFingerTrackpadForNavHandleLongPress(mGestureState)) {
- reasonString.append(NEWLINE_PREFIX)
- .append(reasonPrefix)
- .append(SUBSTRING_PREFIX)
- .append("Not running recents animation, ");
+ reasonString.append("%s%s%sNot running recents animation, ",
+ NEWLINE_PREFIX,
+ reasonPrefix,
+ SUBSTRING_PREFIX);
if (tac != null && tac.getNavHandle().canNavHandleBeLongPressed()) {
reasonString.append("stashed handle is long-pressable, ");
}
@@ -1252,74 +1219,74 @@
if (!enableBubblesLongPressNavHandle()) {
// Continue overriding nav handle input consumer with bubbles
if (mDeviceState.isBubblesExpanded()) {
- reasonString = newCompoundString(reasonPrefix)
- .append(SUBSTRING_PREFIX)
- .append("bubbles expanded, trying to use default input consumer");
+ reasonString = newCompoundString(reasonPrefix).append(
+ "%sbubbles expanded, trying to use default input consumer",
+ SUBSTRING_PREFIX);
// Bubbles can handle home gesture itself.
base = getDefaultInputConsumer(reasonString);
}
}
if (mDeviceState.isSystemUiDialogShowing()) {
- reasonString = newCompoundString(reasonPrefix)
- .append(SUBSTRING_PREFIX)
- .append("system dialog is showing, using SysUiOverlayInputConsumer");
+ reasonString = newCompoundString(reasonPrefix).append(
+ "%ssystem dialog is showing, using SysUiOverlayInputConsumer",
+ SUBSTRING_PREFIX);
base = new SysUiOverlayInputConsumer(
getBaseContext(), mDeviceState, mInputMonitorCompat);
}
if (mGestureState.isTrackpadGesture()
&& canStartSystemGesture && !previousGestureState.isRecentsAnimationRunning()) {
- reasonString = newCompoundString(reasonPrefix)
- .append(SUBSTRING_PREFIX)
- .append("Trackpad 3-finger gesture, using TrackpadStatusBarInputConsumer");
+ reasonString = newCompoundString(reasonPrefix).append(
+ "%sTrackpad 3-finger gesture, using TrackpadStatusBarInputConsumer",
+ SUBSTRING_PREFIX);
base = new TrackpadStatusBarInputConsumer(getBaseContext(), base,
mInputMonitorCompat);
}
if (mDeviceState.isScreenPinningActive()) {
- reasonString = newCompoundString(reasonPrefix)
- .append(SUBSTRING_PREFIX)
- .append("screen pinning is active, using ScreenPinnedInputConsumer");
+ reasonString = newCompoundString(reasonPrefix).append(
+ "%sscreen pinning is active, using ScreenPinnedInputConsumer",
+ SUBSTRING_PREFIX);
// Note: we only allow accessibility to wrap this, and it replaces the previous
// base input consumer (which should be NO_OP anyway since topTaskLocked == true).
base = new ScreenPinnedInputConsumer(this, newGestureState);
}
if (mDeviceState.canTriggerOneHandedAction(event)) {
- reasonString.append(NEWLINE_PREFIX)
- .append(reasonPrefix)
- .append(SUBSTRING_PREFIX)
- .append("gesture can trigger one handed mode")
- .append(", using OneHandedModeInputConsumer");
+ reasonString.append("%s%s%sgesture can trigger one handed mode, "
+ + "using OneHandedModeInputConsumer",
+ NEWLINE_PREFIX,
+ reasonPrefix,
+ SUBSTRING_PREFIX);
base = new OneHandedModeInputConsumer(
this, mDeviceState, base, mInputMonitorCompat);
}
if (mDeviceState.isAccessibilityMenuAvailable()) {
- reasonString.append(NEWLINE_PREFIX)
- .append(reasonPrefix)
- .append(SUBSTRING_PREFIX)
- .append("accessibility menu is available")
- .append(", using AccessibilityInputConsumer");
+ reasonString.append(
+ "%s%s%saccessibility menu is available, using AccessibilityInputConsumer",
+ NEWLINE_PREFIX,
+ reasonPrefix,
+ SUBSTRING_PREFIX);
base = new AccessibilityInputConsumer(
this, mDeviceState, mGestureState, base, mInputMonitorCompat);
}
} else {
String reasonPrefix = "device is not in gesture navigation mode";
if (mDeviceState.isScreenPinningActive()) {
- reasonString = newCompoundString(reasonPrefix)
- .append(SUBSTRING_PREFIX)
- .append("screen pinning is active, trying to use default input consumer");
+ reasonString = newCompoundString(reasonPrefix).append(
+ "%sscreen pinning is active, trying to use default input consumer",
+ SUBSTRING_PREFIX);
base = getDefaultInputConsumer(reasonString);
}
if (mDeviceState.canTriggerOneHandedAction(event)) {
- reasonString.append(NEWLINE_PREFIX)
- .append(reasonPrefix)
- .append(SUBSTRING_PREFIX)
- .append("gesture can trigger one handed mode")
- .append(", using OneHandedModeInputConsumer");
+ reasonString.append("%s%s%sgesture can trigger one handed mode, "
+ + "using OneHandedModeInputConsumer",
+ NEWLINE_PREFIX,
+ reasonPrefix,
+ SUBSTRING_PREFIX);
base = new OneHandedModeInputConsumer(
this, mDeviceState, base, mInputMonitorCompat);
}
@@ -1329,7 +1296,7 @@
}
private CompoundString newCompoundString(String substring) {
- return new CompoundString(NEWLINE_PREFIX).append(substring);
+ return new CompoundString("%s%s", NEWLINE_PREFIX, substring);
}
private boolean ignoreThreeFingerTrackpadForNavHandleLongPress(GestureState gestureState) {
@@ -1339,10 +1306,7 @@
private void logInputConsumerSelectionReason(
InputConsumer consumer, CompoundString reasonString) {
- ActiveGestureLog.INSTANCE.addLog(new CompoundString("setInputConsumer: ")
- .append(consumer.getName())
- .append(". reason(s):")
- .append(reasonString));
+ ActiveGestureProtoLogProxy.logSetInputConsumer(consumer.getName(), reasonString.toString());
if ((consumer.getType() & InputConsumer.TYPE_OTHER_ACTIVITY) != 0) {
ActiveGestureLog.INSTANCE.trackEvent(FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER);
}
@@ -1359,14 +1323,12 @@
CompoundString reasonString) {
if (mDeviceState.isKeyguardShowingOccluded()) {
// This handles apps showing over the lockscreen (e.g. camera)
- return createDeviceLockedInputConsumer(
- gestureState,
- reasonString.append(SUBSTRING_PREFIX)
- .append("keyguard is showing occluded")
- .append(", trying to use device locked input consumer"));
+ return createDeviceLockedInputConsumer(gestureState, reasonString.append(
+ "%skeyguard is showing occluded, trying to use device locked input consumer",
+ SUBSTRING_PREFIX));
}
- reasonString.append(SUBSTRING_PREFIX).append("keyguard is not showing occluded");
+ reasonString.append("%skeyguard is not showing occluded", SUBSTRING_PREFIX);
TopTaskTracker.CachedTaskInfo runningTask = gestureState.getRunningTask();
// Use overview input consumer for sharesheets on top of home.
@@ -1380,11 +1342,8 @@
? null
: runningTask.getVisibleNonExcludedTask();
if (otherVisibleTask != null) {
- ActiveGestureLog.INSTANCE.addLog(new CompoundString("Changing active task to ")
- .append(otherVisibleTask.getPackageName())
- .append(" because the previous task running on top of this one (")
- .append(runningTask.getPackageName())
- .append(") was excluded from recents"));
+ ActiveGestureProtoLogProxy.logUpdateGestureStateRunningTask(
+ otherVisibleTask.getPackageName(), runningTask.getPackageName());
gestureState.updateRunningTask(otherVisibleTask);
}
@@ -1410,11 +1369,12 @@
gestureState,
event,
forceOverviewInputConsumer,
- reasonString.append(SUBSTRING_PREFIX)
- .append("is in live tile mode, trying to use overview input consumer"));
+ reasonString.append(
+ "%sis in live tile mode, trying to use overview input consumer",
+ SUBSTRING_PREFIX));
} else if (runningTask == null) {
- return getDefaultInputConsumer(reasonString.append(SUBSTRING_PREFIX)
- .append("running task == null"));
+ return getDefaultInputConsumer(reasonString.append(
+ "%srunning task == null", SUBSTRING_PREFIX));
} else if (previousGestureAnimatedToLauncher
|| launcherResumedThroughShellTransition
|| forceOverviewInputConsumer) {
@@ -1423,21 +1383,22 @@
gestureState,
event,
forceOverviewInputConsumer,
- reasonString.append(SUBSTRING_PREFIX)
- .append(previousGestureAnimatedToLauncher
- ? "previous gesture animated to launcher"
+ reasonString.append(previousGestureAnimatedToLauncher
+ ? "%sprevious gesture animated to launcher, "
+ + "trying to use overview input consumer"
: (launcherResumedThroughShellTransition
- ? "launcher resumed through a shell transition"
- : "forceOverviewInputConsumer == true"))
- .append(", trying to use overview input consumer"));
+ ? "%slauncher resumed through a shell transition, "
+ + "trying to use overview input consumer"
+ : "%sforceOverviewInputConsumer == true, "
+ + "trying to use overview input consumer"),
+ SUBSTRING_PREFIX));
} else if (mDeviceState.isGestureBlockedTask(runningTask) || launcherChildActivityResumed) {
- return getDefaultInputConsumer(reasonString.append(SUBSTRING_PREFIX)
- .append(launcherChildActivityResumed
- ? "is launcher child-task, trying to use default input consumer"
- : "is gesture-blocked task, trying to use default input consumer"));
+ return getDefaultInputConsumer(reasonString.append(launcherChildActivityResumed
+ ? "%sis launcher child-task, trying to use default input consumer"
+ : "%sis gesture-blocked task, trying to use default input consumer",
+ SUBSTRING_PREFIX));
} else {
- reasonString.append(SUBSTRING_PREFIX)
- .append("using OtherActivityInputConsumer");
+ reasonString.append("%susing OtherActivityInputConsumer", SUBSTRING_PREFIX);
return createOtherActivityInputConsumer(gestureState, event);
}
}
@@ -1464,20 +1425,18 @@
GestureState gestureState, CompoundString reasonString) {
if ((mDeviceState.isFullyGesturalNavMode() || gestureState.isTrackpadGesture())
&& gestureState.getRunningTask() != null) {
- reasonString.append(SUBSTRING_PREFIX)
- .append("device is in gesture nav mode or 3-button mode with a trackpad")
- .append(" gesture and running task != null")
- .append(", using DeviceLockedInputConsumer");
+ reasonString.append("%sdevice is in gesture nav mode or 3-button mode with a trackpad "
+ + "gesture and running task != null, using DeviceLockedInputConsumer",
+ SUBSTRING_PREFIX);
return new DeviceLockedInputConsumer(
this, mDeviceState, mTaskAnimationManager, gestureState, mInputMonitorCompat);
} else {
- return getDefaultInputConsumer(reasonString
- .append(SUBSTRING_PREFIX)
- .append((mDeviceState.isFullyGesturalNavMode()
- || gestureState.isTrackpadGesture())
- ? "running task == null"
- : "device is not in gesture nav mode and it's not a trackpad gesture")
- .append(", trying to use default input consumer"));
+ return getDefaultInputConsumer(reasonString.append(
+ mDeviceState.isFullyGesturalNavMode() || gestureState.isTrackpadGesture()
+ ? "%srunning task == null, trying to use default input consumer"
+ : "%sdevice is not in gesture nav mode and it's not a trackpad gesture,"
+ + " trying to use default input consumer",
+ SUBSTRING_PREFIX));
}
}
@@ -1489,9 +1448,8 @@
CompoundString reasonString) {
RecentsViewContainer container = gestureState.getContainerInterface().getCreatedContainer();
if (container == null) {
- return getDefaultInputConsumer(
- reasonString.append(SUBSTRING_PREFIX)
- .append("activity == null, trying to use default input consumer"));
+ return getDefaultInputConsumer(reasonString.append(
+ "%sactivity == null, trying to use default input consumer", SUBSTRING_PREFIX));
}
View rootview = container.getRootView();
@@ -1501,24 +1459,24 @@
|| mDeviceState.isPredictiveBackToHomeInProgress();
boolean isInLiveTileMode = gestureState.getContainerInterface().isInLiveTileMode();
- reasonString.append(SUBSTRING_PREFIX)
- .append(hasWindowFocus
- ? "activity has window focus"
- : (isPreviousGestureAnimatingToLauncher
- ? "previous gesture is still animating to launcher"
- : isInLiveTileMode
- ? "device is in live mode"
- : "all overview focus conditions failed"));
+ reasonString.append(hasWindowFocus
+ ? "%sactivity has window focus"
+ : (isPreviousGestureAnimatingToLauncher
+ ? "%sprevious gesture is still animating to launcher"
+ : isInLiveTileMode
+ ? "%sdevice is in live mode"
+ : "%sall overview focus conditions failed"), SUBSTRING_PREFIX);
if (hasWindowFocus
|| isPreviousGestureAnimatingToLauncher
|| isInLiveTileMode) {
- reasonString.append(SUBSTRING_PREFIX)
- .append("overview should have focus, using OverviewInputConsumer");
+ reasonString.append(
+ "%soverview should have focus, using OverviewInputConsumer", SUBSTRING_PREFIX);
return new OverviewInputConsumer(gestureState, container, mInputMonitorCompat,
false /* startingInActivityBounds */);
} else {
- reasonString.append(SUBSTRING_PREFIX).append(
- "overview shouldn't have focus, using OverviewWithoutFocusInputConsumer");
+ reasonString.append(
+ "%soverview shouldn't have focus, using OverviewWithoutFocusInputConsumer",
+ SUBSTRING_PREFIX);
final boolean disableHorizontalSwipe = mDeviceState.isInExclusionRegion(event);
return new OverviewWithoutFocusInputConsumer(container.asContext(), mDeviceState,
gestureState, mInputMonitorCompat, disableHorizontalSwipe);
@@ -1555,12 +1513,14 @@
*/
private @NonNull InputConsumer getDefaultInputConsumer(@NonNull CompoundString reasonString) {
if (mResetGestureInputConsumer != null) {
- reasonString.append(SUBSTRING_PREFIX).append(
- "mResetGestureInputConsumer initialized, using ResetGestureInputConsumer");
+ reasonString.append(
+ "%smResetGestureInputConsumer initialized, using ResetGestureInputConsumer",
+ SUBSTRING_PREFIX);
return mResetGestureInputConsumer;
} else {
- reasonString.append(SUBSTRING_PREFIX).append(
- "mResetGestureInputConsumer not initialized, using no-op input consumer");
+ reasonString.append(
+ "%smResetGestureInputConsumer not initialized, using no-op input consumer",
+ SUBSTRING_PREFIX);
// mResetGestureInputConsumer isn't initialized until onUserUnlocked(), so reset to
// NO_OP until then (we never want these to be null).
return InputConsumer.NO_OP;
@@ -1604,8 +1564,7 @@
// TODO(b/258022658): Remove temporary logging.
Log.i(TAG, "preloadOverview: forSUWAllSet=" + forSUWAllSet
+ ", isHomeAndOverviewSame=" + mOverviewComponentObserver.isHomeAndOverviewSame());
-
- ActiveGestureLog.INSTANCE.addLog("preloadRecentsAnimation");
+ ActiveGestureProtoLogProxy.logPreloadRecentsAnimation();
mTaskAnimationManager.preloadRecentsAnimation(overviewIntent);
}
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index 11f1151..daac9fb 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -18,6 +18,7 @@
import static com.android.app.animation.Interpolators.FINAL_FRAME;
import static com.android.app.animation.Interpolators.INSTANT;
import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.launcher3.Flags.enableLargeDesktopWindowingTile;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
@@ -26,6 +27,7 @@
import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
import static com.android.quickstep.fallback.RecentsState.OVERVIEW_SPLIT_SELECT;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
+import static com.android.quickstep.views.RecentsView.DESKTOP_CAROUSEL_DETACH_PROGRESS;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
import static com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS;
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
@@ -81,7 +83,7 @@
// While animating into recents, update the visible task data as needed
setter.addOnFrameCallback(() -> mRecentsView.loadVisibleTaskData(FLAG_UPDATE_ALL));
setter.addEndListener(success -> {
- if (!success) {
+ if (!success && !toState.isRecentsViewVisible()) {
mRecentsView.reset();
}
});
@@ -116,6 +118,11 @@
getOverviewInterpolator(state));
setter.setFloat(mRecentsView, TASK_THUMBNAIL_SPLASH_ALPHA,
state.showTaskThumbnailSplash() ? 1f : 0f, getOverviewInterpolator(state));
+ if (enableLargeDesktopWindowingTile()) {
+ setter.setFloat(mRecentsView, DESKTOP_CAROUSEL_DETACH_PROGRESS,
+ state.detachDesktopCarousel() ? 1f : 0f,
+ getOverviewInterpolator(state));
+ }
setter.setViewBackgroundColor(mRecentsViewContainer.getScrimView(),
state.getScrimColor(mRecentsViewContainer.asContext()),
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 7f0d665..5a4c769 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -19,7 +19,6 @@
import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
import static com.android.quickstep.fallback.RecentsState.DEFAULT;
-import static com.android.quickstep.fallback.RecentsState.HOME;
import static com.android.quickstep.fallback.RecentsState.MODAL_TASK;
import static com.android.quickstep.fallback.RecentsState.OVERVIEW_SPLIT_SELECT;
@@ -271,7 +270,7 @@
@Override
public void onStateTransitionComplete(RecentsState finalState) {
- if (finalState == HOME) {
+ if (!finalState.isRecentsViewVisible()) {
// Clean-up logic that occurs when recents is no longer in use/visible.
reset();
}
@@ -324,7 +323,7 @@
}
@Override
- protected boolean canLaunchFullscreenTask() {
+ public boolean canLaunchFullscreenTask() {
return !mContainer.isInState(OVERVIEW_SPLIT_SELECT);
}
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsState.java b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
index ca9753f..082b96c 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsState.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
@@ -42,6 +42,7 @@
private static final int FLAG_LIVE_TILE = BaseState.getFlag(6);
private static final int FLAG_RECENTS_VIEW_VISIBLE = BaseState.getFlag(7);
private static final int FLAG_TASK_THUMBNAIL_SPLASH = BaseState.getFlag(8);
+ private static final int FLAG_DETACH_DESKTOP_CAROUSEL = BaseState.getFlag(9);
public static final RecentsState DEFAULT = new RecentsState(0,
FLAG_DISABLE_RESTORE | FLAG_CLEAR_ALL_BUTTON | FLAG_OVERVIEW_ACTIONS | FLAG_SHOW_AS_GRID
@@ -51,8 +52,8 @@
| FLAG_SHOW_AS_GRID | FLAG_SCRIM | FLAG_LIVE_TILE | FLAG_RECENTS_VIEW_VISIBLE);
public static final RecentsState BACKGROUND_APP = new BackgroundAppState(2,
FLAG_DISABLE_RESTORE | FLAG_NON_INTERACTIVE | FLAG_FULL_SCREEN
- | FLAG_RECENTS_VIEW_VISIBLE
- | FLAG_TASK_THUMBNAIL_SPLASH);
+ | FLAG_RECENTS_VIEW_VISIBLE | FLAG_TASK_THUMBNAIL_SPLASH
+ | FLAG_DETACH_DESKTOP_CAROUSEL);
public static final RecentsState HOME = new RecentsState(3, 0);
public static final RecentsState BG_LAUNCHER = new LauncherState(4, 0);
public static final RecentsState OVERVIEW_SPLIT_SELECT = new RecentsState(5,
@@ -149,6 +150,11 @@
return hasFlag(FLAG_TASK_THUMBNAIL_SPLASH);
}
+ @Override
+ public boolean detachDesktopCarousel() {
+ return hasFlag(FLAG_DETACH_DESKTOP_CAROUSEL);
+ }
+
/**
* True if the state has overview panel visible.
*/
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
index 5557639..4afd92a 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
@@ -5,7 +5,7 @@
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.quickstep.InputConsumer;
-import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.ActiveGestureProtoLogProxy;
import com.android.systemui.shared.system.InputMonitorCompat;
public abstract class DelegateInputConsumer implements InputConsumer {
@@ -57,8 +57,7 @@
}
protected void setActive(MotionEvent ev) {
- ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(getDelegatorName())
- .append(" became active"));
+ ActiveGestureProtoLogProxy.logInputConsumerBecameActive(getDelegatorName());
mState = STATE_ACTIVE;
TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers");
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
index 5ad55ae..49bff8d 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
@@ -228,10 +228,13 @@
}
float velocityYPxPerS = mVelocityTracker.getYVelocity();
+ float dY = Math.abs(mLastPos.y - mDownPos.y);
if (mCanPlayTaskbarBgAlphaAnimation
&& mMotionMoveCount >= NUM_MOTION_MOVE_THRESHOLD // Arbitrary value
&& velocityYPxPerS != 0 // Ignore these
- && velocityYPxPerS >= mTaskbarSlowVelocityYThreshold) {
+ && velocityYPxPerS >= mTaskbarSlowVelocityYThreshold
+ && dY != 0
+ && dY > mTouchSlop) {
mTaskbarActivityContext.playTaskbarBackgroundAlphaAnimation();
mCanPlayTaskbarBgAlphaAnimation = false;
}
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index 5028da4..9510a05 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -52,7 +52,6 @@
import androidx.annotation.StringRes;
import androidx.annotation.StyleRes;
import androidx.appcompat.app.AlertDialog;
-import androidx.appcompat.content.res.AppCompatResources;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
@@ -610,8 +609,8 @@
private void updateDrawables() {
if (mContext != null) {
- mTutorialFragment.getRootView().setBackground(AppCompatResources.getDrawable(
- mContext, getMockWallpaperResId()));
+ mTutorialFragment.getRootView()
+ .setBackground(mContext.getDrawable(getMockWallpaperResId()));
mTutorialFragment.updateFeedbackAnimation();
mFakeLauncherView.setBackgroundColor(getFakeLauncherColor());
updateFakeViewLayout(mFakeHotseatView, getMockHotseatResId());
@@ -619,9 +618,7 @@
mFakeTaskView.animate().alpha(1).setListener(
AnimatorListeners.forSuccessCallback(() -> mFakeTaskView.animate().cancel()));
mFakePreviousTaskView.setFakeTaskViewFillColor(getMockPreviousAppTaskThumbnailColor());
- mFakeIconView.setBackground(AppCompatResources.getDrawable(
- mContext, getMockAppIconResId()));
-
+ mFakeIconView.setBackground(mContext.getDrawable(getMockAppIconResId()));
mExitingAppView.setBackgroundColor(getExitingAppColor());
mFakeTaskView.setBackgroundColor(getFakeTaskViewColor());
updateHotseatChildViewColor(mHotseatIconView);
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialStepIndicator.java b/quickstep/src/com/android/quickstep/interaction/TutorialStepIndicator.java
index ae0e725..f1fc179 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialStepIndicator.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialStepIndicator.java
@@ -22,8 +22,6 @@
import android.widget.ImageView;
import android.widget.LinearLayout;
-import androidx.appcompat.content.res.AppCompatResources;
-
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.icons.GraphicsUtils;
@@ -91,9 +89,8 @@
int inactiveStepIndicatorColor = GraphicsUtils.getAttrColor(
getContext(), android.R.attr.textColorSecondaryInverse);
for (int i = 0; i < mTotalSteps; i++) {
- Drawable pageIndicatorPillDrawable = AppCompatResources.getDrawable(
- getContext(), R.drawable.tutorial_step_indicator_pill);
-
+ Drawable pageIndicatorPillDrawable =
+ getContext().getDrawable(R.drawable.tutorial_step_indicator_pill);
if (i >= getChildCount()) {
ImageView pageIndicatorPill = new ImageView(getContext());
pageIndicatorPill.setImageDrawable(pageIndicatorPillDrawable);
diff --git a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
index eb3c2d1..dc8d537 100644
--- a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
@@ -17,6 +17,7 @@
package com.android.quickstep.recents.data
import android.graphics.drawable.Drawable
+import android.util.Log
import com.android.launcher3.util.coroutines.DispatcherProvider
import com.android.quickstep.recents.data.TaskVisualsChangedDelegate.TaskIconChangedCallback
import com.android.quickstep.recents.data.TaskVisualsChangedDelegate.TaskThumbnailChangedCallback
@@ -106,7 +107,7 @@
}
}
.flowOn(dispatcherProvider.io)
- .shareIn(recentsCoroutineScope, SharingStarted.WhileSubscribed(), replay = 1)
+ .shareIn(recentsCoroutineScope, SharingStarted.WhileSubscribed(5000), replay = 1)
override fun getAllTaskData(forceRefresh: Boolean): Flow<List<Task>> {
if (forceRefresh) {
@@ -122,6 +123,7 @@
getTaskDataById(taskId).map { it?.thumbnail }.distinctUntilChangedBy { it?.snapshotId }
override fun setVisibleTasks(visibleTaskIdList: List<Int>) {
+ Log.d(TAG, "setVisibleTasks: $visibleTaskIdList")
this.visibleTaskIds.value = visibleTaskIdList.toSet()
}
@@ -185,7 +187,7 @@
TaskIconQueryResponse(
it.newDrawable().mutate(),
contentDescription,
- title
+ title,
)
)
}
@@ -193,12 +195,16 @@
continuation.invokeOnCancellation { cancellableTask?.cancel() }
}
}
+
+ companion object {
+ private const val TAG = "TasksRepository"
+ }
}
data class TaskIconQueryResponse(
val icon: Drawable,
val contentDescription: String,
- val title: String
+ val title: String,
)
private fun Task.getTaskIconQueryResponse(): TaskIconQueryResponse? {
diff --git a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
index 0a5544f..b53650e 100644
--- a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
+++ b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
@@ -66,7 +66,7 @@
val taskVisualsChangedDelegate =
TaskVisualsChangedDelegateImpl(
recentsModel,
- recentsModel.thumbnailCache.highResLoadingState
+ recentsModel.thumbnailCache.highResLoadingState,
)
set(TaskVisualsChangedDelegate::class.java.simpleName, taskVisualsChangedDelegate)
@@ -79,7 +79,7 @@
iconCache,
taskVisualsChangedDelegate,
recentsCoroutineScope,
- ProductionDispatchers
+ ProductionDispatchers,
)
}
set(RecentTasksRepository::class.java.simpleName, recentTasksRepository)
@@ -155,7 +155,8 @@
scopeId: RecentsScopeId,
extras: RecentsDependenciesExtras,
): T {
- log("createDependency ${modelClass.simpleName} with $scopeId and $extras", Log.WARN)
+ log("createDependency ${modelClass.simpleName} with $scopeId and $extras started", Log.WARN)
+ log("linked scopes: ${getScope(scopeId).scopeIdsLinked}")
val instance: Any =
when (modelClass) {
RecentTasksRepository::class.java -> {
@@ -166,7 +167,7 @@
iconCache,
get(),
get(),
- ProductionDispatchers
+ ProductionDispatchers,
)
}
}
@@ -193,7 +194,7 @@
task = task,
recentsViewData = inject(),
recentTasksRepository = inject(),
- getThumbnailPositionUseCase = inject()
+ getThumbnailPositionUseCase = inject(),
)
}
GetThumbnailUseCase::class.java -> GetThumbnailUseCase(taskRepository = inject())
@@ -203,7 +204,7 @@
GetThumbnailPositionUseCase(
deviceProfileRepository = inject(),
rotationStateRepository = inject(),
- tasksRepository = inject()
+ tasksRepository = inject(),
)
SplashAlphaUseCase::class.java ->
SplashAlphaUseCase(
@@ -218,7 +219,12 @@
error("Factory for ${modelClass.simpleName} not defined!")
}
}
- return instance as T
+ return (instance as T).also {
+ log(
+ "createDependency ${modelClass.simpleName} with $scopeId and $extras completed",
+ Log.WARN,
+ )
+ }
}
private fun createScope(scopeId: RecentsScopeId): RecentsDependenciesScope {
@@ -247,11 +253,7 @@
fun initialize(view: View): RecentsDependencies = initialize(view.context)
fun initialize(context: Context): RecentsDependencies {
- synchronized(this) {
- if (!Companion::instance.isInitialized) {
- instance = RecentsDependencies(context.applicationContext)
- }
- }
+ synchronized(this) { instance = RecentsDependencies(context.applicationContext) }
return instance
}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
index 0279818..e7416ec 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
@@ -22,6 +22,7 @@
import android.graphics.Outline
import android.graphics.Rect
import android.util.AttributeSet
+import android.util.Log
import android.view.View
import android.view.ViewOutlineProvider
import androidx.annotation.ColorInt
@@ -92,6 +93,7 @@
CoroutineScope(SupervisorJob() + Dispatchers.Main + CoroutineName("TaskThumbnailView"))
viewModel.uiState
.onEach { viewModelUiState ->
+ Log.d(TAG, "viewModelUiState changed from $uiState to: $viewModelUiState")
uiState = viewModelUiState
resetViews()
when (viewModelUiState) {
@@ -211,6 +213,10 @@
Utilities.mapRange(
viewModel.cornerRadiusProgress.value,
overviewCornerRadius,
- fullscreenCornerRadius
+ fullscreenCornerRadius,
) / inheritedScale
+
+ private companion object {
+ const val TAG = "TaskThumbnailView"
+ }
}
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
index b1bb65e..4970685 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
@@ -19,6 +19,7 @@
import android.annotation.ColorInt
import android.app.ActivityTaskManager.INVALID_TASK_ID
import android.graphics.Matrix
+import android.util.Log
import androidx.core.graphics.ColorUtils
import com.android.quickstep.recents.data.RecentTasksRepository
import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
@@ -82,7 +83,7 @@
combine(
task.flatMapLatest { it }.map { it?.key?.id }.distinctUntilChanged(),
recentsViewData.runningTaskIds,
- recentsViewData.runningTaskShowScreenshot
+ recentsViewData.runningTaskShowScreenshot,
) { taskId, runningTaskIds, runningTaskShowScreenshot ->
runningTaskIds.contains(taskId) && !runningTaskShowScreenshot
}
@@ -90,6 +91,13 @@
val uiState: Flow<TaskThumbnailUiState> =
combine(task.flatMapLatest { it }, isLiveTile) { taskVal, isRunning ->
+ // TODO(b/369339561) This log is firing a lot. Reduce emissions from TasksRepository
+ // then re-enable this log.
+ // Log.d(
+ // TAG,
+ // "Received task and / or live tile update. taskVal: $taskVal"
+ // + " isRunning: $isRunning.",
+ // )
when {
taskVal == null -> Uninitialized
isRunning -> LiveTile
@@ -103,6 +111,7 @@
.distinctUntilChanged()
fun bind(taskId: Int) {
+ Log.d(TAG, "bind taskId: $taskId")
this.taskId = taskId
task.value = tasksRepository.getTaskDataById(taskId)
splashProgress.value = splashAlphaUseCase.execute(taskId)
@@ -139,5 +148,6 @@
private companion object {
const val MAX_SCRIM_ALPHA = 0.4f
+ const val TAG = "TaskThumbnailViewModel"
}
}
diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java
index e1013db..7388d59 100644
--- a/quickstep/src/com/android/quickstep/util/AppPairsController.java
+++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java
@@ -26,7 +26,7 @@
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_NONE;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
@@ -189,7 +189,7 @@
@PersistentSnapPosition int snapPosition = gtv.getSnapPosition();
if (snapPosition == SNAP_TO_NONE) {
// Free snap mode is enabled, just save it as 50/50 split.
- snapPosition = SNAP_TO_50_50;
+ snapPosition = SNAP_TO_2_50_50;
}
if (!isPersistentSnapPosition(snapPosition)) {
// If we received an illegal snap position, log an error and do not create the app pair
diff --git a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
index 3a1c99b..64e46d8 100644
--- a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
+++ b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
@@ -85,6 +85,7 @@
.setBitmap(screenshot)
.setBoundsOnScreen(screenshotBounds)
.setInsets(visibleInsets)
+ .setDisplayId(task.displayId)
.build();
systemUiProxy.takeScreenshot(request);
}
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
index 15081da..8762e86 100644
--- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
+++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
@@ -98,10 +98,8 @@
mSpeedFast = res.getDimension(R.dimen.motion_pause_detector_speed_fast);
mForcePauseTimeout = new Alarm();
mForcePauseTimeout.setOnAlarmListener(alarm -> {
- ActiveGestureLog.CompoundString log =
- new ActiveGestureLog.CompoundString("Force pause timeout after ")
- .append(alarm.getLastSetTimeout())
- .append("ms");
+ ActiveGestureLog.CompoundString log = new ActiveGestureLog.CompoundString(
+ "Force pause timeout after %dms", alarm.getLastSetTimeout());
addLogs(log);
updatePaused(true /* isPaused */, log);
});
@@ -124,9 +122,8 @@
* @param disallowPause If true, we will not detect any pauses until this is set to false again.
*/
public void setDisallowPause(boolean disallowPause) {
- ActiveGestureLog.CompoundString log =
- new ActiveGestureLog.CompoundString("Set disallowPause=")
- .append(disallowPause);
+ ActiveGestureLog.CompoundString log = new ActiveGestureLog.CompoundString(
+ "Set disallowPause=%b", disallowPause);
if (mDisallowPause != disallowPause) {
addLogs(log);
}
@@ -188,8 +185,8 @@
speed < previousSpeed * getRapidDecelerationFactor();
isPaused = isRapidDeceleration && speed < mSpeedSomewhatFast;
isPausedReason = new ActiveGestureLog.CompoundString(
- "Didn't have back to back slow speeds, checking for rapid ")
- .append(" deceleration on first pause only");
+ "Didn't have back to back slow speeds, checking for rapid "
+ + " deceleration on first pause only");
}
if (mMakePauseHarderToTrigger) {
if (speed < mSpeedSlow) {
@@ -198,8 +195,8 @@
}
isPaused = time - mSlowStartTime >= HARDER_TRIGGER_TIMEOUT;
isPausedReason = new ActiveGestureLog.CompoundString(
- "Maintained slow speed for sufficient duration when making")
- .append(" pause harder to trigger");
+ "Maintained slow speed for sufficient duration when making"
+ + " pause harder to trigger");
} else {
mSlowStartTime = 0;
isPaused = false;
@@ -215,17 +212,14 @@
private void updatePaused(boolean isPaused, ActiveGestureLog.CompoundString reason) {
if (mDisallowPause) {
reason = new ActiveGestureLog.CompoundString(
- "Disallow pause; otherwise, would have been ")
- .append(isPaused)
- .append(" due to reason:")
+ "Disallow pause; otherwise, would have been %b due to reason: ", isPaused)
.append(reason);
isPaused = false;
}
if (mIsPaused != isPaused) {
mIsPaused = isPaused;
- addLogs(new ActiveGestureLog.CompoundString("onMotionPauseChanged triggered; paused=")
- .append(mIsPaused)
- .append(", reason=")
+ addLogs(new ActiveGestureLog.CompoundString(
+ "onMotionPauseChanged triggered; paused=%b, reason=", mIsPaused)
.append(reason));
boolean isFirstDetectedPause = !mHasEverBeenPaused && mIsPaused;
if (mIsPaused) {
@@ -245,14 +239,13 @@
}
}
- private void addLogs(ActiveGestureLog.CompoundString compoundString) {
- ActiveGestureLog.CompoundString logString =
- new ActiveGestureLog.CompoundString("MotionPauseDetector: ")
- .append(compoundString);
+ private void addLogs(ActiveGestureLog.CompoundString event) {
if (Utilities.isRunningInTestHarness()) {
- Log.d(TAG, logString.toString());
+ Log.d(TAG, new ActiveGestureLog.CompoundString("MotionPauseDetector: ")
+ .append(event)
+ .toString());
}
- ActiveGestureLog.INSTANCE.addLog(logString);
+ ActiveGestureProtoLogProxy.logMotionPauseDetectorEvent(event);
}
public void clear() {
diff --git a/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt
index be1af64..c3b072d 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt
+++ b/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt
@@ -66,13 +66,28 @@
fun getLargeTaskViewIds(taskViews: Iterable<TaskView>): List<Int> =
taskViews.filter { it.isLargeTile }.map { it.taskViewId }
+ /** Counts [TaskView]s that are large tiles. */
+ fun getLargeTileCount(taskViews: Iterable<TaskView>): Int = taskViews.count { it.isLargeTile }
+
/**
* Returns the first TaskView that should be displayed as a large tile.
*
* @param taskViews List of [TaskView]s
+ * @param splitSelectActive current split state
*/
- fun getFirstLargeTaskView(taskViews: Iterable<TaskView>): TaskView? =
- taskViews.firstOrNull { it.isLargeTile }
+ fun getFirstLargeTaskView(
+ taskViews: MutableIterable<TaskView>,
+ splitSelectActive: Boolean,
+ ): TaskView? =
+ taskViews.firstOrNull { it.isLargeTile && !(splitSelectActive && it is DesktopTaskView) }
+
+ /**
+ * Returns the first TaskView that is not large
+ *
+ * @param taskViews List of [TaskView]s
+ */
+ fun getFirstSmallTaskView(taskViews: MutableIterable<TaskView>): TaskView? =
+ taskViews.firstOrNull { !it.isLargeTile }
/** Returns the last TaskView that should be displayed as a large tile. */
fun getLastLargeTaskView(taskViews: Iterable<TaskView>): TaskView? =
@@ -80,24 +95,30 @@
/** Returns the first [TaskView], with some tasks possibly hidden in the carousel. */
fun getFirstTaskViewInCarousel(
- nonRunningTaskCategoryHidden: Boolean,
+ nonRunningTaskCarouselHidden: Boolean,
taskViews: Iterable<TaskView>,
runningTaskView: TaskView?,
): TaskView? =
taskViews.firstOrNull {
- it.isVisibleInCarousel(runningTaskView, nonRunningTaskCategoryHidden)
+ it.isVisibleInCarousel(runningTaskView, nonRunningTaskCarouselHidden)
}
/** Returns the last [TaskView], with some tasks possibly hidden in the carousel. */
fun getLastTaskViewInCarousel(
- nonRunningTaskCategoryHidden: Boolean,
+ nonRunningTaskCarouselHidden: Boolean,
taskViews: Iterable<TaskView>,
runningTaskView: TaskView?,
): TaskView? =
taskViews.lastOrNull {
- it.isVisibleInCarousel(runningTaskView, nonRunningTaskCategoryHidden)
+ it.isVisibleInCarousel(runningTaskView, nonRunningTaskCarouselHidden)
}
+ /** Returns if any small tasks are fully visible */
+ fun isAnySmallTaskFullyVisible(
+ taskViews: Iterable<TaskView>,
+ isTaskViewFullyVisible: (TaskView) -> Boolean,
+ ): Boolean = taskViews.any { !it.isLargeTile && isTaskViewFullyVisible(it) }
+
/** Returns the current list of [TaskView] children. */
fun getTaskViews(taskViewCount: Int, requireTaskViewAt: (Int) -> TaskView): Iterable<TaskView> =
(0 until taskViewCount).map(requireTaskViewAt)
@@ -106,28 +127,33 @@
fun applyAttachAlpha(
taskViews: Iterable<TaskView>,
runningTaskView: TaskView?,
- runningTaskTileHidden: Boolean,
- nonRunningTaskCategoryHidden: Boolean,
+ runningTaskAttachAlpha: Float,
+ nonRunningTaskCarouselHidden: Boolean,
) {
taskViews.forEach { taskView ->
- val isVisible =
- if (taskView == runningTaskView) !runningTaskTileHidden
- else taskView.isVisibleInCarousel(runningTaskView, nonRunningTaskCategoryHidden)
- taskView.attachAlpha = if (isVisible) 1f else 0f
+ taskView.attachAlpha =
+ if (taskView == runningTaskView) {
+ runningTaskAttachAlpha
+ } else {
+ if (taskView.isVisibleInCarousel(runningTaskView, nonRunningTaskCarouselHidden))
+ 1f
+ else 0f
+ }
}
}
- private fun TaskView.isVisibleInCarousel(
+ fun TaskView.isVisibleInCarousel(
runningTaskView: TaskView?,
- nonRunningTaskCategoryHidden: Boolean,
+ nonRunningTaskCarouselHidden: Boolean,
): Boolean =
- if (!nonRunningTaskCategoryHidden) true
- else if (runningTaskView == null) true else getCategory() == runningTaskView.getCategory()
+ if (!nonRunningTaskCarouselHidden) true
+ else getCarouselType() == runningTaskView.getCarouselType()
- private fun TaskView.getCategory(): TaskViewCategory =
- if (this is DesktopTaskView) TaskViewCategory.DESKTOP else TaskViewCategory.FULL_SCREEN
+ /** Returns the carousel type of the TaskView, and default to fullscreen if it's null. */
+ private fun TaskView?.getCarouselType(): TaskViewCarousel =
+ if (this is DesktopTaskView) TaskViewCarousel.DESKTOP else TaskViewCarousel.FULL_SCREEN
- private enum class TaskViewCategory {
+ private enum class TaskViewCarousel {
FULL_SCREEN,
DESKTOP,
}
diff --git a/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt b/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt
index f547a7fb..a94d023 100644
--- a/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt
+++ b/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt
@@ -23,8 +23,10 @@
import android.view.View
import android.view.animation.PathInterpolator
import androidx.core.graphics.transform
+import com.android.app.animation.Animations
import com.android.app.animation.Interpolators
import com.android.app.animation.Interpolators.LINEAR
+import com.android.launcher3.Flags
import com.android.launcher3.LauncherAnimUtils.HOTSEAT_SCALE_PROPERTY_FACTORY
import com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_WORKSPACE_STATE
import com.android.launcher3.LauncherAnimUtils.WORKSPACE_SCALE_PROPERTY_FACTORY
@@ -44,9 +46,9 @@
* the screen outwards radially. This is used in conjunction with the swipe up to home animation.
*/
class ScalingWorkspaceRevealAnim(
- launcher: QuickstepLauncher,
+ private val launcher: QuickstepLauncher,
siblingAnimation: RectFSpringAnim?,
- windowTargetRect: RectF?
+ windowTargetRect: RectF?,
) {
companion object {
private const val FADE_DURATION_MS = 200L
@@ -86,25 +88,40 @@
launcher.workspace.stateTransitionAnimation.setScrim(
PropertySetter.NO_ANIM_PROPERTY_SETTER,
LauncherState.BACKGROUND_APP,
- setupConfig
+ setupConfig,
)
val workspace = launcher.workspace
val hotseat = launcher.hotseat
+ var fromSize =
+ if (Flags.coordinateWorkspaceScale()) {
+ // Interrupt the current animation, if any.
+ Animations.cancelOngoingAnimation(workspace)
+ Animations.cancelOngoingAnimation(hotseat)
+
+ if (workspace.scaleX != MAX_SIZE) {
+ workspace.scaleX
+ } else {
+ MIN_SIZE
+ }
+ } else {
+ MIN_SIZE
+ }
+
// Scale the Workspace and Hotseat around the same pivot.
workspace.setPivotToScaleWithSelf(hotseat)
animation.addFloat(
workspace,
WORKSPACE_SCALE_PROPERTY_FACTORY[SCALE_INDEX_WORKSPACE_STATE],
- MIN_SIZE,
+ fromSize,
MAX_SIZE,
SCALE_INTERPOLATOR,
)
animation.addFloat(
hotseat,
HOTSEAT_SCALE_PROPERTY_FACTORY[SCALE_INDEX_WORKSPACE_STATE],
- MIN_SIZE,
+ fromSize,
MAX_SIZE,
SCALE_INTERPOLATOR,
)
@@ -116,13 +133,13 @@
animation.setViewAlpha(
workspace,
MAX_ALPHA,
- Interpolators.clampToProgress(LINEAR, 0f, fadeClamp)
+ Interpolators.clampToProgress(LINEAR, 0f, fadeClamp),
)
hotseat.alpha = MIN_ALPHA
animation.setViewAlpha(
hotseat,
MAX_ALPHA,
- Interpolators.clampToProgress(LINEAR, 0f, fadeClamp)
+ Interpolators.clampToProgress(LINEAR, 0f, fadeClamp),
)
val transitionConfig = StateAnimationConfig()
@@ -137,7 +154,7 @@
launcher.workspace.stateTransitionAnimation.setScrim(
animation,
LauncherState.NORMAL,
- transitionConfig
+ transitionConfig,
)
// To avoid awkward jumps in icon position, we want the sibling animation to always be
@@ -164,7 +181,7 @@
1 / workspace.scaleX,
1 / workspace.scaleY,
transformed.centerX(),
- transformed.centerY()
+ transformed.centerY(),
)
}
)
@@ -183,6 +200,12 @@
Runnable {
workspace.setLayerType(View.LAYER_TYPE_NONE, null)
hotseat.setLayerType(View.LAYER_TYPE_NONE, null)
+
+ if (Flags.coordinateWorkspaceScale()) {
+ // Reset the cached animations.
+ Animations.setOngoingAnimation(workspace, animation = null)
+ Animations.setOngoingAnimation(hotseat, animation = null)
+ }
}
)
)
@@ -193,6 +216,14 @@
}
fun start() {
- getAnimators().start()
+ val animators = getAnimators()
+ if (Flags.coordinateWorkspaceScale()) {
+ // Make sure to cache the current animation, so it can be properly interrupted.
+ // TODO(b/367591368): ideally these animations would be refactored to be controlled
+ // centrally so each instances doesn't need to care about this coordination.
+ Animations.setOngoingAnimation(launcher.workspace, animators)
+ Animations.setOngoingAnimation(launcher.hotseat, animators)
+ }
+ animators.start()
}
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index 3449cf2..f708f4b 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -238,7 +238,7 @@
taskViewHeight,
)
val snapshotViewSize =
- if (isPrimaryTaskSplitting) primarySnapshotViewSize else secondarySnapshotViewSize
+ if (isPrimaryTaskSplitting) secondarySnapshotViewSize else primarySnapshotViewSize
if (deviceProfile.isLeftRightSplit) {
// Center view first so scaling happens uniformly, alternatively we can move pivotX to 0
val centerThumbnailTranslationX: Float = (taskViewWidth - snapshotViewSize.x) / 2f
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java b/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java
index b618546..90569b4 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java
@@ -105,6 +105,10 @@
default Interpolator getGridSlidePrimaryInterpolator() { return LINEAR; }
default Interpolator getGridSlideSecondaryInterpolator() { return LINEAR; }
+ default Interpolator getDesktopTaskFadeInterpolator() {
+ return LINEAR;
+ }
+
// Defaults for HomeToSplit
default float getScrimFadeInStartOffset() { return 0; }
default float getScrimFadeInEndOffset() { return 0; }
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index fbeeef2..8f579e2 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -35,7 +35,7 @@
import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_SHORTCUT;
import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_TASK;
import static com.android.wm.shell.shared.split.SplitScreenConstants.KEY_EXTRA_WIDGET_INTENT;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -363,7 +363,7 @@
* A version of {@link #launchSplitTasks(int, Consumer)} that launches with default split ratio.
*/
public void launchSplitTasks(@Nullable Consumer<Boolean> callback) {
- launchSplitTasks(SNAP_TO_50_50, callback);
+ launchSplitTasks(SNAP_TO_2_50_50, callback);
}
/**
@@ -371,7 +371,7 @@
* ratio and no callback.
*/
public void launchSplitTasks() {
- launchSplitTasks(SNAP_TO_50_50, null);
+ launchSplitTasks(SNAP_TO_2_50_50, null);
}
/**
@@ -565,13 +565,13 @@
switch (launchData.getSplitLaunchType()) {
case SPLIT_SINGLE_TASK_FULLSCREEN -> mSystemUiProxy.startTasks(firstTaskId,
optionsBundle, secondTaskId, null /* options2 */, initialStagePosition,
- SNAP_TO_50_50, remoteTransition, instanceId);
+ SNAP_TO_2_50_50, remoteTransition, instanceId);
case SPLIT_SINGLE_INTENT_FULLSCREEN -> mSystemUiProxy.startIntentAndTask(firstPI,
firstUserId, optionsBundle, secondTaskId, null /*options2*/,
- initialStagePosition, SNAP_TO_50_50, remoteTransition, instanceId);
+ initialStagePosition, SNAP_TO_2_50_50, remoteTransition, instanceId);
case SPLIT_SINGLE_SHORTCUT_FULLSCREEN -> mSystemUiProxy.startShortcutAndTask(
initialShortcut, optionsBundle, firstTaskId, null /* options2 */,
- initialStagePosition, SNAP_TO_50_50, remoteTransition, instanceId);
+ initialStagePosition, SNAP_TO_2_50_50, remoteTransition, instanceId);
}
}
@@ -743,6 +743,7 @@
*/
public void resetState() {
mSplitSelectDataHolder.resetState();
+ mContainer.<RecentsView>getOverviewPanel().resetDesktopTaskFromSplitSelectState();
dispatchOnSplitSelectionExit();
mRecentsAnimationRunning = false;
mLaunchingTaskView = null;
diff --git a/quickstep/src/com/android/quickstep/util/TaskRemovedDuringLaunchListener.java b/quickstep/src/com/android/quickstep/util/TaskRemovedDuringLaunchListener.java
index e80d2a6..40a328c 100644
--- a/quickstep/src/com/android/quickstep/util/TaskRemovedDuringLaunchListener.java
+++ b/quickstep/src/com/android/quickstep/util/TaskRemovedDuringLaunchListener.java
@@ -98,10 +98,7 @@
final Runnable taskLaunchFailedCallback = mTaskLaunchFailedCallback;
RecentsModel.INSTANCE.get(mContext).isTaskRemoved(mLaunchedTaskId, (taskRemoved) -> {
if (taskRemoved) {
- ActiveGestureLog.INSTANCE.addLog(
- new ActiveGestureLog.CompoundString("Launch failed, task (id=")
- .append(launchedTaskId)
- .append(") finished mid transition"));
+ ActiveGestureProtoLogProxy.logTaskLaunchFailed(launchedTaskId);
taskLaunchFailedCallback.run();
}
}, (task) -> true /* filter */);
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index c7777d8..f5be103 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -305,6 +305,14 @@
}
/**
+ * Override the pivot used to apply scale changes.
+ */
+ public void setPivotOverride(PointF pivotOverride) {
+ mPivotOverride = pivotOverride;
+ getFullScreenScale();
+ }
+
+ /**
* Adds animation for all the components corresponding to transition from an app to overview.
*/
public void addAppToOverviewAnim(PendingAnimation pa, Interpolator interpolator) {
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
index 6db0923..8c854e7 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
@@ -59,7 +59,7 @@
this,
R.layout.task_thumbnail_deprecated,
VIEW_POOL_MAX_SIZE,
- VIEW_POOL_INITIAL_SIZE
+ VIEW_POOL_INITIAL_SIZE,
)
private val tempPointF = PointF()
private val tempRect = Rect()
@@ -80,7 +80,7 @@
setTint(
resources.getColor(
android.R.color.system_neutral2_300,
- context.theme
+ context.theme,
)
)
}
@@ -92,8 +92,8 @@
ResourcesCompat.getDrawable(
context.resources,
R.drawable.ic_desktop_with_bg,
- context.theme
- )
+ context.theme,
+ ),
)
setText(resources.getText(R.string.recent_task_desktop))
}
@@ -104,7 +104,7 @@
fun bind(
tasks: List<Task>,
orientedState: RecentsOrientedState,
- taskOverlayFactory: TaskOverlayFactory
+ taskOverlayFactory: TaskOverlayFactory,
) {
if (DEBUG) {
val sb = StringBuilder()
@@ -126,7 +126,7 @@
snapshotView,
// Add snapshotView to the front after initial views e.g. icon and
// background.
- childCountAtInflation
+ childCountAtInflation,
)
TaskContainer(
this,
@@ -137,7 +137,7 @@
SplitConfigurationOptions.STAGE_POSITION_UNDEFINED,
digitalWellBeingToast = null,
showWindowsView = null,
- taskOverlayFactory
+ taskOverlayFactory,
)
}
taskContainers.forEach { it.bind() }
@@ -159,12 +159,12 @@
override fun updateTaskSize(
lastComputedTaskSize: Rect,
lastComputedGridTaskSize: Rect,
- lastComputedCarouselTaskSize: Rect
+ lastComputedCarouselTaskSize: Rect,
) {
super.updateTaskSize(
lastComputedTaskSize,
lastComputedGridTaskSize,
- lastComputedCarouselTaskSize
+ lastComputedCarouselTaskSize,
)
if (taskContainers.isEmpty()) {
return
@@ -186,7 +186,7 @@
Log.d(
TAG,
"onMeasure: container=[$containerWidth,$containerHeight]" +
- "window=[$windowWidth,$windowHeight] scale=[$scaleWidth,$scaleHeight]"
+ "window=[$windowWidth,$windowHeight] scale=[$scaleWidth,$scaleHeight]",
)
}
@@ -218,7 +218,7 @@
Log.d(
TAG,
"onMeasure: task=${it.task.key} size=[$width,$height]" +
- " margin=[$leftMargin,$topMargin]"
+ " margin=[$leftMargin,$topMargin]",
)
}
}
@@ -252,7 +252,7 @@
TestLogging.recordEvent(
TestProtocol.SEQUENCE_MAIN,
"launchDesktopFromRecents",
- taskIds.contentToString()
+ taskIds.contentToString(),
)
val endCallback = RunnableList()
val desktopController = recentsView.desktopRecentsController
@@ -262,7 +262,7 @@
}
Log.d(
TAG,
- "launchTaskWithDesktopController: ${taskIds.contentToString()}, withRemoteTransition: $animated"
+ "launchTaskWithDesktopController: ${taskIds.contentToString()}, withRemoteTransition: $animated",
)
// Callbacks get run from recentsView for case when recents animation already running
@@ -274,11 +274,13 @@
override fun launchWithoutAnimation(
isQuickSwitch: Boolean,
- callback: (launched: Boolean) -> Unit
+ callback: (launched: Boolean) -> Unit,
) = launchTaskWithDesktopController(animated = false)?.add { callback(true) } ?: callback(false)
- // Desktop tile can't be in split screen
- override fun confirmSecondSplitSelectApp(): Boolean = false
+ // Return true when Task cannot be launched as fullscreen (i.e. in split select state) to skip
+ // putting DesktopTaskView to split as it's not supported.
+ override fun confirmSecondSplitSelectApp(): Boolean =
+ recentsView?.canLaunchFullscreenTask() != true
// TODO(b/330685808) support overlay for Screenshot action
override fun setOverlayEnabled(overlayEnabled: Boolean) {}
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 73edb9e..00e57c2 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -18,14 +18,11 @@
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY;
-import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.CLEAR_ALL_BUTTON;
-import static com.android.launcher3.LauncherState.EDIT_MODE;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
-import static com.android.launcher3.LauncherState.SPRING_LOADED;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_HOME;
import android.annotation.TargetApi;
@@ -173,8 +170,7 @@
@Override
public void onStateTransitionComplete(LauncherState finalState) {
- if (finalState == NORMAL || finalState == SPRING_LOADED || finalState == EDIT_MODE
- || finalState == ALL_APPS) {
+ if (!finalState.isRecentsViewVisible) {
// Clean-up logic that occurs when recents is no longer in use/visible.
reset();
}
@@ -254,7 +250,7 @@
}
@Override
- protected boolean canLaunchFullscreenTask() {
+ public boolean canLaunchFullscreenTask() {
if (FeatureFlags.enableSplitContextually()) {
return !mSplitSelectStateController.isSplitSelectActive();
} else {
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index da68a03..82f08e5 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -205,8 +205,7 @@
import com.android.quickstep.recents.di.RecentsDependencies;
import com.android.quickstep.recents.viewmodel.RecentsViewData;
import com.android.quickstep.recents.viewmodel.RecentsViewModel;
-import com.android.quickstep.util.ActiveGestureErrorDetector;
-import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.ActiveGestureProtoLogProxy;
import com.android.quickstep.util.AnimUtils;
import com.android.quickstep.util.DesktopTask;
import com.android.quickstep.util.GroupTask;
@@ -236,6 +235,8 @@
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
+import kotlin.Unit;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -249,8 +250,6 @@
import java.util.function.Consumer;
import java.util.stream.Collectors;
-import kotlin.Unit;
-
/**
* A list of recent tasks.
*
@@ -325,20 +324,13 @@
new FloatProperty<RecentsView>("runningTaskAttachAlpha") {
@Override
public void setValue(RecentsView recentsView, float v) {
- TaskView runningTask = recentsView.getRunningTaskView();
- if (runningTask == null) {
- return;
- }
- runningTask.setAttachAlpha(v);
+ recentsView.mRunningTaskAttachAlpha = v;
+ recentsView.applyAttachAlpha();
}
@Override
public Float get(RecentsView recentsView) {
- TaskView runningTask = recentsView.getRunningTaskView();
- if (runningTask == null) {
- return null;
- }
- return runningTask.getAttachAlpha();
+ return recentsView.mRunningTaskAttachAlpha;
}
};
@@ -464,6 +456,21 @@
}
};
+ public static final FloatProperty<RecentsView> DESKTOP_CAROUSEL_DETACH_PROGRESS =
+ new FloatProperty<>("desktopCarouselDetachProgress") {
+ @Override
+ public void setValue(RecentsView view, float offset) {
+ view.mDesktopCarouselDetachProgress = offset;
+ view.applyAttachAlpha();
+ view.updatePageOffsets();
+ }
+
+ @Override
+ public Float get(RecentsView view) {
+ return view.mDesktopCarouselDetachProgress;
+ }
+ };
+
/**
* Alpha of the task thumbnail splash, where being in BackgroundAppState has a value of 1, and
* being in any other state has a value of 0.
@@ -575,6 +582,7 @@
private int mClampedScrollOffsetBound;
private float mAdjacentPageHorizontalOffset = 0;
+ private float mDesktopCarouselDetachProgress = 0;
protected float mTaskViewsSecondaryTranslation = 0;
protected float mTaskViewsPrimarySplitTranslation = 0;
protected float mTaskViewsSecondarySplitTranslation = 0;
@@ -681,13 +689,13 @@
protected int mRunningTaskViewId = -1;
private int mTaskViewIdCount;
protected boolean mRunningTaskTileHidden;
- private boolean mNonRunningTaskCategoryHidden;
@Nullable
private Task[] mTmpRunningTasks;
protected int mFocusedTaskViewId = INVALID_TASK_ID;
private boolean mTaskIconScaledDown = false;
private boolean mRunningTaskShowScreenshot = false;
+ private float mRunningTaskAttachAlpha;
private boolean mOverviewStateEnabled;
private boolean mHandleTaskStackChanges;
@@ -799,12 +807,6 @@
@Nullable
private DesktopRecentsTransitionController mDesktopRecentsTransitionController;
- /**
- * Keeps track of the desktop task. Optional and only present when the feature flag is enabled.
- */
- @Nullable
- private DesktopTaskView mDesktopTaskView;
-
private MultiWindowModeChangedListener mMultiWindowModeChangedListener =
new MultiWindowModeChangedListener() {
@Override
@@ -1178,7 +1180,7 @@
*
* @return {@code true} if child TaskViews can be launched when user taps on them
*/
- protected boolean canLaunchFullscreenTask() {
+ public boolean canLaunchFullscreenTask() {
return true;
}
@@ -1587,8 +1589,7 @@
@Override
protected void onPageEndTransition() {
super.onPageEndTransition();
- ActiveGestureLog.INSTANCE.addLog(
- "onPageEndTransition: current page index updated", getNextPage());
+ ActiveGestureProtoLogProxy.logOnPageEndTransition(getNextPage());
if (isClearAllHidden() && !mContainer.getDeviceProfile().isTablet) {
mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false);
}
@@ -1705,10 +1706,11 @@
return;
}
TaskView taskView = getTaskViewAt(mNextPage);
- // Snap to fully visible focused task and clear all button.
boolean shouldSnapToLargeTask = taskView != null && taskView.isLargeTile()
- && isTaskViewFullyVisible(taskView);
+ && !mUtils.isAnySmallTaskFullyVisible(getTaskViews(),
+ this::isTaskViewFullyVisible);
boolean shouldSnapToClearAll = mNextPage == indexOfChild(mClearAllButton);
+ // Snap to large tile when grid tasks aren't fully visible or the clear all button.
if (!shouldSnapToLargeTask && !shouldSnapToClearAll) {
return;
}
@@ -1792,8 +1794,7 @@
@Override
protected void onScrollerAnimationAborted() {
- ActiveGestureLog.INSTANCE.addLog("scroller animation aborted",
- ActiveGestureErrorDetector.GestureEvent.SCROLLER_ANIMATION_ABORTED);
+ ActiveGestureProtoLogProxy.logOnScrollerAnimationAborted();
}
@Override
@@ -1873,7 +1874,6 @@
mFilterState.updateInstanceCountMap(taskGroups);
// Clear out desktop view if it is set
- mDesktopTaskView = null;
// Move Desktop Tasks to the end of the list
if (enableLargeDesktopWindowingTile()) {
@@ -1912,7 +1912,6 @@
.toList();
((DesktopTaskView) taskView).bind(nonMinimizedTasks, mOrientationState,
mTaskOverlayFactory);
- mDesktopTaskView = (DesktopTaskView) taskView;
} else {
Task task = groupTask.task1.key.id == stagedTaskIdToBeRemoved ? groupTask.task2
: groupTask.task1;
@@ -2744,9 +2743,6 @@
setEnableFreeScroll(false);
setEnableDrawingLiveTile(false);
setRunningTaskHidden(true);
- if (enableLargeDesktopWindowingTile()) {
- setNonRunningTaskCategoryHidden(true);
- }
setTaskIconScaledDown(true);
}
@@ -2870,6 +2866,14 @@
animatorSet.play(
ObjectAnimator.ofFloat(this, TASK_THUMBNAIL_SPLASH_ALPHA, splashAlpha));
}
+ if (enableLargeDesktopWindowingTile()) {
+ if (animatorSet != null) {
+ animatorSet.play(
+ ObjectAnimator.ofFloat(this, DESKTOP_CAROUSEL_DETACH_PROGRESS, 0f));
+ } else {
+ DESKTOP_CAROUSEL_DETACH_PROGRESS.set(this, 0f);
+ }
+ }
}
/**
@@ -2885,9 +2889,6 @@
setEnableDrawingLiveTile(mCurrentGestureEndTarget == GestureState.GestureEndTarget.RECENTS);
Log.d(TAG, "onGestureAnimationEnd - mEnableDrawingLiveTile: " + mEnableDrawingLiveTile);
setRunningTaskHidden(false);
- if (enableLargeDesktopWindowingTile()) {
- setNonRunningTaskCategoryHidden(false);
- }
animateUpTaskIconScale();
animateActionsViewIn();
@@ -3039,6 +3040,9 @@
*/
public void setRunningTaskHidden(boolean isHidden) {
mRunningTaskTileHidden = isHidden;
+ // mRunningTaskAttachAlpha can be changed by RUNNING_TASK_ATTACH_ALPHA animation without
+ // changing mRunningTaskTileHidden.
+ mRunningTaskAttachAlpha = isHidden ? 0f : 1f;
TaskView runningTask = getRunningTaskView();
if (runningTask == null) {
return;
@@ -3050,18 +3054,11 @@
}
}
- /**
- * Hides the tasks that has a different category (Fullscreen/Desktop) from the running task.
- */
- public void setNonRunningTaskCategoryHidden(boolean isHidden) {
- mNonRunningTaskCategoryHidden = isHidden;
- updateMinAndMaxScrollX();
- applyAttachAlpha();
- }
-
private void applyAttachAlpha() {
- mUtils.applyAttachAlpha(getTaskViews(), getRunningTaskView(), mRunningTaskTileHidden,
- mNonRunningTaskCategoryHidden);
+ // Only hide non running task carousel when it's fully off screen, otherwise it needs to
+ // be visible to move to on screen.
+ mUtils.applyAttachAlpha(getTaskViews(), getRunningTaskView(), mRunningTaskAttachAlpha,
+ /*nonRunningTaskCarouselHidden=*/mDesktopCarouselDetachProgress == 1f);
}
private void setRunningTaskViewShowScreenshot(boolean showScreenshot) {
@@ -3144,7 +3141,7 @@
// Horizontal grid translation for each task
float[] gridTranslations = new float[taskCount];
- int focusedTaskIndex = Integer.MAX_VALUE;
+ int lastLargeTaskIndex = Integer.MAX_VALUE;
Set<Integer> largeTasksIndices = new HashSet<>();
int focusedTaskShift = 0;
int largeTaskWidthAndSpacing = 0;
@@ -3167,8 +3164,12 @@
boolean isLargeTile = taskView.isLargeTile();
if (isLargeTile) {
- topRowWidth += taskWidthAndSpacing;
- bottomRowWidth += taskWidthAndSpacing;
+ // DesktopTaskView`s are hidden during split select state, so we shouldn't count
+ // them when calculating row width.
+ if (!(taskView instanceof DesktopTaskView && isSplitSelectionActive())) {
+ topRowWidth += taskWidthAndSpacing;
+ bottomRowWidth += taskWidthAndSpacing;
+ }
gridTranslations[i] += focusedTaskShift;
gridTranslations[i] += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing;
@@ -3176,9 +3177,7 @@
taskView.setGridTranslationY((mLastComputedTaskSize.height() + taskTopMargin
- taskView.getLayoutParams().height) / 2f);
- if (taskView.getTaskViewId() == mFocusedTaskViewId) {
- focusedTaskIndex = i;
- }
+ lastLargeTaskIndex = i;
largeTasksIndices.add(i);
largeTaskWidthAndSpacing = taskWidthAndSpacing;
@@ -3187,8 +3186,8 @@
snappedTaskRowWidth = taskWidthAndSpacing;
}
} else {
- if (i > focusedTaskIndex) {
- // For tasks after the focused task, shift by focused task's width and spacing.
+ if (i > lastLargeTaskIndex) {
+ // For tasks after the last large task, shift by large task's width and spacing.
gridTranslations[i] +=
mIsRtl ? largeTaskWidthAndSpacing : -largeTaskWidthAndSpacing;
} else {
@@ -3613,10 +3612,10 @@
float dismissedTaskWidth = 0;
float nextFocusedTaskWidth = 0;
- // Non-grid specific properties.
int[] oldScroll = new int[count];
int[] newScroll = new int[count];
int scrollDiffPerPage = 0;
+ // Non-grid specific properties.
boolean needsCurveUpdates = false;
if (showAsGrid) {
@@ -3646,13 +3645,13 @@
}
}
}
- } else {
- getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC);
- getPageScrolls(newScroll, false,
- v -> v.getVisibility() != GONE && v != dismissedTaskView);
- if (count > 1) {
- scrollDiffPerPage = Math.abs(oldScroll[1] - oldScroll[0]);
- }
+ }
+
+ getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC);
+ getPageScrolls(newScroll, false,
+ v -> v.getVisibility() != GONE && v != dismissedTaskView);
+ if (count > 1) {
+ scrollDiffPerPage = Math.abs(oldScroll[1] - oldScroll[0]);
}
float dismissTranslationInterpolationEnd = 1;
@@ -3672,8 +3671,8 @@
float longGridRowWidthDiff = 0;
int topGridRowSize = mTopRowIdSet.size();
- int bottomGridRowSize = taskCount - mTopRowIdSet.size()
- - (enableGridOnlyOverview() ? 0 : 1);
+ int numLargeTiles = mUtils.getLargeTileCount(getTaskViews());
+ int bottomGridRowSize = taskCount - mTopRowIdSet.size() - numLargeTiles;
boolean topRowLonger = topGridRowSize > bottomGridRowSize;
boolean bottomRowLonger = bottomGridRowSize > topGridRowSize;
boolean dismissedTaskFromTop = mTopRowIdSet.contains(dismissedTaskViewId);
@@ -3804,89 +3803,30 @@
addDismissedTaskAnimations(dismissedTaskView, duration, anim);
}
}
- } else if (!showAsGrid) {
- // Compute scroll offsets from task dismissal for animation.
- // If we just take newScroll - oldScroll, everything to the right of dragged task
- // translates to the left. We need to offset this in some cases:
- // - In RTL, add page offset to all pages, since we want pages to move to the right
- // Additionally, add a page offset if:
- // - Current page is rightmost page (leftmost for RTL)
- // - Dragging an adjacent page on the left side (right side for RTL)
- int offset = mIsRtl ? scrollDiffPerPage : 0;
- if (mCurrentPage == dismissedIndex) {
- int lastPage = taskCount - 1;
- if (mCurrentPage == lastPage) {
- offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage;
- }
- } else {
- // Dismissing an adjacent page.
- int negativeAdjacent = mCurrentPage - 1; // (Right in RTL, left in LTR)
- if (dismissedIndex == negativeAdjacent) {
- offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage;
- }
- }
-
+ } else if (!showAsGrid || (enableLargeDesktopWindowingTile()
+ && dismissedTaskView.isLargeTile()
+ && nextFocusedTaskView == null)) {
+ int offset = getOffsetToDismissedTask(scrollDiffPerPage, dismissedIndex, taskCount);
int scrollDiff = newScroll[i] - oldScroll[i] + offset;
if (scrollDiff != 0) {
- FloatProperty translationProperty = child instanceof TaskView
- ? ((TaskView) child).getPrimaryDismissTranslationProperty()
- : getPagedOrientationHandler().getPrimaryViewTranslate();
-
- float additionalDismissDuration =
- ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET * Math.abs(
- i - dismissedIndex);
-
- // We are in non-grid layout.
- // If dismissing for split select, use split timings.
- // If not, use dismiss timings.
- float animationStartProgress = isSplitSelectionActive()
- ? Utilities.boundToRange(splitTimings.getGridSlideStartOffset(), 0f, 1f)
- : Utilities.boundToRange(
- INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
- + additionalDismissDuration, 0f, 1f);
-
- float animationEndProgress = isSplitSelectionActive()
- ? Utilities.boundToRange(splitTimings.getGridSlideStartOffset()
- + splitTimings.getGridSlideDurationOffset(), 0f, 1f)
- : 1f;
-
- // Slide tiles in horizontally to fill dismissed area
- anim.setFloat(child, translationProperty, scrollDiff,
- clampToProgress(
- splitTimings.getGridSlidePrimaryInterpolator(),
- animationStartProgress,
- animationEndProgress
- )
- );
-
- if (mEnableDrawingLiveTile && child instanceof TaskView
- && ((TaskView) child).isRunningTask()) {
- anim.addOnFrameCallback(() -> {
- runActionOnRemoteHandles(
- remoteTargetHandle ->
- remoteTargetHandle.getTaskViewSimulator()
- .taskPrimaryTranslation.value =
- getPagedOrientationHandler().getPrimaryValue(
- child.getTranslationX(),
- child.getTranslationY()
- ));
- redrawLiveTile();
- });
- }
+ translateTaskWhenDismissed(
+ child,
+ Math.abs(i - dismissedIndex),
+ scrollDiff,
+ anim,
+ splitTimings);
needsCurveUpdates = true;
}
- } else if (child instanceof TaskView) {
- TaskView taskView = (TaskView) child;
+ } else if (child instanceof TaskView taskView) {
if (isFocusedTaskDismissed) {
if (nextFocusedTaskView != null &&
!isSameGridRow(taskView, nextFocusedTaskView)) {
continue;
}
- } else {
- if (i < dismissedIndex || !isSameGridRow(taskView, dismissedTaskView)) {
- continue;
- }
+ } else if (i < dismissedIndex || !isSameGridRow(taskView, dismissedTaskView)) {
+ continue;
}
+
// Animate task with index >= dismissed index and in the same row as the
// dismissed index or next focused index. Offset successive task dismissal
// durations for a staggered effect.
@@ -3972,12 +3912,12 @@
final boolean finalCloseGapBetweenClearAll = closeGapBetweenClearAll;
final boolean finalSnapToLastTask = snapToLastTask;
final boolean finalIsFocusedTaskDismissed = isFocusedTaskDismissed;
- mPendingAnimation.addEndListener(new Consumer<Boolean>() {
+ mPendingAnimation.addEndListener(new Consumer<>() {
@Override
public void accept(Boolean success) {
if (mEnableDrawingLiveTile && dismissedTaskView.isRunningTask() && success) {
finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
- () -> onEnd(success));
+ () -> onEnd(true));
} else {
onEnd(success);
}
@@ -4147,6 +4087,14 @@
// If snapping to last task, find the last task after dismissal.
pageToSnapTo = indexOfChild(
getLastGridTaskView(topRowIdArray, bottomRowIdArray));
+
+ if (pageToSnapTo == INVALID_PAGE) {
+ // Snap to latest large tile page after dismissing the
+ // last grid task. This will prevent snapping to page 0 when
+ // desktop task is visible as large tile.
+ pageToSnapTo = indexOfChild(
+ mUtils.getLastLargeTaskView(getTaskViews()));
+ }
} else if (taskViewIdToSnapTo != -1) {
// If snapping to another page due to indices rearranging, find
// the new index after dismissal & rearrange using the task view id.
@@ -4180,6 +4128,90 @@
}
/**
+ * Compute scroll offsets from task dismissal for animation.
+ * If we just take newScroll - oldScroll, everything to the right of dragged task
+ * translates to the left. We need to offset this in some cases:
+ * - In RTL, add page offset to all pages, since we want pages to move to the right
+ * Additionally, add a page offset if:
+ * - Current page is rightmost page (leftmost for RTL)
+ * - Dragging an adjacent page on the left side (right side for RTL)
+ */
+ private int getOffsetToDismissedTask(int scrollDiffPerPage, int dismissedIndex, int taskCount) {
+ // When mCurrentPage is ClearAllButton, use the last TaskView instead to calculate
+ // offset.
+ int currentPage = mCurrentPage == taskCount ? taskCount - 1 : mCurrentPage;
+ int offset = mIsRtl ? scrollDiffPerPage : 0;
+ if (currentPage == dismissedIndex) {
+ int lastPage = taskCount - 1;
+ if (currentPage == lastPage) {
+ offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage;
+ }
+ } else {
+ // Dismissing an adjacent page.
+ int negativeAdjacent = currentPage - 1; // (Right in RTL, left in LTR)
+ if (dismissedIndex == negativeAdjacent) {
+ offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage;
+ }
+ }
+ return offset;
+ }
+
+ private void translateTaskWhenDismissed(
+ View view,
+ int indexDiff,
+ int scrollDiffPerPage,
+ PendingAnimation pendingAnimation,
+ SplitAnimationTimings splitTimings) {
+ FloatProperty translationProperty = view instanceof TaskView
+ ? ((TaskView) view).getPrimaryDismissTranslationProperty()
+ : getPagedOrientationHandler().getPrimaryViewTranslate();
+
+ float additionalDismissDuration =
+ ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET * indexDiff;
+
+ // We are in non-grid layout.
+ // If dismissing for split select, use split timings.
+ // If not, use dismiss timings.
+ float animationStartProgress = isSplitSelectionActive()
+ ? Utilities.boundToRange(splitTimings.getGridSlideStartOffset(), 0f, 1f)
+ : Utilities.boundToRange(
+ INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
+ + additionalDismissDuration, 0f, 1f);
+
+ float animationEndProgress = isSplitSelectionActive()
+ ? Utilities.boundToRange(splitTimings.getGridSlideStartOffset()
+ + splitTimings.getGridSlideDurationOffset(), 0f, 1f)
+ : 1f;
+
+ // Slide tiles in horizontally to fill dismissed area
+ pendingAnimation.setFloat(
+ view,
+ translationProperty,
+ scrollDiffPerPage,
+ clampToProgress(
+ splitTimings.getGridSlidePrimaryInterpolator(),
+ animationStartProgress,
+ animationEndProgress
+ )
+ );
+
+ if (mEnableDrawingLiveTile && view instanceof TaskView
+ && ((TaskView) view).isRunningTask()) {
+ pendingAnimation.addOnFrameCallback(() -> {
+ runActionOnRemoteHandles(
+ remoteTargetHandle ->
+ remoteTargetHandle.getTaskViewSimulator()
+ .taskPrimaryTranslation.value =
+ getPagedOrientationHandler().getPrimaryValue(
+ view.getTranslationX(),
+ view.getTranslationY()
+ ));
+ redrawLiveTile();
+ });
+ }
+ }
+
+ /**
* Hides all overview actions if user is halfway through split selection, shows otherwise.
* We only show split option if:
* * Focused view is a single app
@@ -4674,6 +4706,10 @@
? (runningTask == null ? INVALID_PAGE : indexOfChild(runningTask))
: mOffsetMidpointIndexOverride;
int modalMidpoint = getCurrentPage();
+ TaskView carouselHiddenMidpointTask = runningTask != null ? runningTask
+ : mUtils.getFirstTaskViewInCarousel(/*nonRunningTaskCarouselHidden=*/true,
+ getTaskViews(), null);
+ int carouselHiddenMidpoint = indexOfChild(carouselHiddenMidpointTask);
boolean shouldCalculateOffsetForAllTasks = showAsGrid
&& (enableGridOnlyOverview() || enableLargeDesktopWindowingTile())
&& mTaskModalness > 0;
@@ -4693,6 +4729,7 @@
float modalLeftOffsetSize = 0;
float modalRightOffsetSize = 0;
float gridOffsetSize = 0;
+ float carouselHiddenOffsetSize = 0;
if (showAsGrid) {
// In grid, we only focus the task on the side. The reference index used for offset
@@ -4710,7 +4747,10 @@
: 0;
}
+ int primarySize = getPagedOrientationHandler().getPrimaryValue(getWidth(), getHeight());
+ float maxOverscroll = primarySize * OverScroll.OVERSCROLL_DAMP_FACTOR;
for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
float translation = i == midpoint
? midpointOffsetSize
: i < midpoint
@@ -4720,16 +4760,31 @@
gridOffsetSize = getHorizontalOffsetSize(i, modalMidpoint, modalOffset);
gridOffsetSize = Math.abs(gridOffsetSize) * (i <= modalMidpoint ? 1 : -1);
}
+ if (enableLargeDesktopWindowingTile()) {
+ if (child instanceof TaskView
+ && !mUtils.isVisibleInCarousel((TaskView) child,
+ runningTask, /*nonRunningTaskCarouselHidden=*/true)) {
+ // Increment carouselHiddenOffsetSize by maxOverscroll so it won't be on screen
+ // even when user overscroll.
+ carouselHiddenOffsetSize = (Math.abs(getMaxHorizontalOffsetSize(i,
+ carouselHiddenMidpoint)) + maxOverscroll)
+ * mDesktopCarouselDetachProgress;
+ carouselHiddenOffsetSize = carouselHiddenOffsetSize * (
+ i <= carouselHiddenMidpoint ? 1 : -1);
+ } else {
+ carouselHiddenOffsetSize = 0;
+ }
+ }
float modalTranslation = i == modalMidpoint
? modalMidpointOffsetSize
: showAsGrid
? gridOffsetSize
: i < modalMidpoint ? modalLeftOffsetSize : modalRightOffsetSize;
- View child = getChildAt(i);
boolean skipTranslationOffset = enableDesktopTaskAlphaAnimation()
&& i == getRunningTaskIndex()
&& child instanceof DesktopTaskView;
- float totalTranslationX = (skipTranslationOffset ? 0f : translation) + modalTranslation;
+ float totalTranslationX = (skipTranslationOffset ? 0f : translation) + modalTranslation
+ + carouselHiddenOffsetSize;
FloatProperty translationPropertyX = child instanceof TaskView
? ((TaskView) child).getPrimaryTaskOffsetTranslationProperty()
: getPagedOrientationHandler().getPrimaryViewTranslate();
@@ -4786,6 +4841,14 @@
return 0;
}
+ return getMaxHorizontalOffsetSize(childIndex, midpointIndex) * offsetProgress;
+ }
+
+ /**
+ * Computes the distance to offset the given child such that it is completely offscreen when
+ * translating away from the given midpoint.
+ */
+ private float getMaxHorizontalOffsetSize(int childIndex, int midpointIndex) {
// First, get the position of the task relative to the midpoint. If there is no midpoint
// then we just use the normal (centered) task position.
RectF taskPosition = mTempRectF;
@@ -4845,7 +4908,7 @@
}
distanceToOffscreen -= mLastComputedTaskEndPushOutDistance;
}
- return distanceToOffscreen * offsetProgress;
+ return distanceToOffscreen;
}
/**
@@ -4943,7 +5006,6 @@
mSplitSelectStateController.setAnimateCurrentTaskDismissal(
true /*animateCurrentTaskDismissal*/);
mSplitHiddenTaskViewIndex = indexOfChild(taskView);
- updateDesktopTaskVisibility(false /* visible */);
}
/**
@@ -4965,12 +5027,34 @@
mSplitSelectStateController.setInitialTaskSelect(splitSelectSource.intent,
splitSelectSource.position.stagePosition, splitSelectSource.getItemInfo(),
splitSelectSource.splitEvent, splitSelectSource.alreadyRunningTaskId);
- updateDesktopTaskVisibility(false /* visible */);
}
- private void updateDesktopTaskVisibility(boolean visible) {
- if (mDesktopTaskView != null) {
- mDesktopTaskView.setVisibility(visible ? VISIBLE : GONE);
+ /**
+ * Animate DesktopTaskView(s) to hide in split select
+ */
+ public void handleDesktopTaskInSplitSelectState(PendingAnimation builder,
+ Interpolator deskTopFadeInterPolator) {
+ if (enableLargeDesktopWindowingTile()) {
+ for (TaskView taskView : getTaskViews()) {
+ if (taskView instanceof DesktopTaskView) {
+ builder.addFloat(taskView.getSplitAlphaProperty(),
+ MULTI_PROPERTY_VALUE, 1f, 0f,
+ deskTopFadeInterPolator);
+ }
+ }
+ }
+ }
+
+ /**
+ * While exiting from split mode, show all existing DesktopTaskViews.
+ */
+ public void resetDesktopTaskFromSplitSelectState() {
+ if (enableLargeDesktopWindowingTile()) {
+ for (TaskView taskView : getTaskViews()) {
+ if (taskView instanceof DesktopTaskView) {
+ taskView.setSplitAlpha(1f);
+ }
+ }
}
}
@@ -5181,7 +5265,6 @@
mSplitHiddenTaskView.setThumbnailVisibility(VISIBLE, INVALID_TASK_ID);
mSplitHiddenTaskView = null;
}
- updateDesktopTaskVisibility(true /* visible */);
}
private void safeRemoveDragLayerView(@Nullable View viewToRemove) {
@@ -5339,6 +5422,13 @@
mTempPointF);
setPivotX(mTempPointF.x);
setPivotY(mTempPointF.y);
+ runActionOnRemoteHandles(
+ remoteTargetHandle -> {
+ remoteTargetHandle.getTaskViewSimulator().setPivotOverride(
+ mTempPointF);
+ remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(
+ false);
+ });
}
});
} else if (!showAsGrid) {
@@ -5807,16 +5897,18 @@
private int getFirstViewIndex() {
final TaskView firstView;
if (mShowAsGridLastOnLayout) {
- // For grid Overivew, it always start if a large tile (focused task or desktop task) if
+ // For grid Overview, it always start if a large tile (focused task or desktop task) if
// they exist, otherwise it start with the first task.
- TaskView firstLargeTaskView = mUtils.getFirstLargeTaskView(getTaskViews());
+ TaskView firstLargeTaskView = mUtils.getFirstLargeTaskView(getTaskViews(),
+ isSplitSelectionActive());
if (firstLargeTaskView != null) {
firstView = firstLargeTaskView;
} else {
- firstView = getTaskViewAt(0);
+ firstView = mUtils.getFirstSmallTaskView(getTaskViews());
}
} else {
- firstView = mUtils.getFirstTaskViewInCarousel(mNonRunningTaskCategoryHidden,
+ firstView = mUtils.getFirstTaskViewInCarousel(
+ /*nonRunningTaskCarouselHidden=*/mDesktopCarouselDetachProgress > 0,
getTaskViews(), getRunningTaskView());
}
return indexOfChild(firstView);
@@ -5837,7 +5929,8 @@
lastView = mUtils.getLastLargeTaskView(getTaskViews());
}
} else {
- lastView = mUtils.getLastTaskViewInCarousel(mNonRunningTaskCategoryHidden,
+ lastView = mUtils.getLastTaskViewInCarousel(
+ /*nonRunningTaskCarouselHidden=*/mDesktopCarouselDetachProgress > 0,
getTaskViews(), getRunningTaskView());
}
return indexOfChild(lastView);
diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
index 57d68a0..6cb7741 100644
--- a/quickstep/src/com/android/quickstep/views/TaskContainer.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
@@ -56,7 +56,7 @@
@SplitConfigurationOptions.StagePosition val stagePosition: Int,
val digitalWellBeingToast: DigitalWellBeingToast?,
val showWindowsView: View?,
- taskOverlayFactory: TaskOverlayFactory
+ taskOverlayFactory: TaskOverlayFactory,
) {
val overlay: TaskOverlayFactory.TaskOverlay<*> = taskOverlayFactory.createOverlay(this)
lateinit var taskContainerData: TaskContainerData
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 2ed6ae6..cc64dba 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -402,6 +402,15 @@
}
get() = taskViewAlpha.get(ALPHA_INDEX_ATTACH).value
+ var splitAlpha
+ set(value) {
+ splitAlphaProperty.value = value
+ }
+ get() = splitAlphaProperty.value
+
+ val splitAlphaProperty: MultiPropertyFactory<View>.MultiProperty
+ get() = taskViewAlpha.get(ALPHA_INDEX_SPLIT)
+
protected var shouldShowScreenshot = false
get() = !isRunningTask || field
private set
@@ -606,6 +615,7 @@
override fun onRecycle() {
resetPersistentViewTransforms()
attachAlpha = 1f
+ splitAlpha = 1f
// Clear any references to the thumbnail (it will be re-read either from the cache or the
// system on next bind)
if (!enableRefactorTaskThumbnail()) {
@@ -687,7 +697,6 @@
orientedState: RecentsOrientedState,
taskOverlayFactory: TaskOverlayFactory,
) {
-
cancelPendingLoadTasks()
taskContainers =
listOf(
@@ -720,6 +729,7 @@
thumbnailViewDeprecated.visibility = GONE
val indexOfSnapshotView = indexOfChild(thumbnailViewDeprecated)
LayoutInflater.from(context).inflate(R.layout.task_thumbnail, this, false).also {
+ it.id = thumbnailViewId
addView(it, indexOfSnapshotView, thumbnailViewDeprecated.layoutParams)
}
} else {
@@ -1687,8 +1697,9 @@
private const val ALPHA_INDEX_STABLE = 0
private const val ALPHA_INDEX_ATTACH = 1
+ private const val ALPHA_INDEX_SPLIT = 2
- private const val NUM_ALPHA_CHANNELS = 2
+ private const val NUM_ALPHA_CHANNELS = 3
/** The maximum amount that a task view can be scrimmed, dimmed or tinted. */
const val MAX_PAGE_SCRIM_ALPHA = 0.4f
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java b/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureErrorDetector.java
similarity index 99%
rename from quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
rename to quickstep/src_protolog/com/android/quickstep/util/ActiveGestureErrorDetector.java
index 2398e66..ab10979 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
+++ b/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureErrorDetector.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java b/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureLog.java
similarity index 78%
rename from quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
rename to quickstep/src_protolog/com/android/quickstep/util/ActiveGestureLog.java
index d46b8fc..23e245c 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
+++ b/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureLog.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -18,11 +18,10 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.launcher3.util.Preconditions;
-
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
@@ -72,14 +71,6 @@
addLog(event, null);
}
- public void addLog(@NonNull String event, int extras) {
- addLog(event, extras, null);
- }
-
- public void addLog(@NonNull String event, boolean extras) {
- addLog(event, extras, null);
- }
-
/**
* Adds a log to be printed at log-dump-time and track the associated event for error detection.
*
@@ -90,20 +81,6 @@
addLog(new CompoundString(event), gestureEvent);
}
- public void addLog(
- @NonNull String event,
- int extras,
- @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
- addLog(new CompoundString(event).append(": ").append(extras), gestureEvent);
- }
-
- public void addLog(
- @NonNull String event,
- boolean extras,
- @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
- addLog(new CompoundString(event).append(": ").append(extras), gestureEvent);
- }
-
public void addLog(@NonNull CompoundString compoundString) {
addLog(compoundString, null);
}
@@ -252,25 +229,27 @@
/** A buildable string stored as an array for memory efficiency. */
public static class CompoundString {
- public static final CompoundString NO_OP = new CompoundString();
+ public static final CompoundString NO_OP = new CompoundString(true);
private final List<String> mSubstrings;
private final List<Object> mArgs;
private final boolean mIsNoOp;
- private CompoundString() {
- this(null);
+ public static CompoundString newEmptyString() {
+ return new CompoundString(false);
}
- public CompoundString(String substring) {
- mIsNoOp = substring == null;
+ private CompoundString(boolean isNoOp) {
+ mIsNoOp = isNoOp;
mSubstrings = mIsNoOp ? null : new ArrayList<>();
mArgs = mIsNoOp ? null : new ArrayList<>();
+ }
- if (!mIsNoOp) {
- mSubstrings.add(substring);
- }
+ public CompoundString(String substring, Object... args) {
+ this(substring == null);
+
+ append(substring, args);
}
public CompoundString append(CompoundString substring) {
@@ -283,80 +262,24 @@
return this;
}
- public CompoundString append(String substring) {
+ public CompoundString append(String substring, Object... args) {
if (mIsNoOp) {
return this;
}
mSubstrings.add(substring);
+ mArgs.addAll(Arrays.stream(args).toList());
return this;
}
- public CompoundString append(int num) {
- if (mIsNoOp) {
- return this;
- }
- mArgs.add(num);
-
- return append("%d");
- }
-
- public CompoundString append(long num) {
- if (mIsNoOp) {
- return this;
- }
- mArgs.add(num);
-
- return append("%d");
- }
-
- public CompoundString append(float num) {
- if (mIsNoOp) {
- return this;
- }
- mArgs.add(num);
-
- return append("%.2f");
- }
-
- public CompoundString append(double num) {
- if (mIsNoOp) {
- return this;
- }
- mArgs.add(num);
-
- return append("%.2f");
- }
-
- public CompoundString append(boolean bool) {
- if (mIsNoOp) {
- return this;
- }
- mArgs.add(bool);
-
- return append("%b");
- }
-
- private Object[] getArgs() {
- Preconditions.assertTrue(!mIsNoOp);
-
- return mArgs.toArray();
- }
-
@Override
public String toString() {
- return String.format(toUnformattedString(), getArgs());
- }
-
- private String toUnformattedString() {
- Preconditions.assertTrue(!mIsNoOp);
-
+ if (mIsNoOp) return null;
StringBuilder sb = new StringBuilder();
for (String substring : mSubstrings) {
sb.append(substring);
}
-
- return sb.toString();
+ return String.format(sb.toString(), mArgs.toArray());
}
@Override
@@ -366,10 +289,9 @@
@Override
public boolean equals(Object obj) {
- if (!(obj instanceof CompoundString)) {
+ if (!(obj instanceof CompoundString other)) {
return false;
}
- CompoundString other = (CompoundString) obj;
return (mIsNoOp == other.mIsNoOp)
&& Objects.equals(mSubstrings, other.mSubstrings)
&& Objects.equals(mArgs, other.mArgs);
diff --git a/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java b/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java
new file mode 100644
index 0000000..f43a125
--- /dev/null
+++ b/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java
@@ -0,0 +1,502 @@
+/*
+ * 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.util;
+
+import static android.view.MotionEvent.ACTION_DOWN;
+
+import static com.android.launcher3.Flags.enableActiveGestureProtoLog;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.CANCEL_RECENTS_ANIMATION;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.FINISH_RECENTS_ANIMATION;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.INVALID_VELOCITY_ON_SWIPE_UP;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.LAUNCHER_DESTROYED;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_DOWN;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_MOVE;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_UP;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.NAVIGATION_MODE_SWITCHED;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_CANCEL_RECENTS_ANIMATION;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_FINISH_RECENTS_ANIMATION;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_SETTLED_ON_END_TARGET;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_START_RECENTS_ANIMATION;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.QUICK_SWITCH_FROM_HOME_FAILED;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.QUICK_SWITCH_FROM_HOME_FALLBACK;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.RECENTS_ANIMATION_START_PENDING;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.RECENT_TASKS_MISSING;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.START_RECENTS_ANIMATION;
+import static com.android.quickstep.util.QuickstepProtoLogGroup.ACTIVE_GESTURE_LOG;
+
+import android.view.MotionEvent;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.protolog.ProtoLog;
+import com.android.internal.protolog.common.IProtoLogGroup;
+
+/**
+ * Proxy class used for ActiveGestureLog ProtoLog support.
+ * <p>
+ * This file will have all of its static strings in the
+ * {@link ProtoLog#d(IProtoLogGroup, String, Object...)} calls replaced by dynamic code/strings.
+ * <p>
+ * When a new ActiveGestureLog entry needs to be added to the codebase (or and existing entry needs
+ * to be modified), add it here under a new unique method and make sure the ProtoLog entry matches
+ * to avoid confusion.
+ */
+public class ActiveGestureProtoLogProxy {
+
+ public static void logLauncherDestroyed() {
+ ActiveGestureLog.INSTANCE.addLog("Launcher destroyed", LAUNCHER_DESTROYED);
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "Launcher destroyed");
+ }
+
+ public static void logAbsSwipeUpHandlerOnRecentsAnimationCanceled() {
+ ActiveGestureLog.INSTANCE.addLog(
+ /* event= */ "AbsSwipeUpHandler.onRecentsAnimationCanceled",
+ /* gestureEvent= */ CANCEL_RECENTS_ANIMATION);
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "AbsSwipeUpHandler.onRecentsAnimationCanceled");
+ }
+
+ public static void logAbsSwipeUpHandlerOnRecentsAnimationFinished() {
+ ActiveGestureLog.INSTANCE.addLog(
+ /* event= */ "RecentsAnimationCallbacks.onAnimationFinished",
+ ON_FINISH_RECENTS_ANIMATION);
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "AbsSwipeUpHandler.onAnimationFinished");
+ }
+
+ public static void logAbsSwipeUpHandlerCancelCurrentAnimation() {
+ ActiveGestureLog.INSTANCE.addLog(
+ "AbsSwipeUpHandler.cancelCurrentAnimation",
+ ActiveGestureErrorDetector.GestureEvent.CANCEL_CURRENT_ANIMATION);
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "AbsSwipeUpHandler.cancelCurrentAnimation");
+ }
+
+ public static void logAbsSwipeUpHandlerOnTasksAppeared() {
+ ActiveGestureLog.INSTANCE.addLog("AbsSwipeUpHandler.onTasksAppeared: "
+ + "force finish recents animation complete; clearing state callback.");
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "AbsSwipeUpHandler.onTasksAppeared: "
+ + "force finish recents animation complete; clearing state callback.");
+ }
+
+ public static void logFinishRecentsAnimationOnTasksAppeared() {
+ ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimationOnTasksAppeared");
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "finishRecentsAnimationOnTasksAppeared");
+ }
+
+ public static void logRecentsAnimationCallbacksOnAnimationCancelled() {
+ ActiveGestureLog.INSTANCE.addLog(
+ /* event= */ "RecentsAnimationCallbacks.onAnimationCanceled",
+ /* gestureEvent= */ ON_CANCEL_RECENTS_ANIMATION);
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "RecentsAnimationCallbacks.onAnimationCanceled");
+ }
+
+ public static void logRecentsAnimationCallbacksOnTasksAppeared() {
+ ActiveGestureLog.INSTANCE.addLog("RecentsAnimationCallbacks.onTasksAppeared",
+ ActiveGestureErrorDetector.GestureEvent.TASK_APPEARED);
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "RecentsAnimationCallbacks.onTasksAppeared");
+ }
+
+ public static void logStartRecentsAnimation() {
+ ActiveGestureLog.INSTANCE.addLog(
+ /* event= */ "TaskAnimationManager.startRecentsAnimation",
+ /* gestureEvent= */ START_RECENTS_ANIMATION);
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "TaskAnimationManager.startRecentsAnimation");
+ }
+
+ public static void logLaunchingSideTaskFailed() {
+ ActiveGestureLog.INSTANCE.addLog("Unable to launch side task (no recents)");
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "Unable to launch side task (no recents)");
+ }
+
+ public static void logContinueRecentsAnimation() {
+ ActiveGestureLog.INSTANCE.addLog(/* event= */ "continueRecentsAnimation");
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "continueRecentsAnimation");
+ }
+
+ public static void logCleanUpRecentsAnimationSkipped() {
+ ActiveGestureLog.INSTANCE.addLog(
+ /* event= */ "cleanUpRecentsAnimation skipped due to wrong callbacks");
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "cleanUpRecentsAnimation skipped due to wrong callbacks");
+ }
+
+ public static void logCleanUpRecentsAnimation() {
+ ActiveGestureLog.INSTANCE.addLog(/* event= */ "cleanUpRecentsAnimation");
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "cleanUpRecentsAnimation");
+ }
+
+ public static void logOnInputEventUserLocked() {
+ ActiveGestureLog.INSTANCE.addLog(
+ "TIS.onInputEvent: Cannot process input event: user is locked");
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG,
+ "TIS.onInputEvent: Cannot process input event: user is locked");
+ }
+
+ public static void logOnInputIgnoringFollowingEvents() {
+ ActiveGestureLog.INSTANCE.addLog("TIS.onMotionEvent: A new gesture has been started, "
+ + "but a previously-requested recents animation hasn't started. "
+ + "Ignoring all following motion events.",
+ RECENTS_ANIMATION_START_PENDING);
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "TIS.onMotionEvent: A new gesture has been started, "
+ + "but a previously-requested recents animation hasn't started. "
+ + "Ignoring all following motion events.");
+ }
+
+ public static void logOnInputEventThreeButtonNav() {
+ ActiveGestureLog.INSTANCE.addLog("TIS.onInputEvent: Cannot process input event: "
+ + "using 3-button nav and event is not a trackpad event");
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "TIS.onInputEvent: Cannot process input event: "
+ + "using 3-button nav and event is not a trackpad event");
+ }
+
+ public static void logPreloadRecentsAnimation() {
+ ActiveGestureLog.INSTANCE.addLog("preloadRecentsAnimation");
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "preloadRecentsAnimation");
+ }
+
+ public static void logRecentTasksMissing() {
+ ActiveGestureLog.INSTANCE.addLog("Null mRecentTasks", RECENT_TASKS_MISSING);
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "Null mRecentTasks");
+ }
+
+ public static void logExecuteHomeCommand() {
+ ActiveGestureLog.INSTANCE.addLog("OverviewCommandHelper.executeCommand(HOME)");
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "OverviewCommandHelper.executeCommand(HOME)");
+ }
+
+ public static void logFinishRecentsAnimationCallback() {
+ ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation-callback");
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "finishRecentsAnimation-callback");
+ }
+
+ public static void logOnScrollerAnimationAborted() {
+ ActiveGestureLog.INSTANCE.addLog("scroller animation aborted",
+ ActiveGestureErrorDetector.GestureEvent.SCROLLER_ANIMATION_ABORTED);
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "scroller animation aborted");
+ }
+
+ public static void logInputConsumerBecameActive(@NonNull String consumerName) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "%s became active", consumerName));
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "%s became active", consumerName);
+ }
+
+ public static void logTaskLaunchFailed(int launchedTaskId) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "Launch failed, task (id=%d) finished mid transition", launchedTaskId));
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG,
+ "Launch failed, task (id=%d) finished mid transition", launchedTaskId);
+ }
+
+ public static void logOnPageEndTransition(int nextPageIndex) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "onPageEndTransition: current page index updated: %d", nextPageIndex));
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG,
+ "onPageEndTransition: current page index updated: %d", nextPageIndex);
+ }
+
+ public static void logQuickSwitchFromHomeFallback(int taskIndex) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "Quick switch from home fallback case: The TaskView at index %d is missing.",
+ taskIndex),
+ QUICK_SWITCH_FROM_HOME_FALLBACK);
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG,
+ "Quick switch from home fallback case: The TaskView at index %d is missing.",
+ taskIndex);
+ }
+
+ public static void logQuickSwitchFromHomeFailed(int taskIndex) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "Quick switch from home failed: TaskViews at indices %d and 0 are missing.",
+ taskIndex),
+ QUICK_SWITCH_FROM_HOME_FAILED);
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG,
+ "Quick switch from home failed: TaskViews at indices %d and 0 are missing.",
+ taskIndex);
+ }
+
+ public static void logFinishRecentsAnimation(boolean toRecents) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "finishRecentsAnimation: %b", toRecents),
+ /* gestureEvent= */ FINISH_RECENTS_ANIMATION);
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "finishRecentsAnimation: %b", toRecents);
+ }
+
+ public static void logSetEndTarget(@NonNull String target) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "setEndTarget %s", target), /* gestureEvent= */ SET_END_TARGET);
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "setEndTarget %s", target);
+ }
+
+ public static void logStartHomeIntent(@NonNull String reason) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "OverviewComponentObserver.startHomeIntent: %s", reason));
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "OverviewComponentObserver.startHomeIntent: %s", reason);
+ }
+
+ public static void logRunningTaskPackage(@NonNull String packageName) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "Current running task package name=%s", packageName));
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "Current running task package name=%s", packageName);
+ }
+
+ public static void logSysuiStateFlags(@NonNull String stateFlags) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "Current SystemUi state flags=%s", stateFlags));
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "Current SystemUi state flags=%s", stateFlags);
+ }
+
+ public static void logSetInputConsumer(@NonNull String consumerName, @NonNull String reason) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "setInputConsumer: %s. reason(s):%s", consumerName, reason));
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG,
+ "setInputConsumer: %s. reason(s):%s", consumerName, reason);
+ }
+
+ public static void logUpdateGestureStateRunningTask(
+ @NonNull String otherTaskPackage, @NonNull String runningTaskPackage) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "Changing active task to %s because the previous task running on top of this "
+ + "one (%s) was excluded from recents",
+ otherTaskPackage,
+ runningTaskPackage));
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG,
+ "Changing active task to %s because the previous task running on top of this "
+ + "one (%s) was excluded from recents",
+ otherTaskPackage,
+ runningTaskPackage);
+ }
+
+ public static void logOnInputEventActionUp(
+ int x, int y, int action, @NonNull String classification) {
+ String actionString = MotionEvent.actionToString(action);
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "onMotionEvent(%d, %d): %s, %s", x, y, actionString, classification),
+ /* gestureEvent= */ action == ACTION_DOWN
+ ? MOTION_DOWN
+ : MOTION_UP);
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG,
+ "onMotionEvent(%d, %d): %s, %s", x, y, actionString, classification);
+ }
+
+ public static void logOnInputEventActionMove(
+ @NonNull String action, @NonNull String classification, int pointerCount) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "onMotionEvent: %s, %s, pointerCount: %d",
+ action,
+ classification,
+ pointerCount),
+ MOTION_MOVE);
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG,
+ "onMotionEvent: %s, %s, pointerCount: %d", action, classification, pointerCount);
+ }
+
+ public static void logOnInputEventGenericAction(
+ @NonNull String action, @NonNull String classification) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "onMotionEvent: %s, %s", action, classification));
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "onMotionEvent: %s, %s", action, classification);
+ }
+
+ public static void logOnInputEventNavModeSwitched(
+ @NonNull String startNavMode, @NonNull String currentNavMode) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "TIS.onInputEvent: Navigation mode switched mid-gesture (%s -> %s); "
+ + "cancelling gesture.",
+ startNavMode,
+ currentNavMode),
+ NAVIGATION_MODE_SWITCHED);
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG,
+ "TIS.onInputEvent: Navigation mode switched mid-gesture (%s -> %s); "
+ + "cancelling gesture.",
+ startNavMode,
+ currentNavMode);
+ }
+
+ public static void logUnknownInputEvent(@NonNull String event) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "TIS.onInputEvent: Cannot process input event: received unknown event %s", event));
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG,
+ "TIS.onInputEvent: Cannot process input event: received unknown event %s", event);
+ }
+
+ public static void logFinishRunningRecentsAnimation(boolean toHome) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "finishRunningRecentsAnimation: %b", toHome));
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "finishRunningRecentsAnimation: %b", toHome);
+ }
+
+ public static void logOnRecentsAnimationStartCancelled() {
+ ActiveGestureLog.INSTANCE.addLog("RecentsAnimationCallbacks.onAnimationStart (canceled): 0",
+ /* gestureEvent= */ ON_START_RECENTS_ANIMATION);
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "RecentsAnimationCallbacks.onAnimationStart (canceled): 0");
+ }
+
+ public static void logOnRecentsAnimationStart(int appCount) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "RecentsAnimationCallbacks.onAnimationStart (canceled): %d", appCount),
+ /* gestureEvent= */ ON_START_RECENTS_ANIMATION);
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG,
+ "RecentsAnimationCallbacks.onAnimationStart (canceled): %d", appCount);
+ }
+
+ public static void logStartRecentsAnimationCallback(@NonNull String callback) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "TaskAnimationManager.startRecentsAnimation(%s): "
+ + "Setting mRecentsAnimationStartPending = false",
+ callback));
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG,
+ "TaskAnimationManager.startRecentsAnimation(%s): "
+ + "Setting mRecentsAnimationStartPending = false",
+ callback);
+ }
+
+ public static void logSettingRecentsAnimationStartPending(boolean value) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "TaskAnimationManager.startRecentsAnimation: "
+ + "Setting mRecentsAnimationStartPending = %b",
+ value));
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG,
+ "TaskAnimationManager.startRecentsAnimation: "
+ + "Setting mRecentsAnimationStartPending = %b",
+ value);
+ }
+
+ public static void logLaunchingSideTask(int taskId) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "Launching side task id=%d", taskId));
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "Launching side task id=%d", taskId);
+ }
+
+ public static void logOnInputEventActionDown(@NonNull ActiveGestureLog.CompoundString reason) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "TIS.onMotionEvent: ").append(reason));
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "TIS.onMotionEvent: %s", reason.toString());
+ }
+
+ public static void logStartNewTask(@NonNull ActiveGestureLog.CompoundString tasks) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "Launching task: ").append(tasks));
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "TIS.onMotionEvent: %s", tasks.toString());
+ }
+
+ public static void logMotionPauseDetectorEvent(@NonNull ActiveGestureLog.CompoundString event) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "MotionPauseDetector: ").append(event));
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "MotionPauseDetector: %s", event.toString());
+ }
+
+ public static void logHandleTaskAppearedFailed(
+ @NonNull ActiveGestureLog.CompoundString reason) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "handleTaskAppeared check failed: ").append(reason));
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "handleTaskAppeared check failed: %s", reason.toString());
+ }
+
+ /**
+ * This is for special cases where the string is purely dynamic and therefore has no format that
+ * can be extracted. Do not use in any other case.
+ */
+ public static void logDynamicString(
+ @NonNull String string,
+ @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
+ ActiveGestureLog.INSTANCE.addLog(string, gestureEvent);
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "%s", string);
+ }
+
+ public static void logOnSettledOnEndTarget(@NonNull String endTarget) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "onSettledOnEndTarget %s", endTarget),
+ /* gestureEvent= */ ON_SETTLED_ON_END_TARGET);
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "onSettledOnEndTarget %s", endTarget);
+ }
+
+ public static void logOnCalculateEndTarget(float velocityX, float velocityY, double angle) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "calculateEndTarget: velocities=(x=%fdp/ms, y=%fdp/ms), angle=%f",
+ velocityX,
+ velocityY,
+ angle),
+ velocityX == 0 && velocityY == 0 ? INVALID_VELOCITY_ON_SWIPE_UP : null);
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG,
+ "calculateEndTarget: velocities=(x=%fdp/ms, y=%fdp/ms), angle=%f",
+ velocityX,
+ velocityY,
+ angle);
+ }
+
+ public static void logUnexpectedTaskAppeared(int taskId, @NonNull String packageName) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "Forcefully finishing recents animation: Unexpected task appeared id=%d, pkg=%s",
+ taskId,
+ packageName));
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG,
+ "Forcefully finishing recents animation: Unexpected task appeared id=%d, pkg=%s",
+ taskId,
+ packageName);
+ }
+}
diff --git a/quickstep/src_protolog/com/android/quickstep/util/QuickstepProtoLogGroup.java b/quickstep/src_protolog/com/android/quickstep/util/QuickstepProtoLogGroup.java
new file mode 100644
index 0000000..d0863f8
--- /dev/null
+++ b/quickstep/src_protolog/com/android/quickstep/util/QuickstepProtoLogGroup.java
@@ -0,0 +1,102 @@
+/*
+ * 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.util;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.protolog.ProtoLog;
+import com.android.internal.protolog.common.IProtoLogGroup;
+
+import java.util.UUID;
+
+/** Enums used to interface with the ProtoLog API. */
+public enum QuickstepProtoLogGroup implements IProtoLogGroup {
+
+ ACTIVE_GESTURE_LOG(true, true, false, "ActiveGestureLog");
+
+ private final boolean mEnabled;
+ private volatile boolean mLogToProto;
+ private volatile boolean mLogToLogcat;
+ private final @NonNull String mTag;
+
+ public static void initProtoLog() {
+ ProtoLog.init(QuickstepProtoLogGroup.values());
+ }
+
+ /**
+ * @param enabled set to false to exclude all log statements for this group from
+ * compilation,
+ * they will not be available in runtime.
+ * @param logToProto enable binary logging for the group
+ * @param logToLogcat enable text logging for the group
+ * @param tag name of the source of the logged message
+ */
+ QuickstepProtoLogGroup(
+ boolean enabled, boolean logToProto, boolean logToLogcat, @NonNull String tag) {
+ this.mEnabled = enabled;
+ this.mLogToProto = logToProto;
+ this.mLogToLogcat = logToLogcat;
+ this.mTag = tag;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+
+ @Override
+ public boolean isLogToProto() {
+ return mLogToProto;
+ }
+
+ @Override
+ public boolean isLogToLogcat() {
+ return mLogToLogcat;
+ }
+
+ @Override
+ public boolean isLogToAny() {
+ return mLogToLogcat || mLogToProto;
+ }
+
+ @Override
+ public int getId() {
+ return Constants.LOG_START_ID + this.ordinal();
+ }
+
+ @Override
+ public @NonNull String getTag() {
+ return mTag;
+ }
+
+ @Override
+ public void setLogToProto(boolean logToProto) {
+ this.mLogToProto = logToProto;
+ }
+
+ @Override
+ public void setLogToLogcat(boolean logToLogcat) {
+ this.mLogToLogcat = logToLogcat;
+ }
+
+ private static final class Constants {
+
+ private static final int LOG_START_ID =
+ (int) (UUID.nameUUIDFromBytes(QuickstepProtoLogGroup.class.getName().getBytes())
+ .getMostSignificantBits() % Integer.MAX_VALUE);
+ }
+}
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewScreenshotTest.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewScreenshotTest.kt
index e4b8069..b5a418b 100644
--- a/quickstep/tests/multivalentScreenshotTests/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewScreenshotTest.kt
+++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewScreenshotTest.kt
@@ -97,6 +97,8 @@
fun bubbleBarView_expanded_threeBubbles() {
// if we're still expanding, wait with taking a screenshot
val shouldWait: (ComponentActivity, View) -> Boolean = { _, _ -> bubbleBarView.isExpanding }
+ // increase the frame limit to allow the animation to end before taking the screenshot
+ screenshotRule.frameLimit = 500
screenshotRule.screenshotTest(
"bubbleBarView_expanded_threeBubbles",
checkView = shouldWait,
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/launcher3/taskbar/bubbles/OWNERS b/quickstep/tests/multivalentScreenshotTests/src/com/android/launcher3/taskbar/bubbles/OWNERS
new file mode 100644
index 0000000..63c1498
--- /dev/null
+++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/launcher3/taskbar/bubbles/OWNERS
@@ -0,0 +1,4 @@
+atsjenk@google.com
+liranb@google.com
+madym@google.com
+mpodolian@google.com
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutViewScreenshotTest.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutViewScreenshotTest.kt
index 537a755..6aba6a3 100644
--- a/quickstep/tests/multivalentScreenshotTests/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutViewScreenshotTest.kt
+++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutViewScreenshotTest.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.graphics.Color
+import android.graphics.PointF
import android.graphics.drawable.ColorDrawable
import androidx.test.core.app.ApplicationProvider
import com.google.android.apps.nexuslauncher.imagecomparison.goldenpathmanager.ViewScreenshotGoldenPathManager
@@ -59,15 +60,17 @@
fun bubbleBarFlyoutView_noAvatar_onRight() {
screenshotRule.screenshotTest("bubbleBarFlyoutView_noAvatar_onRight") { activity ->
activity.actionBar?.hide()
- val flyout = BubbleBarFlyoutView(context, onLeft = false)
- flyout.setData(
+ val flyout =
+ BubbleBarFlyoutView(context, FakeBubbleBarFlyoutPositioner(isOnLeft = false))
+ flyout.showFromCollapsed(
BubbleBarFlyoutMessage(
senderAvatar = null,
senderName = "sender",
message = "message",
isGroupChat = false,
)
- )
+ ) {}
+ flyout.updateExpansionProgress(1f)
flyout
}
}
@@ -76,15 +79,17 @@
fun bubbleBarFlyoutView_noAvatar_onLeft() {
screenshotRule.screenshotTest("bubbleBarFlyoutView_noAvatar_onLeft") { activity ->
activity.actionBar?.hide()
- val flyout = BubbleBarFlyoutView(context, onLeft = true)
- flyout.setData(
+ val flyout =
+ BubbleBarFlyoutView(context, FakeBubbleBarFlyoutPositioner(isOnLeft = true))
+ flyout.showFromCollapsed(
BubbleBarFlyoutMessage(
senderAvatar = null,
senderName = "sender",
message = "message",
isGroupChat = false,
)
- )
+ ) {}
+ flyout.updateExpansionProgress(1f)
flyout
}
}
@@ -93,15 +98,17 @@
fun bubbleBarFlyoutView_noAvatar_longMessage() {
screenshotRule.screenshotTest("bubbleBarFlyoutView_noAvatar_longMessage") { activity ->
activity.actionBar?.hide()
- val flyout = BubbleBarFlyoutView(context, onLeft = true)
- flyout.setData(
+ val flyout =
+ BubbleBarFlyoutView(context, FakeBubbleBarFlyoutPositioner(isOnLeft = true))
+ flyout.showFromCollapsed(
BubbleBarFlyoutMessage(
senderAvatar = null,
senderName = "sender",
message = "really, really, really, really, really long message. like really.",
isGroupChat = false,
)
- )
+ ) {}
+ flyout.updateExpansionProgress(1f)
flyout
}
}
@@ -110,15 +117,17 @@
fun bubbleBarFlyoutView_avatar_onRight() {
screenshotRule.screenshotTest("bubbleBarFlyoutView_avatar_onRight") { activity ->
activity.actionBar?.hide()
- val flyout = BubbleBarFlyoutView(context, onLeft = false)
- flyout.setData(
+ val flyout =
+ BubbleBarFlyoutView(context, FakeBubbleBarFlyoutPositioner(isOnLeft = false))
+ flyout.showFromCollapsed(
BubbleBarFlyoutMessage(
senderAvatar = ColorDrawable(Color.RED),
senderName = "sender",
message = "message",
isGroupChat = true,
)
- )
+ ) {}
+ flyout.updateExpansionProgress(1f)
flyout
}
}
@@ -127,15 +136,17 @@
fun bubbleBarFlyoutView_avatar_onLeft() {
screenshotRule.screenshotTest("bubbleBarFlyoutView_avatar_onLeft") { activity ->
activity.actionBar?.hide()
- val flyout = BubbleBarFlyoutView(context, onLeft = true)
- flyout.setData(
+ val flyout =
+ BubbleBarFlyoutView(context, FakeBubbleBarFlyoutPositioner(isOnLeft = true))
+ flyout.showFromCollapsed(
BubbleBarFlyoutMessage(
senderAvatar = ColorDrawable(Color.RED),
senderName = "sender",
message = "message",
isGroupChat = true,
)
- )
+ ) {}
+ flyout.updateExpansionProgress(1f)
flyout
}
}
@@ -144,16 +155,117 @@
fun bubbleBarFlyoutView_avatar_longMessage() {
screenshotRule.screenshotTest("bubbleBarFlyoutView_avatar_longMessage") { activity ->
activity.actionBar?.hide()
- val flyout = BubbleBarFlyoutView(context, onLeft = true)
- flyout.setData(
+ val flyout =
+ BubbleBarFlyoutView(context, FakeBubbleBarFlyoutPositioner(isOnLeft = true))
+ flyout.showFromCollapsed(
BubbleBarFlyoutMessage(
senderAvatar = ColorDrawable(Color.RED),
senderName = "sender",
message = "really, really, really, really, really long message. like really.",
isGroupChat = true,
)
- )
+ ) {}
+ flyout.updateExpansionProgress(1f)
flyout
}
}
+
+ @Test
+ fun bubbleBarFlyoutView_collapsed_onLeft() {
+ screenshotRule.screenshotTest("bubbleBarFlyoutView_collapsed_onLeft") { activity ->
+ activity.actionBar?.hide()
+ val flyout =
+ BubbleBarFlyoutView(context, FakeBubbleBarFlyoutPositioner(isOnLeft = true))
+ flyout.showFromCollapsed(
+ BubbleBarFlyoutMessage(
+ senderAvatar = ColorDrawable(Color.RED),
+ senderName = "sender",
+ message = "collapsed on left",
+ isGroupChat = true,
+ )
+ ) {}
+ flyout.updateExpansionProgress(0f)
+ flyout
+ }
+ }
+
+ @Test
+ fun bubbleBarFlyoutView_collapsed_onRight() {
+ screenshotRule.screenshotTest("bubbleBarFlyoutView_collapsed_onRight") { activity ->
+ activity.actionBar?.hide()
+ val flyout =
+ BubbleBarFlyoutView(context, FakeBubbleBarFlyoutPositioner(isOnLeft = false))
+ flyout.showFromCollapsed(
+ BubbleBarFlyoutMessage(
+ senderAvatar = ColorDrawable(Color.RED),
+ senderName = "sender",
+ message = "collapsed on right",
+ isGroupChat = true,
+ )
+ ) {}
+ flyout.updateExpansionProgress(0f)
+ flyout
+ }
+ }
+
+ @Test
+ fun bubbleBarFlyoutView_90p_onLeft() {
+ screenshotRule.screenshotTest("bubbleBarFlyoutView_90p_onLeft") { activity ->
+ activity.actionBar?.hide()
+ val flyout =
+ BubbleBarFlyoutView(
+ context,
+ FakeBubbleBarFlyoutPositioner(
+ isOnLeft = true,
+ distanceToCollapsedPosition = PointF(100f, 100f),
+ ),
+ )
+ flyout.showFromCollapsed(
+ BubbleBarFlyoutMessage(
+ senderAvatar = ColorDrawable(Color.RED),
+ senderName = "sender",
+ message = "expanded 90% on left",
+ isGroupChat = true,
+ )
+ ) {}
+ flyout.updateExpansionProgress(0.9f)
+ flyout
+ }
+ }
+
+ @Test
+ fun bubbleBarFlyoutView_80p_onRight() {
+ screenshotRule.screenshotTest("bubbleBarFlyoutView_80p_onRight") { activity ->
+ activity.actionBar?.hide()
+ val flyout =
+ BubbleBarFlyoutView(
+ context,
+ FakeBubbleBarFlyoutPositioner(
+ isOnLeft = false,
+ distanceToCollapsedPosition = PointF(200f, 100f),
+ ),
+ )
+ flyout.showFromCollapsed(
+ BubbleBarFlyoutMessage(
+ senderAvatar = ColorDrawable(Color.RED),
+ senderName = "sender",
+ message = "expanded 80% on right",
+ isGroupChat = true,
+ )
+ ) {}
+ flyout.updateExpansionProgress(0.8f)
+ flyout
+ }
+ }
+
+ private class FakeBubbleBarFlyoutPositioner(
+ override val isOnLeft: Boolean,
+ override val distanceToCollapsedPosition: PointF = PointF(0f, 0f),
+ ) : BubbleBarFlyoutPositioner {
+ override val targetTy = 0f
+ override val collapsedSize = 30f
+ override val collapsedColor = Color.BLUE
+ override val collapsedElevation = 1f
+ override val distanceToRevealTriangle = 10f
+ }
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendControllerTest.kt
new file mode 100644
index 0000000..f3fff9f
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendControllerTest.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar
+
+import android.animation.AnimatorTestRule
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+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.TaskbarModeRule
+import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.TRANSIENT
+import com.android.launcher3.taskbar.rules.TaskbarModeRule.TaskbarMode
+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.LauncherMultivalentJUnit
+import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
+import com.android.quickstep.SystemUiProxy
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestRule
+import org.junit.runner.RunWith
+import org.junit.runners.model.Statement
+
+@RunWith(LauncherMultivalentJUnit::class)
+@EmulatedDevices(["pixelTablet2023"])
+class TaskbarAutohideSuspendControllerTest {
+
+ private val context = TaskbarWindowSandboxContext.create(getInstrumentation().targetContext)
+
+ @get:Rule(order = 0) val animatorTestRule = AnimatorTestRule(this)
+ @get:Rule(order = 1)
+ val systemUiProxyRule = TestRule { base, _ ->
+ object : Statement() {
+ override fun evaluate() {
+ getInstrumentation().runOnMainSync {
+ context.applicationContext.putObject(
+ SystemUiProxy.INSTANCE,
+ object : SystemUiProxy(context) {
+ override fun notifyTaskbarAutohideSuspend(suspend: Boolean) {
+ latestSuspendNotification = suspend
+ }
+ },
+ )
+ }
+ base.evaluate()
+ }
+ }
+ }
+ @get:Rule(order = 2) val taskbarModeRule = TaskbarModeRule(context)
+ @get:Rule(order = 3) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
+
+ @InjectController lateinit var autohideSuspendController: TaskbarAutohideSuspendController
+ @InjectController lateinit var stashController: TaskbarStashController
+
+ private var latestSuspendNotification: Boolean? = null
+
+ @Test
+ fun testUpdateFlag_suspendInLauncher_notifiesSuspend() {
+ getInstrumentation().runOnMainSync {
+ autohideSuspendController.updateFlag(FLAG_AUTOHIDE_SUSPEND_IN_LAUNCHER, true)
+ }
+ assertThat(latestSuspendNotification).isTrue()
+ }
+
+ @Test
+ fun testUpdateFlag_toggleSuspendDraggingTwice_notifiesUnsuspend() {
+ getInstrumentation().runOnMainSync {
+ autohideSuspendController.updateFlag(FLAG_AUTOHIDE_SUSPEND_DRAGGING, true)
+ autohideSuspendController.updateFlag(FLAG_AUTOHIDE_SUSPEND_DRAGGING, false)
+ }
+ assertThat(latestSuspendNotification).isFalse()
+ }
+
+ @Test
+ fun testUpdateFlag_resetsAlreadyUnsetFlag_noNotifyUnsuspend() {
+ getInstrumentation().runOnMainSync {
+ autohideSuspendController.updateFlag(FLAG_AUTOHIDE_SUSPEND_DRAGGING, false)
+ }
+ assertThat(latestSuspendNotification).isNull()
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testUpdateFlag_suspendTransientTaskbarForTouch_cancelsAutoStashTimeout() {
+ // Unstash and verify alarm.
+ getInstrumentation().runOnMainSync {
+ stashController.updateAndAnimateTransientTaskbar(false)
+ animatorTestRule.advanceTimeBy(stashController.stashDuration)
+ }
+ assertThat(stashController.timeoutAlarm.alarmPending()).isTrue()
+
+ // EDU opens while unstashed.
+ getInstrumentation().runOnMainSync {
+ autohideSuspendController.updateFlag(FLAG_AUTOHIDE_SUSPEND_TOUCHING, true)
+ }
+ assertThat(stashController.timeoutAlarm.alarmPending()).isFalse()
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarScrimViewControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarScrimViewControllerTest.kt
new file mode 100644
index 0000000..3524961
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarScrimViewControllerTest.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar
+
+import android.animation.AnimatorTestRule
+import android.view.View.GONE
+import android.view.View.VISIBLE
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+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.TaskbarUnitTestRule
+import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController
+import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
+import com.android.quickstep.SystemUiProxy
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE
+import com.android.wm.shell.shared.bubbles.BubbleConstants.BUBBLE_EXPANDED_SCRIM_ALPHA
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(LauncherMultivalentJUnit::class)
+@EmulatedDevices(["pixelTablet2023"])
+class TaskbarScrimViewControllerTest {
+ private val context = TaskbarWindowSandboxContext.create(getInstrumentation().targetContext)
+
+ @get:Rule(order = 0) val taskbarModeRule = TaskbarModeRule(context)
+ @get:Rule(order = 1) val animatorTestRule = AnimatorTestRule(this)
+ @get:Rule(order = 2) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
+
+ @InjectController lateinit var scrimViewController: TaskbarScrimViewController
+
+ // Default animation duration.
+ private val animationDuration =
+ context.resources.getInteger(android.R.integer.config_mediumAnimTime).toLong()
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testOnTaskbarVisibleChanged_onlyTaskbarVisible_noScrim() {
+ getInstrumentation().runOnMainSync {
+ scrimViewController.onTaskbarVisibilityChanged(VISIBLE)
+ scrimViewController.updateStateForSysuiFlags(0, true)
+ }
+ assertThat(scrimViewController.scrimAlpha).isEqualTo(0)
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testOnTaskbarVisibilityChanged_pinnedTaskbarVisibleWithBubblesExpanded_showsScrim() {
+ getInstrumentation().runOnMainSync {
+ scrimViewController.updateStateForSysuiFlags(SYSUI_STATE_BUBBLES_EXPANDED, true)
+ scrimViewController.onTaskbarVisibilityChanged(VISIBLE)
+ animatorTestRule.advanceTimeBy(animationDuration)
+ }
+
+ assertThat(scrimViewController.scrimAlpha).isEqualTo(BUBBLE_EXPANDED_SCRIM_ALPHA)
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testOnTaskbarVisibilityChanged_pinnedTaskbarHiddenDuringScrim_hidesScrim() {
+ getInstrumentation().runOnMainSync {
+ scrimViewController.onTaskbarVisibilityChanged(VISIBLE)
+ scrimViewController.updateStateForSysuiFlags(SYSUI_STATE_BUBBLES_EXPANDED, true)
+ }
+ assertThat(scrimViewController.scrimAlpha).isEqualTo(BUBBLE_EXPANDED_SCRIM_ALPHA)
+
+ getInstrumentation().runOnMainSync {
+ scrimViewController.onTaskbarVisibilityChanged(GONE)
+ animatorTestRule.advanceTimeBy(animationDuration)
+ }
+ assertThat(scrimViewController.scrimAlpha).isEqualTo(0)
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testOnTaskbarVisibilityChanged_notificationsOverPinnedTaskbarAndBubbles_noScrim() {
+ getInstrumentation().runOnMainSync {
+ scrimViewController.updateStateForSysuiFlags(
+ SYSUI_STATE_BUBBLES_EXPANDED or SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE,
+ true,
+ )
+ scrimViewController.onTaskbarVisibilityChanged(VISIBLE)
+ }
+ assertThat(scrimViewController.scrimAlpha).isEqualTo(0)
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testOnTaskbarVisibilityChanged_pinnedTaskbarWithBubbleMenu_darkerScrim() {
+ getInstrumentation().runOnMainSync {
+ scrimViewController.onTaskbarVisibilityChanged(VISIBLE)
+ scrimViewController.updateStateForSysuiFlags(
+ SYSUI_STATE_BUBBLES_EXPANDED or SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED,
+ true,
+ )
+ }
+ assertThat(scrimViewController.scrimAlpha).isGreaterThan(BUBBLE_EXPANDED_SCRIM_ALPHA)
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testOnTaskbarVisibilityChanged_stashedTaskbarWithBubbles_noScrim() {
+ getInstrumentation().runOnMainSync {
+ scrimViewController.updateStateForSysuiFlags(SYSUI_STATE_BUBBLES_EXPANDED, true)
+ scrimViewController.onTaskbarVisibilityChanged(VISIBLE)
+ }
+ assertThat(scrimViewController.scrimAlpha).isEqualTo(0)
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testOnClick_scrimShown_performsSystemBack() {
+ var backPressed = false
+ context.applicationContext.putObject(
+ SystemUiProxy.INSTANCE,
+ object : SystemUiProxy(context) {
+ override fun onBackPressed() {
+ backPressed = true
+ }
+ },
+ )
+
+ getInstrumentation().runOnMainSync {
+ scrimViewController.updateStateForSysuiFlags(SYSUI_STATE_BUBBLES_EXPANDED, true)
+ scrimViewController.onTaskbarVisibilityChanged(VISIBLE)
+ }
+ assertThat(scrimViewController.scrimView.isClickable).isTrue()
+
+ getInstrumentation().runOnMainSync { scrimViewController.scrimView.performClick() }
+ assertThat(backPressed).isTrue()
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testOnClick_scrimHidden_notClickable() {
+ getInstrumentation().runOnMainSync {
+ scrimViewController.updateStateForSysuiFlags(SYSUI_STATE_BUBBLES_EXPANDED, true)
+ scrimViewController.onTaskbarVisibilityChanged(VISIBLE)
+ }
+ assertThat(scrimViewController.scrimView.isClickable).isFalse()
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt
new file mode 100644
index 0000000..e736446
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt
@@ -0,0 +1,681 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar
+
+import android.animation.AnimatorTestRule
+import android.platform.test.annotations.EnableFlags
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.R
+import com.android.launcher3.taskbar.StashedHandleViewController.ALPHA_INDEX_STASHED
+import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_EDU_OPEN
+import com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP
+import com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_OVERVIEW
+import com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE
+import com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_DEVICE_LOCKED
+import com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_IME
+import com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_IN_APP_AUTO
+import com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_SMALL_SCREEN
+import com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_SYSUI
+import com.android.launcher3.taskbar.TaskbarStashController.TASKBAR_STASH_DURATION
+import com.android.launcher3.taskbar.TaskbarStashController.TASKBAR_STASH_DURATION_FOR_IME
+import com.android.launcher3.taskbar.TaskbarStashController.TRANSIENT_TASKBAR_STASH_ALPHA_DURATION
+import com.android.launcher3.taskbar.TaskbarStashController.TRANSIENT_TASKBAR_STASH_DURATION
+import com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_STASH
+import com.android.launcher3.taskbar.bubbles.BubbleControllers
+import com.android.launcher3.taskbar.rules.TaskbarModeRule
+import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.PINNED
+import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.THREE_BUTTONS
+import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.TRANSIENT
+import com.android.launcher3.taskbar.rules.TaskbarModeRule.TaskbarMode
+import com.android.launcher3.taskbar.rules.TaskbarPinningPreferenceRule
+import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule
+import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController
+import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.UserSetupMode
+import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING
+import com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_BAR
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(LauncherMultivalentJUnit::class)
+@EmulatedDevices(["pixelTablet2023"])
+class TaskbarStashControllerTest {
+ private val context = TaskbarWindowSandboxContext.create(getInstrumentation().targetContext)
+
+ @get:Rule(order = 0) val taskbarModeRule = TaskbarModeRule(context)
+ @get:Rule(order = 1) val taskbarPinningPreferenceRule = TaskbarPinningPreferenceRule(context)
+ @get:Rule(order = 2) val animatorTestRule = AnimatorTestRule(this)
+ @get:Rule(order = 3) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
+
+ @InjectController lateinit var stashController: TaskbarStashController
+ @InjectController lateinit var viewController: TaskbarViewController
+ @InjectController lateinit var stashedHandleViewController: StashedHandleViewController
+ @InjectController lateinit var dragLayerController: TaskbarDragLayerController
+ @InjectController lateinit var autohideSuspendController: TaskbarAutohideSuspendController
+ @InjectController lateinit var bubbleControllers: Optional<BubbleControllers>
+
+ private val activityContext by taskbarUnitTestRule::activityContext
+
+ // Disable hardware keyboard mode during tests.
+ @Before fun enableSoftwareIme() = TaskbarStashController.enableSoftwareImeForTests(true)
+
+ @After fun resetIme() = TaskbarStashController.enableSoftwareImeForTests(false)
+
+ @After fun cancelTimeoutIfExists() = stashController.cancelTimeoutIfExists()
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testInit_transientMode_stashedInApp() {
+ assertThat(stashController.isStashedInApp).isTrue()
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testInit_pinnedMode_unstashedInApp() {
+ assertThat(stashController.isStashedInApp).isFalse()
+ }
+
+ @Test
+ @UserSetupMode
+ @TaskbarMode(PINNED)
+ fun testInit_userSetupWithPinnedMode_stashedInApp() {
+ assertThat(stashController.isStashedInApp).isTrue()
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testSetSetupUiVisible_true_stashedInApp() {
+ getInstrumentation().runOnMainSync { stashController.setSetupUIVisible(true) }
+ assertThat(stashController.isStashedInApp).isTrue()
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testSetSetupUiVisible_false_unstashedInApp() {
+ getInstrumentation().runOnMainSync { stashController.setSetupUIVisible(false) }
+ assertThat(stashController.isStashedInApp).isFalse()
+ }
+
+ @Test
+ fun testRecreateAsTransient_timeoutStarted() {
+ taskbarPinningPreferenceRule.isPinned = true
+ activityContext.controllers.sharedState?.taskbarWasPinned = true
+
+ taskbarPinningPreferenceRule.isPinned = false
+ assertThat(stashController.timeoutAlarm.alarmPending()).isTrue()
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testSupportsVisualStashing_transientMode_supported() {
+ assertThat(stashController.supportsVisualStashing()).isTrue()
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testSupportsVisualStashing_pinnedMode_supported() {
+ assertThat(stashController.supportsVisualStashing()).isTrue()
+ }
+
+ @Test
+ @TaskbarMode(THREE_BUTTONS)
+ fun testSupportsVisualStashing_threeButtonsMode_unsupported() {
+ assertThat(stashController.supportsVisualStashing()).isFalse()
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testGetStashDuration_transientMode() {
+ assertThat(stashController.stashDuration).isEqualTo(TRANSIENT_TASKBAR_STASH_DURATION)
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testGetStashDuration_pinnedMode() {
+ assertThat(stashController.stashDuration).isEqualTo(TASKBAR_STASH_DURATION)
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testIsStashed_pinnedInApp_isUnstashed() {
+ getInstrumentation().runOnMainSync {
+ stashController.updateStateForFlag(FLAG_IN_APP, true)
+ stashController.applyState(0)
+ }
+ assertThat(stashController.isStashed).isFalse()
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testIsStashed_transientInApp_isStashed() {
+ getInstrumentation().runOnMainSync {
+ stashController.updateStateForFlag(FLAG_IN_APP, true)
+ stashController.applyState(0)
+ }
+ assertThat(stashController.isStashed).isTrue()
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testIsStashed_transientNotInApp_isUnstashed() {
+ getInstrumentation().runOnMainSync {
+ stashController.updateStateForFlag(FLAG_IN_APP, false)
+ stashController.applyState(0)
+ }
+ assertThat(stashController.isStashed).isFalse()
+ }
+
+ @Test
+ fun testIsStashed_stashedInLauncherState_isStashed() {
+ getInstrumentation().runOnMainSync {
+ stashController.updateStateForFlag(FLAG_IN_APP, false)
+ stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, true)
+ stashController.applyState(0)
+ }
+ assertThat(stashController.isStashed).isTrue()
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testIsStashed_transientInOverview_isUnstashed() {
+ getInstrumentation().runOnMainSync {
+ stashController.updateStateForFlag(FLAG_IN_APP, false)
+ stashController.updateStateForFlag(FLAG_IN_OVERVIEW, true)
+ stashController.applyState(0)
+ }
+ assertThat(stashController.isStashed).isFalse()
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testIsStashed_pinnedInOverviewWithIme_isStashed() {
+ getInstrumentation().runOnMainSync {
+ stashController.updateStateForFlag(FLAG_IN_APP, false)
+ stashController.updateStateForFlag(FLAG_IN_OVERVIEW, true)
+ stashController.updateStateForFlag(FLAG_STASHED_IME, true)
+ stashController.applyState(0)
+ }
+ assertThat(stashController.isStashed).isTrue()
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testIsStashed_pinnedTaskbarWithPinnedApp_isStashed() {
+ getInstrumentation().runOnMainSync {
+ stashController.updateStateForFlag(FLAG_IN_APP, true)
+ stashController.updateStateForFlag(FLAG_STASHED_SYSUI, true) // App pinned.
+ stashController.applyState(0)
+ }
+ assertThat(stashController.isStashed).isTrue()
+ }
+
+ @Test
+ fun testIsInStashedLauncherState_flagUnset_false() {
+ stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, false)
+ assertThat(stashController.isInStashedLauncherState).isFalse()
+ }
+
+ @Test
+ @TaskbarMode(THREE_BUTTONS)
+ fun testIsInStashedLauncherState_flagSetInThreeButtonsMode_false() {
+ stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, true)
+ assertThat(stashController.isInStashedLauncherState).isFalse()
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testIsInStashedLauncherState_flagSetInPinnedMode_true() {
+ stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, true)
+ assertThat(stashController.isInStashedLauncherState).isTrue()
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testIsTaskbarVisibleAndNotStashing_pinnedButNotVisible_false() {
+ getInstrumentation().runOnMainSync {
+ viewController.taskbarIconAlpha.get(ALPHA_INDEX_STASH).value = 0f
+ }
+ assertThat(stashController.isTaskbarVisibleAndNotStashing).isFalse()
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testIsTaskbarVisibleAndNotStashing_visibleButStashed_false() {
+ getInstrumentation().runOnMainSync {
+ viewController.taskbarIconAlpha.get(ALPHA_INDEX_STASH).value = 1f
+ }
+ assertThat(stashController.isTaskbarVisibleAndNotStashing).isFalse()
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testIsTaskbarVisibleAndNotStashing_pinnedAndVisible_true() {
+ getInstrumentation().runOnMainSync {
+ viewController.taskbarIconAlpha.get(ALPHA_INDEX_STASH).value = 1f
+ }
+ assertThat(stashController.isTaskbarVisibleAndNotStashing).isTrue()
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testGetTouchableHeight_isStashed_stashedHeight() {
+ assertThat(stashController.touchableHeight).isEqualTo(stashController.stashedHeight)
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testGetTouchableHeight_unstashedTransientMode_heightAndBottomMargin() {
+ getInstrumentation().runOnMainSync {
+ stashController.updateStateForFlag(FLAG_STASHED_IN_APP_AUTO, false)
+ stashController.applyState(0)
+ }
+
+ val expectedHeight =
+ activityContext.deviceProfile.run { taskbarHeight + taskbarBottomMargin }
+ assertThat(stashController.touchableHeight).isEqualTo(expectedHeight)
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testGetTouchableHeight_pinnedMode_taskbarHeight() {
+ assertThat(stashController.touchableHeight)
+ .isEqualTo(activityContext.deviceProfile.taskbarHeight)
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testGetContentHeightToReportToApps_transientMode_stashedHeight() {
+ assertThat(stashController.contentHeightToReportToApps)
+ .isEqualTo(stashController.stashedHeight)
+ }
+
+ @Test
+ @TaskbarMode(THREE_BUTTONS)
+ fun testGetContentHeightToReportToApps_threeButtonsMode_taskbarHeight() {
+ assertThat(stashController.contentHeightToReportToApps)
+ .isEqualTo(activityContext.deviceProfile.taskbarHeight)
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testGetContentHeightToReportToApps_pinnedMode_taskbarHeight() {
+ assertThat(stashController.contentHeightToReportToApps)
+ .isEqualTo(activityContext.deviceProfile.taskbarHeight)
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ @UserSetupMode
+ fun testGetContentHeightToReportToApps_pinnedInSetupMode_setupWizardInsets() {
+ assertThat(stashController.contentHeightToReportToApps)
+ .isEqualTo(context.resources.getDimensionPixelSize(R.dimen.taskbar_suw_insets))
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testGetContentHeightToReportToApps_pinnedModeButFolded_stashedHeight() {
+ getInstrumentation().runOnMainSync {
+ stashedHandleViewController.stashedHandleAlpha.get(ALPHA_INDEX_STASHED).value = 1f
+ stashController.updateStateForFlag(FLAG_STASHED_SMALL_SCREEN, true)
+ }
+ assertThat(stashController.contentHeightToReportToApps)
+ .isEqualTo(stashController.stashedHeight)
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testGetContentHeightToReportToApps_homeDisabledWhenFolded_zeroHeight() {
+ getInstrumentation().runOnMainSync {
+ stashedHandleViewController.stashedHandleAlpha.get(ALPHA_INDEX_STASHED).value = 1f
+ stashedHandleViewController.setIsHomeButtonDisabled(true)
+ stashController.updateStateForFlag(FLAG_STASHED_SMALL_SCREEN, true)
+ }
+ assertThat(stashController.contentHeightToReportToApps).isEqualTo(0)
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testGetTappableHeightToReportToApps_transientMode_zeroHeight() {
+ assertThat(stashController.tappableHeightToReportToApps).isEqualTo(0)
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testGetTappableHeightToReportToApps_pinnedMode_taskbarHeight() {
+ assertThat(stashController.tappableHeightToReportToApps)
+ .isEqualTo(activityContext.deviceProfile.taskbarHeight)
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testUpdateAndAnimateTransientTaskbar_unstashTaskbar_updatesState() {
+ getInstrumentation().runOnMainSync {
+ stashController.updateAndAnimateTransientTaskbar(false)
+ }
+ assertThat(stashController.isStashed).isFalse()
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testUpdateAndAnimateTransientTaskbar_runUnstashAnimation_startsTaskbarTimeout() {
+ getInstrumentation().runOnMainSync {
+ stashController.updateAndAnimateTransientTaskbar(false)
+ animatorTestRule.advanceTimeBy(stashController.stashDuration)
+ }
+ assertThat(stashController.timeoutAlarm.alarmPending()).isTrue()
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testUpdateAndAnimateTransientTaskbar_finishTaskbarTimeout_taskbarStashes() {
+ getInstrumentation().runOnMainSync {
+ stashController.updateAndAnimateTransientTaskbar(false)
+ animatorTestRule.advanceTimeBy(stashController.stashDuration)
+ }
+ assertThat(stashController.timeoutAlarm.alarmPending()).isTrue()
+
+ getInstrumentation().runOnMainSync {
+ stashController.timeoutAlarm.finishAlarm()
+ animatorTestRule.advanceTimeBy(stashController.stashDuration)
+ }
+ assertThat(stashController.isStashed).isTrue()
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testUpdateAndAnimateTransientTaskbar_autoHideSuspendedForEdu_remainsUnstashed() {
+ getInstrumentation().runOnMainSync {
+ stashController.updateAndAnimateTransientTaskbar(false)
+ animatorTestRule.advanceTimeBy(stashController.stashDuration)
+ }
+
+ getInstrumentation().runOnMainSync {
+ autohideSuspendController.updateFlag(FLAG_AUTOHIDE_SUSPEND_EDU_OPEN, true)
+ stashController.updateAndAnimateTransientTaskbar(true)
+ animatorTestRule.advanceTimeBy(stashController.stashDuration)
+ }
+ assertThat(stashController.isStashed).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+ @TaskbarMode(TRANSIENT)
+ fun testUpdateAndAnimateTransientTaskbar_unstashTaskbarWithBubbles_bubbleBarUnstashes() {
+ getInstrumentation().runOnMainSync {
+ bubbleControllers.get().bubbleBarViewController.setHiddenForBubbles(false)
+ bubbleControllers.get().bubbleStashController.stashBubbleBarImmediate()
+ stashController.updateAndAnimateTransientTaskbar(false, true)
+ }
+ assertThat(bubbleControllers.get().bubbleStashController.isStashed).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+ @TaskbarMode(TRANSIENT)
+ fun testUpdateAndAnimateTransientTaskbar_unstashTaskbarWithoutBubbles_bubbleBarStashed() {
+ getInstrumentation().runOnMainSync {
+ bubbleControllers.get().bubbleBarViewController.setHiddenForBubbles(false)
+ bubbleControllers.get().bubbleStashController.stashBubbleBarImmediate()
+ stashController.updateAndAnimateTransientTaskbar(false, false)
+ }
+ assertThat(bubbleControllers.get().bubbleStashController.isStashed).isTrue()
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+ @TaskbarMode(TRANSIENT)
+ fun testUpdateAndAnimateTransientTaskbar_stashTaskbarWithBubbles_bubbleBarStashes() {
+ getInstrumentation().runOnMainSync {
+ bubbleControllers.get().bubbleBarViewController.setHiddenForBubbles(false)
+ bubbleControllers.get().bubbleStashController.showBubbleBarImmediate()
+ stashController.updateAndAnimateTransientTaskbar(true, true)
+ }
+ assertThat(bubbleControllers.get().bubbleStashController.isStashed).isTrue()
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+ @TaskbarMode(TRANSIENT)
+ fun testUpdateAndAnimateTransientTaskbar_stashTaskbarWithoutBubbles_bubbleBarUnstashed() {
+ getInstrumentation().runOnMainSync {
+ bubbleControllers.get().bubbleBarViewController.setHiddenForBubbles(false)
+ bubbleControllers.get().bubbleStashController.showBubbleBarImmediate()
+ stashController.updateAndAnimateTransientTaskbar(true, false)
+ }
+ assertThat(bubbleControllers.get().bubbleStashController.isStashed).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+ @TaskbarMode(TRANSIENT)
+ fun testUpdateAndAnimateTransientTaskbar_bubbleBarExpandedBeforeTimeout_expandedAfterwards() {
+ getInstrumentation().runOnMainSync {
+ bubbleControllers.get().bubbleBarViewController.setHiddenForBubbles(false)
+ bubbleControllers.get().bubbleBarViewController.isExpanded = true
+ stashController.updateAndAnimateTransientTaskbar(false)
+ animatorTestRule.advanceTimeBy(stashController.stashDuration)
+ }
+ assertThat(stashController.timeoutAlarm.alarmPending()).isTrue()
+
+ getInstrumentation().runOnMainSync {
+ stashController.timeoutAlarm.finishAlarm()
+ animatorTestRule.advanceTimeBy(stashController.stashDuration)
+ }
+ assertThat(bubbleControllers.get().bubbleBarViewController.isExpanded).isTrue()
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testToggleTaskbarStash_pinnedMode_doesNothing() {
+ getInstrumentation().runOnMainSync { stashController.toggleTaskbarStash() }
+ assertThat(stashController.isStashed).isFalse()
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testToggleTaskbarStash_transientMode_unstashesTaskbar() {
+ getInstrumentation().runOnMainSync { stashController.toggleTaskbarStash() }
+ assertThat(stashController.isStashed).isFalse()
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testToggleTaskbarStash_twiceInTransientMode_stashesTaskbar() {
+ getInstrumentation().runOnMainSync {
+ stashController.toggleTaskbarStash()
+ stashController.toggleTaskbarStash()
+ }
+ assertThat(stashController.isStashed).isTrue()
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testToggleTaskbarStash_notInAppWithTransientMode_doesNothing() {
+ getInstrumentation().runOnMainSync {
+ stashController.updateStateForFlag(FLAG_IN_APP, false)
+ stashController.applyState(0)
+ stashController.toggleTaskbarStash()
+ }
+ assertThat(stashController.isStashed).isFalse()
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testAnimateTransientTaskbar_bubblesShownInOverview_stashesTaskbar() {
+ // Start in Overview. Should unstash Taskbar.
+ getInstrumentation().runOnMainSync {
+ stashController.updateStateForFlag(FLAG_STASHED_IN_APP_AUTO, false)
+ stashController.updateStateForFlag(FLAG_IN_APP, false)
+ stashController.updateStateForFlag(FLAG_IN_OVERVIEW, true)
+ stashController.applyState(0)
+ }
+ assertThat(stashController.isStashed).isFalse()
+
+ // Expand bubbles. Should stash Taskbar.
+ getInstrumentation().runOnMainSync {
+ stashController.updateStateForSysuiFlags(SYSUI_STATE_BUBBLES_EXPANDED, false)
+ animatorTestRule.advanceTimeBy(TASKBAR_STASH_DURATION)
+ }
+ assertThat(stashController.isStashed).isTrue()
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testAnimatePinnedTaskbar_imeShown_replacesIconsWithHandle() {
+ getInstrumentation().runOnMainSync {
+ stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, false)
+ animatorTestRule.advanceTimeBy(TASKBAR_STASH_DURATION_FOR_IME)
+ }
+ assertThat(viewController.areIconsVisible()).isFalse()
+ assertThat(stashedHandleViewController.isStashedHandleVisible).isTrue()
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testAnimatePinnedTaskbar_imeHidden_replacesHandleWithIcons() {
+ getInstrumentation().runOnMainSync {
+ stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, true)
+ animatorTestRule.advanceTimeBy(0)
+ }
+
+ getInstrumentation().runOnMainSync {
+ stashController.updateStateForSysuiFlags(0, true)
+ animatorTestRule.advanceTimeBy(0)
+ }
+ assertThat(stashedHandleViewController.isStashedHandleVisible).isFalse()
+ assertThat(viewController.areIconsVisible()).isTrue()
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testAnimatePinnedTaskbar_imeHidden_verifyAnimationDuration() {
+ // Start with IME shown.
+ getInstrumentation().runOnMainSync {
+ stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, true)
+ animatorTestRule.advanceTimeBy(0)
+ }
+
+ // Hide IME with animation.
+ getInstrumentation().runOnMainSync {
+ stashController.updateStateForSysuiFlags(0, false)
+ // Fast forward without start delay.
+ animatorTestRule.advanceTimeBy(TASKBAR_STASH_DURATION_FOR_IME)
+ }
+ // Icons should not be visible yet due to start delay.
+ assertThat(viewController.areIconsVisible()).isFalse()
+
+ // Advance by start delay retroactively. Animation should complete.
+ getInstrumentation().runOnMainSync {
+ animatorTestRule.advanceTimeBy(stashController.taskbarStashStartDelayForIme)
+ }
+ assertThat(viewController.areIconsVisible()).isTrue()
+ }
+
+ @Test
+ @TaskbarMode(THREE_BUTTONS)
+ fun testAnimateThreeButtonsTaskbar_imeShown_hidesIconsAndBg() {
+ getInstrumentation().runOnMainSync {
+ stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, false)
+ animatorTestRule.advanceTimeBy(TASKBAR_STASH_DURATION_FOR_IME)
+ }
+ assertThat(viewController.areIconsVisible()).isFalse()
+ assertThat(dragLayerController.imeBgTaskbar.value).isEqualTo(0)
+ }
+
+ @Test
+ @TaskbarMode(THREE_BUTTONS)
+ fun testAnimateThreeButtonsTaskbar_imeHidden_showsIconsAndBg() {
+ getInstrumentation().runOnMainSync {
+ stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, false)
+ animatorTestRule.advanceTimeBy(TASKBAR_STASH_DURATION_FOR_IME)
+ }
+
+ getInstrumentation().runOnMainSync {
+ stashController.updateStateForSysuiFlags(0, false)
+ animatorTestRule.advanceTimeBy(
+ TASKBAR_STASH_DURATION_FOR_IME + stashController.taskbarStashStartDelayForIme
+ )
+ }
+ assertThat(viewController.areIconsVisible()).isTrue()
+ assertThat(dragLayerController.imeBgTaskbar.value).isEqualTo(1)
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testSetSystemGestureInProgress_whileImeShown_unstashesTaskbar() {
+ getInstrumentation().runOnMainSync {
+ stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, true)
+ animatorTestRule.advanceTimeBy(0)
+ }
+
+ getInstrumentation().runOnMainSync {
+ stashController.setSystemGestureInProgress(true)
+ animatorTestRule.advanceTimeBy(
+ TASKBAR_STASH_DURATION_FOR_IME + stashController.taskbarStashStartDelayForIme
+ )
+ }
+ assertThat(stashController.isStashed).isFalse()
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testUnlockTransition_pinnedMode_fadesOutHandle() {
+ getInstrumentation().runOnMainSync {
+ stashController.updateStateForFlag(FLAG_STASHED_DEVICE_LOCKED, true)
+ stashController.applyState(0)
+ }
+ assertThat(stashedHandleViewController.isStashedHandleVisible).isTrue()
+
+ getInstrumentation().runOnMainSync {
+ stashController.updateStateForFlag(FLAG_STASHED_DEVICE_LOCKED, false)
+ stashController.applyState()
+ animatorTestRule.advanceTimeBy(stashController.stashDuration)
+ }
+ assertThat(stashedHandleViewController.isStashedHandleVisible).isFalse()
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testUnlockTransition_transientMode_fadesOutHandleEarly() {
+ getInstrumentation().runOnMainSync {
+ stashController.updateStateForFlag(FLAG_IN_APP, false)
+ stashController.updateStateForFlag(FLAG_STASHED_DEVICE_LOCKED, true)
+ stashController.applyState(0)
+ }
+ assertThat(stashedHandleViewController.isStashedHandleVisible).isTrue()
+
+ getInstrumentation().runOnMainSync {
+ stashController.updateStateForFlag(FLAG_STASHED_DEVICE_LOCKED, false)
+ stashController.applyState()
+ // Time it takes for just the handle to hide (full stash animation is longer).
+ animatorTestRule.advanceTimeBy(TRANSIENT_TASKBAR_STASH_ALPHA_DURATION)
+ }
+ assertThat(stashedHandleViewController.isStashedHandleVisible).isFalse()
+ }
+}
+
+private fun TaskbarStashController.updateStateForFlag(flag: Int, value: Boolean) {
+ updateStateForFlag(flag.toLong(), value)
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt
index 43d924a..f783e40 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt
@@ -199,8 +199,8 @@
assertThat(editText?.hasFocus()).isTrue()
}
- private companion object {
- private val TEST_APPS =
+ companion object {
+ val TEST_APPS =
Array(16) {
AppInfo(
ComponentName(
@@ -213,6 +213,6 @@
)
}
- private val TEST_PREDICTED_APPS = TEST_APPS.take(4).map { WorkspaceItemInfo(it) }
+ val TEST_PREDICTED_APPS = TEST_APPS.take(4).map { WorkspaceItemInfo(it) }
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewControllerTest.kt
new file mode 100644
index 0000000..04f02e9
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewControllerTest.kt
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.allapps
+
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.R
+import com.android.launcher3.appprediction.AppsDividerView
+import com.android.launcher3.appprediction.AppsDividerView.DividerType
+import com.android.launcher3.appprediction.PredictionRowView
+import com.android.launcher3.taskbar.TaskbarStashController
+import com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_IN_APP_AUTO
+import com.android.launcher3.taskbar.allapps.TaskbarAllAppsControllerTest.Companion.TEST_PREDICTED_APPS
+import com.android.launcher3.taskbar.overlay.TaskbarOverlayController
+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.TaskbarPreferenceRule
+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.LauncherMultivalentJUnit
+import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
+import com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT
+import com.android.launcher3.util.TestUtil
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(LauncherMultivalentJUnit::class)
+@EmulatedDevices(["pixelFoldable2023"])
+class TaskbarAllAppsViewControllerTest {
+
+ private val context = TaskbarWindowSandboxContext.create(getInstrumentation().targetContext)
+
+ @get:Rule(order = 0) val taskbarModeRule = TaskbarModeRule(context)
+ @get:Rule(order = 1)
+ val allAppsVisitedPreferenceRule =
+ TaskbarPreferenceRule(context, ALL_APPS_VISITED_COUNT.prefItem)
+ @get:Rule(order = 2) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
+
+ @InjectController lateinit var overlayController: TaskbarOverlayController
+ @InjectController lateinit var stashController: TaskbarStashController
+
+ private val searchSessionController =
+ TestUtil.getOnUiThread { TaskbarSearchSessionController.newInstance(context) }
+
+ @After
+ fun cleanUpSearchSessionController() {
+ getInstrumentation().runOnMainSync { searchSessionController.onDestroy() }
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testShow_transientMode_stashesTaskbar() {
+ getInstrumentation().runOnMainSync {
+ stashController.updateStateForFlag(FLAG_STASHED_IN_APP_AUTO.toLong(), false)
+ stashController.applyState(0)
+ }
+
+ val viewController = createViewController()
+ getInstrumentation().runOnMainSync { viewController.show(false) }
+ assertThat(stashController.isStashed).isTrue()
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testShow_pinnedMode_taskbarDoesNotStash() {
+ val viewController = createViewController()
+ getInstrumentation().runOnMainSync { viewController.show(false) }
+ assertThat(stashController.isStashed).isFalse()
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testHide_transientMode_unstashesTaskbar() {
+ getInstrumentation().runOnMainSync {
+ stashController.updateStateForFlag(FLAG_STASHED_IN_APP_AUTO.toLong(), false)
+ stashController.applyState(0)
+ }
+
+ val viewController = createViewController()
+ getInstrumentation().runOnMainSync { viewController.show(false) }
+ getInstrumentation().runOnMainSync { viewController.close(false) }
+ assertThat(stashController.isStashed).isFalse()
+ }
+
+ @Test
+ fun testShow_firstAllAppsVisit_hasAllAppsTextDivider() {
+ allAppsVisitedPreferenceRule.value = 0
+ val viewController = createViewController()
+ getInstrumentation().runOnMainSync { viewController.show(false) }
+
+ val appsView = overlayController.requestWindow().appsView
+ getInstrumentation().runOnMainSync {
+ appsView.floatingHeaderView
+ .findFixedRowByType(PredictionRowView::class.java)
+ .setPredictedApps(TEST_PREDICTED_APPS)
+ }
+
+ val dividerView =
+ appsView.floatingHeaderView.findFixedRowByType(AppsDividerView::class.java)
+ assertThat(dividerView.dividerType).isEqualTo(DividerType.ALL_APPS_LABEL)
+ }
+
+ @Test
+ fun testShow_maxAllAppsVisitedCount_hasLineDivider() {
+ allAppsVisitedPreferenceRule.value = ALL_APPS_VISITED_COUNT.maxCount
+ val viewController = createViewController()
+ getInstrumentation().runOnMainSync { viewController.show(false) }
+
+ val appsView = overlayController.requestWindow().appsView
+ getInstrumentation().runOnMainSync {
+ appsView.floatingHeaderView
+ .findFixedRowByType(PredictionRowView::class.java)
+ .setPredictedApps(TEST_PREDICTED_APPS)
+ }
+
+ val dividerView =
+ appsView.floatingHeaderView.findFixedRowByType(AppsDividerView::class.java)
+ assertThat(dividerView.dividerType).isEqualTo(DividerType.LINE)
+ }
+
+ private fun createViewController(): TaskbarAllAppsViewController {
+ return TestUtil.getOnUiThread {
+ val overlayContext = overlayController.requestWindow()
+ TaskbarAllAppsViewController(
+ overlayContext,
+ overlayContext.layoutInflater.inflate(
+ R.layout.taskbar_all_apps_sheet,
+ overlayContext.dragLayer,
+ false,
+ ) as TaskbarAllAppsSlideInView,
+ taskbarUnitTestRule.activityContext.controllers,
+ searchSessionController,
+ /* showKeyboard= */ false, // Covered in TaskbarAllAppsControllerTest.
+ )
+ }
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleBarSwipeControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleBarSwipeControllerTest.kt
index 97847be..3b6952d 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleBarSwipeControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleBarSwipeControllerTest.kt
@@ -25,6 +25,7 @@
import com.android.launcher3.touch.OverScroll
import com.google.common.truth.Truth.assertThat
import java.util.Optional
+import kotlin.math.abs
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -46,11 +47,13 @@
const val UNSTASH_THRESHOLD = 100
const val EXPAND_THRESHOLD = 200
const val MAX_OVERSCROLL = 300
+ const val STASH_THRESHOLD = 50
const val UP_BELOW_UNSTASH = -UNSTASH_THRESHOLD + 10f
const val UP_ABOVE_UNSTASH = -UNSTASH_THRESHOLD - 10f
const val UP_ABOVE_EXPAND = -EXPAND_THRESHOLD - 10f
- const val DOWN_BELOW_UNSTASH = UNSTASH_THRESHOLD + 10f
+ const val DOWN_UNDER_STASH = STASH_THRESHOLD - 10f
+ const val DOWN_OVER_STASH = STASH_THRESHOLD + 10f
}
private val context = ApplicationProvider.getApplicationContext<Context>()
@@ -82,6 +85,9 @@
override val maxOverscroll: Int
get() = MAX_OVERSCROLL
+
+ override val stashThreshold: Int
+ get() = STASH_THRESHOLD
}
bubbleBarSwipeController = BubbleBarSwipeController(context, dimensionProvider)
@@ -102,8 +108,12 @@
bubbleBarSwipeController.init(bubbleControllers)
}
+ // region Test that views have damped translation on swipe
+
private fun testViewsHaveDampedTranslationOnSwipe(swipe: Float) {
- val dampedTranslation = -OverScroll.dampedScroll(-swipe, MAX_OVERSCROLL).toFloat()
+ val isUp = swipe < 0
+ val damped = OverScroll.dampedScroll(abs(swipe), MAX_OVERSCROLL).toFloat()
+ val dampedTranslation = if (isUp) -damped else damped
getInstrumentation().runOnMainSync {
bubbleBarSwipeController.start()
bubbleBarSwipeController.swipeTo(swipe)
@@ -142,6 +152,22 @@
testViewsHaveDampedTranslationOnSwipe(UP_ABOVE_EXPAND)
}
+ @Test
+ fun swipeDown_collapsedBar_belowStashThreshold_viewsHaveDampedTranslation() {
+ setUpCollapsedBar()
+ testViewsHaveDampedTranslationOnSwipe(DOWN_UNDER_STASH)
+ }
+
+ @Test
+ fun swipeDown_collapsedBar_overStashThreshold_viewsHaveDampedTranslation() {
+ setUpCollapsedBar()
+ testViewsHaveDampedTranslationOnSwipe(DOWN_OVER_STASH)
+ }
+
+ // endregion
+
+ // region Test that translation on views is reset on finish
+
private fun testViewsTranslationResetOnFinish(swipe: Float) {
getInstrumentation().runOnMainSync {
bubbleBarSwipeController.start()
@@ -195,6 +221,16 @@
}
@Test
+ fun swipeDown_collapsedBar_aboveStashThreshold_animateTranslationToZeroOnFinish() {
+ setUpCollapsedBar()
+ testViewsTranslationResetOnFinish(DOWN_OVER_STASH)
+ }
+
+ // endregion
+
+ // region Test swipe interactions on stashed bar
+
+ @Test
fun swipeUp_stashedBar_belowUnstashThreshold_doesNotShowBar() {
setUpStashedBar()
getInstrumentation().runOnMainSync {
@@ -282,12 +318,28 @@
}
@Test
- fun swipeUp_expandedBar_swipeIgnored() {
+ fun swipeDown_stashedBar_swipeIgnored() {
+ setUpStashedBar()
+ getInstrumentation().runOnMainSync {
+ bubbleBarSwipeController.start()
+ bubbleBarSwipeController.swipeTo(DOWN_OVER_STASH)
+ }
+ verify(bubbleStashedHandleViewController, never()).setTranslationYForSwipe(any())
+ verify(bubbleBarViewController, never()).setTranslationYForSwipe(any())
+ verify(bubbleStashController, never()).showBubbleBar(any())
+ }
+
+ // endregion
+
+ // region Test swipe interactions on expanded bar
+
+ @Test
+ fun swipe_expandedBar_swipeIgnored() {
setUpExpandedBar()
getInstrumentation().runOnMainSync {
bubbleBarSwipeController.start()
bubbleBarSwipeController.swipeTo(UP_ABOVE_EXPAND)
- bubbleBarSwipeController.swipeTo(DOWN_BELOW_UNSTASH)
+ bubbleBarSwipeController.swipeTo(DOWN_OVER_STASH)
bubbleBarSwipeController.finish()
}
verify(bubbleStashedHandleViewController, never()).setTranslationYForSwipe(any())
@@ -295,18 +347,55 @@
verify(bubbleStashController, never()).showBubbleBar(any())
}
+ // endregion
+
+ // region Test swipe interactions on collapsed bar
+
@Test
- fun swipeDown_stashedBar_swipeIgnored() {
- setUpStashedBar()
+ fun swipeDown_collapsedBar_underStashThreshold_doesNotHideBar() {
+ setUpCollapsedBar()
getInstrumentation().runOnMainSync {
bubbleBarSwipeController.start()
- bubbleBarSwipeController.swipeTo(DOWN_BELOW_UNSTASH)
+ bubbleBarSwipeController.swipeTo(DOWN_UNDER_STASH)
+ bubbleBarSwipeController.finish()
}
- verify(bubbleStashedHandleViewController, never()).setTranslationYForSwipe(any())
- verify(bubbleBarViewController, never()).setTranslationYForSwipe(any())
- verify(bubbleStashController, never()).showBubbleBar(any())
+ verify(bubbleStashController, never()).stashBubbleBar()
}
+ @Test
+ fun swipeDown_collapsedBar_overStashThreshold_doesNotHideBarBeforeFinish() {
+ setUpCollapsedBar()
+ getInstrumentation().runOnMainSync {
+ bubbleBarSwipeController.start()
+ bubbleBarSwipeController.swipeTo(DOWN_OVER_STASH)
+ }
+ verify(bubbleStashController, never()).stashBubbleBar()
+ getInstrumentation().runOnMainSync { bubbleBarSwipeController.finish() }
+ verify(bubbleStashController).stashBubbleBar()
+ }
+
+ @Test
+ fun swipeDown_collapsedBar_underStashThreshold_isSwipeGestureFalse() {
+ setUpCollapsedBar()
+ getInstrumentation().runOnMainSync {
+ bubbleBarSwipeController.start()
+ bubbleBarSwipeController.swipeTo(DOWN_UNDER_STASH)
+ }
+ assertThat(bubbleBarSwipeController.isSwipeGesture()).isFalse()
+ }
+
+ @Test
+ fun swipeDown_collapsedBar_overStashThreshold_isSwipeGestureTrue() {
+ setUpCollapsedBar()
+ getInstrumentation().runOnMainSync {
+ bubbleBarSwipeController.start()
+ bubbleBarSwipeController.swipeTo(DOWN_OVER_STASH)
+ }
+ assertThat(bubbleBarSwipeController.isSwipeGesture()).isTrue()
+ }
+
+ // endregion
+
private fun setUpStashedBar() {
whenever(bubbleStashController.isStashed).thenReturn(true)
whenever(bubbleStashController.isBubbleBarVisible()).thenReturn(false)
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutControllerTest.kt
index a58ce08..d857ae5 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutControllerTest.kt
@@ -17,6 +17,8 @@
package com.android.launcher3.taskbar.bubbles.flyout
import android.content.Context
+import android.graphics.Color
+import android.graphics.PointF
import android.view.Gravity
import android.widget.FrameLayout
import android.widget.TextView
@@ -46,11 +48,15 @@
flyoutContainer = FrameLayout(context)
val positioner =
object : BubbleBarFlyoutPositioner {
- override val isOnLeft: Boolean
+ override val isOnLeft
get() = onLeft
- override val targetTy: Float
- get() = 50f
+ override val targetTy = 50f
+ override val distanceToCollapsedPosition = PointF(100f, 200f)
+ override val collapsedSize = 30f
+ override val collapsedColor = Color.BLUE
+ override val collapsedElevation = 1f
+ override val distanceToRevealTriangle = 50f
}
flyoutController = BubbleBarFlyoutController(flyoutContainer, positioner)
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashControllerTest.kt
index 4106a2c..5dc78a9 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashControllerTest.kt
@@ -27,6 +27,7 @@
import com.android.launcher3.taskbar.TaskbarInsetsController
import com.android.launcher3.taskbar.bubbles.BubbleBarView
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController
+import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.BubbleLauncherState
import com.android.launcher3.util.MultiValueAlpha
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -85,12 +86,12 @@
fun setBubblesShowingOnHomeUpdatedToFalse_barPositionYUpdated_controllersNotified() {
// Given bubble bar is on home and has bubbles
whenever(bubbleBarViewController.hasBubbles()).thenReturn(false)
- persistentTaskBarStashController.isBubblesShowingOnHome = true
+ persistentTaskBarStashController.launcherState = BubbleLauncherState.HOME
whenever(bubbleBarViewController.hasBubbles()).thenReturn(true)
// When switch out of the home screen
getInstrumentation().runOnMainSync {
- persistentTaskBarStashController.isBubblesShowingOnHome = false
+ persistentTaskBarStashController.launcherState = BubbleLauncherState.IN_APP
}
// Then translation Y is animating and the bubble bar controller is notified
@@ -110,7 +111,7 @@
// When switch to home screen
getInstrumentation().runOnMainSync {
- persistentTaskBarStashController.isBubblesShowingOnHome = true
+ persistentTaskBarStashController.launcherState = BubbleLauncherState.HOME
}
// Then translation Y is animating and the bubble bar controller is notified
@@ -127,11 +128,11 @@
@Test
fun setBubblesShowingOnOverviewUpdatedToFalse_controllersNotified() {
// Given bubble bar is on overview
- persistentTaskBarStashController.isBubblesShowingOnOverview = true
+ persistentTaskBarStashController.launcherState = BubbleLauncherState.OVERVIEW
clearInvocations(bubbleBarViewController)
// When switch out of the overview screen
- persistentTaskBarStashController.isBubblesShowingOnOverview = false
+ persistentTaskBarStashController.launcherState = BubbleLauncherState.IN_APP
// Then bubble bar controller is notified
verify(bubbleBarViewController).onBubbleBarConfigurationChanged(/* animate= */ true)
@@ -140,7 +141,7 @@
@Test
fun setBubblesShowingOnOverviewUpdatedToTrue_controllersNotified() {
// When switch to the overview screen
- persistentTaskBarStashController.isBubblesShowingOnOverview = true
+ persistentTaskBarStashController.launcherState = BubbleLauncherState.OVERVIEW
// Then bubble bar controller is notified
verify(bubbleBarViewController).onBubbleBarConfigurationChanged(/* animate= */ true)
@@ -150,7 +151,7 @@
fun isSysuiLockedSwitchedToFalseForOverview_unlockAnimationIsShown() {
// Given screen is locked and bubble bar has bubbles
persistentTaskBarStashController.isSysuiLocked = true
- persistentTaskBarStashController.isBubblesShowingOnOverview = true
+ persistentTaskBarStashController.launcherState = BubbleLauncherState.OVERVIEW
whenever(bubbleBarViewController.hasBubbles()).thenReturn(true)
// When switch to the overview screen
@@ -211,14 +212,14 @@
fun bubbleBarTranslationYForTaskbar() {
// Give bubble bar is on home
whenever(bubbleBarViewController.hasBubbles()).thenReturn(false)
- persistentTaskBarStashController.isBubblesShowingOnHome = true
+ persistentTaskBarStashController.launcherState = BubbleLauncherState.HOME
// Then bubbleBarTranslationY would be HOTSEAT_TRANSLATION_Y
assertThat(persistentTaskBarStashController.bubbleBarTranslationY)
.isEqualTo(HOTSEAT_TRANSLATION_Y)
// Give bubble bar is not on home
- persistentTaskBarStashController.isBubblesShowingOnHome = false
+ persistentTaskBarStashController.launcherState = BubbleLauncherState.IN_APP
// Then bubbleBarTranslationY would be TASK_BAR_TRANSLATION_Y
assertThat(persistentTaskBarStashController.bubbleBarTranslationY)
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt
index d4a3b3a..7973e2df 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt
@@ -29,13 +29,16 @@
import com.android.launcher3.anim.AnimatedFloat
import com.android.launcher3.taskbar.StashedHandleView
import com.android.launcher3.taskbar.TaskbarInsetsController
+import com.android.launcher3.taskbar.TaskbarStashController
import com.android.launcher3.taskbar.bubbles.BubbleBarView
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController
import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController
import com.android.launcher3.taskbar.bubbles.BubbleView
+import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.BubbleLauncherState
import com.android.launcher3.util.MultiValueAlpha
import com.android.wm.shell.shared.animation.PhysicsAnimator
import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
+import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
@@ -59,7 +62,7 @@
const val BUBBLE_BAR_WIDTH = 200
const val BUBBLE_BAR_HEIGHT = 100
const val HOTSEAT_TRANSLATION_Y = -45f
- const val TASK_BAR_TRANSLATION_Y = -TASKBAR_BOTTOM_SPACE
+ const val TASK_BAR_TRANSLATION_Y = -TASKBAR_BOTTOM_SPACE.toFloat()
const val HANDLE_VIEW_WIDTH = 150
const val HANDLE_VIEW_HEIGHT = 4
const val BUBBLE_BAR_STASHED_TRANSLATION_Y = -4.5f
@@ -119,7 +122,7 @@
// When switch out of the home screen
getInstrumentation().runOnMainSync {
- mTransientBubbleStashController.isBubblesShowingOnHome = true
+ mTransientBubbleStashController.launcherState = BubbleLauncherState.HOME
}
// Then BubbleBarView is animating, BubbleBarViewController controller is notified
@@ -137,12 +140,12 @@
@Test
fun setBubblesShowingOnOverviewUpdatedToTrue_barPositionYUpdated_controllersNotified() {
- // Given bubble bar is on home and has bubbles
+ // Given bubble bar is on overview and has bubbles
whenever(bubbleBarViewController.hasBubbles()).thenReturn(true)
// When switch out of the home screen
getInstrumentation().runOnMainSync {
- mTransientBubbleStashController.isBubblesShowingOnOverview = true
+ mTransientBubbleStashController.launcherState = BubbleLauncherState.OVERVIEW
}
// Then BubbleBarView is animating, BubbleBarViewController controller is notified
@@ -159,6 +162,27 @@
}
@Test
+ fun setBubblesShowingOnOverviewUpdatedToTrue_unstashes() {
+ // Given bubble bar is stashed with bubbles
+ whenever(bubbleBarViewController.hasBubbles()).thenReturn(true)
+
+ getInstrumentation().runOnMainSync {
+ mTransientBubbleStashController.updateStashedAndExpandedState(
+ stash = true,
+ expand = false,
+ )
+ }
+ assertThat(mTransientBubbleStashController.isStashed).isTrue()
+
+ // Move to overview
+ getInstrumentation().runOnMainSync {
+ mTransientBubbleStashController.launcherState = BubbleLauncherState.OVERVIEW
+ }
+ // No longer stashed in overview
+ assertThat(mTransientBubbleStashController.isStashed).isFalse()
+ }
+
+ @Test
fun updateStashedAndExpandedState_stashAndCollapse_bubbleBarHidden_stashedHandleShown() {
// Given bubble bar has bubbles and not stashed
mTransientBubbleStashController.isStashed = false
@@ -196,11 +220,98 @@
}
@Test
+ fun updateStashedAndExpandedState_unstash_bubbleBarShown_stashedHandleHidden() {
+ // Given bubble bar has bubbles and is stashed
+ mTransientBubbleStashController.isStashed = true
+ whenever(bubbleBarViewController.isHiddenForNoBubbles).thenReturn(false)
+
+ val bubbleInitialTranslation = bubbleView.translationY
+
+ // When unstash
+ getInstrumentation().runOnMainSync {
+ mTransientBubbleStashController.updateStashedAndExpandedState(
+ stash = false,
+ expand = false,
+ )
+ }
+
+ // Wait until animations ends
+ advanceTimeBy(BubbleStashController.BAR_STASH_DURATION)
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
+
+ // Then check BubbleBarController is notified
+ verify(bubbleBarViewController).onStashStateChanging()
+ // Bubble bar is unstashed
+ assertThat(mTransientBubbleStashController.isStashed).isFalse()
+ assertThat(bubbleBarView.translationY).isEqualTo(TASK_BAR_TRANSLATION_Y)
+ assertThat(bubbleBarView.alpha).isEqualTo(1f)
+ assertThat(bubbleBarView.scaleX).isEqualTo(1f)
+ assertThat(bubbleBarView.scaleY).isEqualTo(1f)
+ assertThat(bubbleBarView.background.alpha).isEqualTo(255)
+ // Handle view is hidden
+ assertThat(stashedHandleView.translationY).isEqualTo(0)
+ assertThat(stashedHandleView.alpha).isEqualTo(0)
+ // Bubble view is reset
+ assertThat(bubbleView.translationY).isEqualTo(bubbleInitialTranslation)
+ assertThat(bubbleView.alpha).isEqualTo(1f)
+ }
+
+ @Test
+ fun updateStashedAndExpandedState_stash_animatesAlphaForBubblesAndBackgroundSeparately() {
+ // Given bubble bar has bubbles and is unstashed
+ mTransientBubbleStashController.isStashed = false
+ whenever(bubbleBarViewController.isHiddenForNoBubbles).thenReturn(false)
+
+ // When stash
+ getInstrumentation().runOnMainSync {
+ mTransientBubbleStashController.updateStashedAndExpandedState(
+ stash = true,
+ expand = false,
+ )
+ }
+
+ // Stop after alpha starts
+ advanceTimeBy(TaskbarStashController.TASKBAR_STASH_ALPHA_START_DELAY + 10)
+
+ // Bubble bar alpha is set to 1
+ assertThat(bubbleBarView.alpha).isEqualTo(1f)
+ // We animate alpha for background and children separately
+ assertThat(bubbleView.alpha).isIn(Range.open(0f, 1f))
+ assertThat(bubbleBarView.background.alpha).isIn(Range.open(0, 255))
+ assertThat(bubbleBarView.background.alpha).isNotEqualTo((bubbleView.alpha * 255f).toInt())
+ }
+
+ @Test
+ fun updateStashedAndExpandedState_unstash_animatesAlphaForBubblesAndBackgroundSeparately() {
+ // Given bubble bar has bubbles and is stashed
+ mTransientBubbleStashController.isStashed = true
+ whenever(bubbleBarViewController.isHiddenForNoBubbles).thenReturn(false)
+
+ // When unstash
+ getInstrumentation().runOnMainSync {
+ mTransientBubbleStashController.updateStashedAndExpandedState(
+ stash = false,
+ expand = false,
+ )
+ }
+
+ // Stop after alpha starts
+ advanceTimeBy(TaskbarStashController.TASKBAR_STASH_ALPHA_START_DELAY + 10)
+
+ // Bubble bar alpha is set to 1
+ assertThat(bubbleBarView.alpha).isEqualTo(1f)
+ // We animate alpha for background and children separately
+ assertThat(bubbleView.alpha).isIn(Range.open(0f, 1f))
+ assertThat(bubbleBarView.background.alpha).isIn(Range.open(0, 255))
+ assertThat(bubbleBarView.background.alpha).isNotEqualTo((bubbleView.alpha * 255f).toInt())
+ }
+
+ @Test
fun isSysuiLockedSwitchedToFalseForOverview_unlockAnimationIsShown() {
// Given screen is locked and bubble bar has bubbles
getInstrumentation().runOnMainSync {
mTransientBubbleStashController.isSysuiLocked = true
- mTransientBubbleStashController.isBubblesShowingOnOverview = true
+ mTransientBubbleStashController.launcherState = BubbleLauncherState.OVERVIEW
whenever(bubbleBarViewController.hasBubbles()).thenReturn(true)
}
advanceTimeBy(BubbleStashController.BAR_TRANSLATION_DURATION)
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
index f31467f..a87465f 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
@@ -20,6 +20,7 @@
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
+import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.launcher3.util.TestDispatcherProvider
import com.android.quickstep.task.thumbnail.TaskThumbnailViewModelTest
import com.android.quickstep.util.DesktopTask
@@ -36,17 +37,19 @@
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Test
+import org.junit.runner.RunWith
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
class TasksRepositoryTest {
private val tasks = (0..5).map(::createTaskWithId)
private val defaultTaskList =
listOf(
GroupTask(tasks[0]),
GroupTask(tasks[1], tasks[2], null),
- DesktopTask(tasks.subList(3, 6))
+ DesktopTask(tasks.subList(3, 6)),
)
private val recentsModel = FakeRecentTasksDataSource()
private val taskThumbnailDataSource = FakeTaskThumbnailDataSource()
@@ -65,7 +68,7 @@
taskIconDataSource,
taskVisualsChangedDelegate,
testScope.backgroundScope,
- TestDispatcherProvider(dispatcher)
+ TestDispatcherProvider(dispatcher),
)
@Test
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt
index 99d3121..541a48d 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt
@@ -28,9 +28,9 @@
import com.android.quickstep.TopTaskTracker.CachedTaskInfo
import com.android.systemui.shared.recents.model.Task
import com.android.systemui.shared.recents.model.Task.TaskKey
-import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_30_70
-import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50
-import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_70_30
+import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_33_66
+import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50
+import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_66_33
import java.util.function.Consumer
import org.junit.Assert.assertEquals
import org.junit.Before
@@ -59,23 +59,23 @@
private lateinit var appPairsController: AppPairsController
- private val left30: Int by lazy {
- appPairsController.encodeRank(STAGE_POSITION_TOP_OR_LEFT, SNAP_TO_30_70)
+ private val left33: Int by lazy {
+ appPairsController.encodeRank(STAGE_POSITION_TOP_OR_LEFT, SNAP_TO_2_33_66)
}
private val left50: Int by lazy {
- appPairsController.encodeRank(STAGE_POSITION_TOP_OR_LEFT, SNAP_TO_50_50)
+ appPairsController.encodeRank(STAGE_POSITION_TOP_OR_LEFT, SNAP_TO_2_50_50)
}
- private val left70: Int by lazy {
- appPairsController.encodeRank(STAGE_POSITION_TOP_OR_LEFT, SNAP_TO_70_30)
+ private val left66: Int by lazy {
+ appPairsController.encodeRank(STAGE_POSITION_TOP_OR_LEFT, SNAP_TO_2_66_33)
}
- private val right30: Int by lazy {
- appPairsController.encodeRank(STAGE_POSITION_BOTTOM_OR_RIGHT, SNAP_TO_30_70)
+ private val right33: Int by lazy {
+ appPairsController.encodeRank(STAGE_POSITION_BOTTOM_OR_RIGHT, SNAP_TO_2_33_66)
}
private val right50: Int by lazy {
- appPairsController.encodeRank(STAGE_POSITION_BOTTOM_OR_RIGHT, SNAP_TO_50_50)
+ appPairsController.encodeRank(STAGE_POSITION_BOTTOM_OR_RIGHT, SNAP_TO_2_50_50)
}
- private val right70: Int by lazy {
- appPairsController.encodeRank(STAGE_POSITION_BOTTOM_OR_RIGHT, SNAP_TO_70_30)
+ private val right66: Int by lazy {
+ appPairsController.encodeRank(STAGE_POSITION_BOTTOM_OR_RIGHT, SNAP_TO_2_66_33)
}
@Mock lateinit var mockAppPairIcon: AppPairIcon
@@ -113,26 +113,26 @@
@Test
fun shouldEncodeRankCorrectly() {
- assertEquals("left + 30-70 should encode as 0 (0b0)", 0, left30)
+ assertEquals("left + 33-66 should encode as 0 (0b0)", 0, left33)
assertEquals("left + 50-50 should encode as 1 (0b1)", 1, left50)
- assertEquals("left + 70-30 should encode as 2 (0b10)", 2, left70)
+ assertEquals("left + 66-33 should encode as 2 (0b10)", 2, left66)
// See AppPairsController#BITMASK_SIZE and BITMASK_FOR_SNAP_POSITION for context
- assertEquals("right + 30-70 should encode as 1 followed by 16 0s", 1 shl 16, right30)
+ assertEquals("right + 33-66 should encode as 1 followed by 16 0s", 1 shl 16, right33)
assertEquals("right + 50-50 should encode as the above value + 1", (1 shl 16) + 1, right50)
- assertEquals("right + 70-30 should encode as the above value + 2", (1 shl 16) + 2, right70)
+ assertEquals("right + 66-33 should encode as the above value + 2", (1 shl 16) + 2, right66)
}
@Test
fun shouldDecodeRankCorrectly() {
assertEquals(
- "left + 30-70 should decode to left",
+ "left + 33-66 should decode to left",
STAGE_POSITION_TOP_OR_LEFT,
- AppPairsController.convertRankToStagePosition(left30),
+ AppPairsController.convertRankToStagePosition(left33),
)
assertEquals(
- "left + 30-70 should decode to 30-70",
- SNAP_TO_30_70,
- AppPairsController.convertRankToSnapPosition(left30),
+ "left + 33-66 should decode to 33-66",
+ SNAP_TO_2_33_66,
+ AppPairsController.convertRankToSnapPosition(left33),
)
assertEquals(
@@ -142,30 +142,30 @@
)
assertEquals(
"left + 50-50 should decode to 50-50",
- SNAP_TO_50_50,
+ SNAP_TO_2_50_50,
AppPairsController.convertRankToSnapPosition(left50),
)
assertEquals(
- "left + 70-30 should decode to left",
+ "left + 66-33 should decode to left",
STAGE_POSITION_TOP_OR_LEFT,
- AppPairsController.convertRankToStagePosition(left70),
+ AppPairsController.convertRankToStagePosition(left66),
)
assertEquals(
- "left + 70-30 should decode to 70-30",
- SNAP_TO_70_30,
- AppPairsController.convertRankToSnapPosition(left70),
+ "left + 66-33 should decode to 66-33",
+ SNAP_TO_2_66_33,
+ AppPairsController.convertRankToSnapPosition(left66),
)
assertEquals(
- "right + 30-70 should decode to right",
+ "right + 33-66 should decode to right",
STAGE_POSITION_BOTTOM_OR_RIGHT,
- AppPairsController.convertRankToStagePosition(right30),
+ AppPairsController.convertRankToStagePosition(right33),
)
assertEquals(
- "right + 30-70 should decode to 30-70",
- SNAP_TO_30_70,
- AppPairsController.convertRankToSnapPosition(right30),
+ "right + 33-66 should decode to 33-66",
+ SNAP_TO_2_33_66,
+ AppPairsController.convertRankToSnapPosition(right33),
)
assertEquals(
@@ -175,19 +175,19 @@
)
assertEquals(
"right + 50-50 should decode to 50-50",
- SNAP_TO_50_50,
+ SNAP_TO_2_50_50,
AppPairsController.convertRankToSnapPosition(right50),
)
assertEquals(
- "right + 70-30 should decode to right",
+ "right + 66-33 should decode to right",
STAGE_POSITION_BOTTOM_OR_RIGHT,
- AppPairsController.convertRankToStagePosition(right70),
+ AppPairsController.convertRankToStagePosition(right66),
)
assertEquals(
- "right + 70-30 should decode to 70-30",
- SNAP_TO_70_30,
- AppPairsController.convertRankToSnapPosition(right70),
+ "right + 66-33 should decode to 66-33",
+ SNAP_TO_2_66_33,
+ AppPairsController.convertRankToSnapPosition(right66),
)
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt
index 7b1c066..108cfb5 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt
@@ -66,7 +66,7 @@
Rect(),
1,
2,
- SplitScreenConstants.SNAP_TO_50_50
+ SplitScreenConstants.SNAP_TO_2_50_50
)
val task1 = GroupTask(createTask(1), createTask(2), splitBounds, TaskViewType.GROUPED)
val task2 = GroupTask(createTask(1), createTask(2), splitBounds, TaskViewType.GROUPED)
@@ -81,7 +81,7 @@
Rect(),
1,
2,
- SplitScreenConstants.SNAP_TO_50_50
+ SplitScreenConstants.SNAP_TO_2_50_50
)
val splitBounds2 =
SplitConfigurationOptions.SplitBounds(
@@ -89,7 +89,7 @@
Rect(),
1,
2,
- SplitScreenConstants.SNAP_TO_30_70
+ SplitScreenConstants.SNAP_TO_2_33_66
)
val task1 = GroupTask(createTask(1), createTask(2), splitBounds1, TaskViewType.GROUPED)
val task2 = GroupTask(createTask(1), createTask(2), splitBounds2, TaskViewType.GROUPED)
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
index 936e996..cb70694 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
@@ -36,9 +36,10 @@
import com.android.quickstep.RecentsModel
import com.android.quickstep.SystemUiProxy
import com.android.quickstep.util.SplitSelectStateController.SplitFromDesktopController
+import com.android.quickstep.views.RecentsView
import com.android.quickstep.views.RecentsViewContainer
import com.android.systemui.shared.recents.model.Task
-import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50
+import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
@@ -50,6 +51,7 @@
import org.mockito.Mockito.`when`
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import java.util.function.Consumer
@@ -66,6 +68,7 @@
private val recentsModel: RecentsModel = mock()
private val pendingIntent: PendingIntent = mock()
private val splitFromDesktopController: SplitFromDesktopController = mock()
+ private val recentsView: RecentsView<*, *> = mock()
private lateinit var splitSelectStateController: SplitSelectStateController
@@ -73,6 +76,7 @@
private val nonPrimaryUserHandle = UserHandle(ActivityManager.RunningTaskInfo().userId + 10)
private var taskIdCounter = 0
+
private fun getUniqueId(): Int {
return ++taskIdCounter
}
@@ -90,7 +94,7 @@
statsLogManager,
systemUiProxy,
recentsModel,
- null /*activityBackCallback*/
+ null, /*activityBackCallback*/
)
}
@@ -100,12 +104,12 @@
val groupTask1 =
generateGroupTask(
ComponentName("pomegranate", "juice"),
- ComponentName("pumpkin", "pie")
+ ComponentName("pumpkin", "pie"),
)
val groupTask2 =
generateGroupTask(
ComponentName("hotdog", "juice"),
- ComponentName("personal", "computer")
+ ComponentName("personal", "computer"),
)
val tasks: ArrayList<GroupTask> = ArrayList()
tasks.add(groupTask1)
@@ -122,7 +126,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(nonMatchingComponent),
false /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -141,12 +145,12 @@
val groupTask1 =
generateGroupTask(
ComponentName(matchingPackage, matchingClass),
- ComponentName("pomegranate", "juice")
+ ComponentName("pomegranate", "juice"),
)
val groupTask2 =
generateGroupTask(
ComponentName("pumpkin", "pie"),
- ComponentName("personal", "computer")
+ ComponentName("personal", "computer"),
)
val tasks: ArrayList<GroupTask> = ArrayList()
tasks.add(groupTask1)
@@ -159,12 +163,12 @@
assertEquals(
"ComponentName package mismatched",
it[0].key.baseIntent.component?.packageName,
- matchingPackage
+ matchingPackage,
)
assertEquals(
"ComponentName class mismatched",
it[0].key.baseIntent.component?.className,
- matchingClass
+ matchingClass,
)
assertEquals(it[0], groupTask1.task1)
}
@@ -175,7 +179,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(matchingComponent),
false /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -194,12 +198,12 @@
val groupTask1 =
generateGroupTask(
ComponentName(matchingPackage, matchingClass),
- ComponentName("pomegranate", "juice")
+ ComponentName("pomegranate", "juice"),
)
val groupTask2 =
generateGroupTask(
ComponentName("pumpkin", "pie"),
- ComponentName("personal", "computer")
+ ComponentName("personal", "computer"),
)
val tasks: ArrayList<GroupTask> = ArrayList()
tasks.add(groupTask1)
@@ -216,7 +220,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(nonPrimaryUserComponent),
false /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -237,12 +241,12 @@
ComponentName(matchingPackage, matchingClass),
nonPrimaryUserHandle,
ComponentName("pomegranate", "juice"),
- nonPrimaryUserHandle
+ nonPrimaryUserHandle,
)
val groupTask2 =
generateGroupTask(
ComponentName("pumpkin", "pie"),
- ComponentName("personal", "computer")
+ ComponentName("personal", "computer"),
)
val tasks: ArrayList<GroupTask> = ArrayList()
tasks.add(groupTask1)
@@ -255,12 +259,12 @@
assertEquals(
"ComponentName package mismatched",
it[0].key.baseIntent.component?.packageName,
- matchingPackage
+ matchingPackage,
)
assertEquals(
"ComponentName class mismatched",
it[0].key.baseIntent.component?.className,
- matchingClass
+ matchingClass,
)
assertEquals("userId mismatched", it[0].key.userId, nonPrimaryUserHandle.identifier)
assertEquals(it[0], groupTask1.task1)
@@ -272,7 +276,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(nonPrimaryUserComponent),
false /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -291,12 +295,12 @@
val groupTask1 =
generateGroupTask(
ComponentName(matchingPackage, matchingClass),
- ComponentName("pumpkin", "pie")
+ ComponentName("pumpkin", "pie"),
)
val groupTask2 =
generateGroupTask(
ComponentName("pomegranate", "juice"),
- ComponentName(matchingPackage, matchingClass)
+ ComponentName(matchingPackage, matchingClass),
)
val tasks: ArrayList<GroupTask> = ArrayList()
tasks.add(groupTask2)
@@ -309,12 +313,12 @@
assertEquals(
"ComponentName package mismatched",
it[0].key.baseIntent.component?.packageName,
- matchingPackage
+ matchingPackage,
)
assertEquals(
"ComponentName class mismatched",
it[0].key.baseIntent.component?.className,
- matchingClass
+ matchingClass,
)
assertEquals(it[0], groupTask1.task1)
}
@@ -325,7 +329,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(matchingComponent),
false /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -348,7 +352,7 @@
val groupTask2 =
generateGroupTask(
ComponentName("pomegranate", "juice"),
- ComponentName(matchingPackage, matchingClass)
+ ComponentName(matchingPackage, matchingClass),
)
val tasks: ArrayList<GroupTask> = ArrayList()
tasks.add(groupTask2)
@@ -363,12 +367,12 @@
assertEquals(
"ComponentName package mismatched",
it[1].key.baseIntent.component?.packageName,
- matchingPackage
+ matchingPackage,
)
assertEquals(
"ComponentName class mismatched",
it[1].key.baseIntent.component?.className,
- matchingClass
+ matchingClass,
)
assertEquals(it[1], groupTask2.task2)
}
@@ -379,7 +383,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(nonMatchingComponent, matchingComponent),
false /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -401,7 +405,7 @@
val groupTask2 =
generateGroupTask(
ComponentName("pomegranate", "juice"),
- ComponentName(matchingPackage, matchingClass)
+ ComponentName(matchingPackage, matchingClass),
)
val tasks: ArrayList<GroupTask> = ArrayList()
tasks.add(groupTask2)
@@ -415,12 +419,12 @@
assertEquals(
"ComponentName package mismatched",
it[0].key.baseIntent.component?.packageName,
- matchingPackage
+ matchingPackage,
)
assertEquals(
"ComponentName class mismatched",
it[0].key.baseIntent.component?.className,
- matchingClass
+ matchingClass,
)
assertEquals(it[0], groupTask2.task2)
assertNull("No tasks should have matched", it[1] /*task*/)
@@ -432,7 +436,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(matchingComponent, matchingComponent),
false /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -452,12 +456,12 @@
val groupTask1 =
generateGroupTask(
ComponentName(matchingPackage, matchingClass),
- ComponentName("pumpkin", "pie")
+ ComponentName("pumpkin", "pie"),
)
val groupTask2 =
generateGroupTask(
ComponentName("pomegranate", "juice"),
- ComponentName(matchingPackage, matchingClass)
+ ComponentName(matchingPackage, matchingClass),
)
val tasks: ArrayList<GroupTask> = ArrayList()
tasks.add(groupTask2)
@@ -471,23 +475,23 @@
assertEquals(
"ComponentName package mismatched",
it[0].key.baseIntent.component?.packageName,
- matchingPackage
+ matchingPackage,
)
assertEquals(
"ComponentName class mismatched",
it[0].key.baseIntent.component?.className,
- matchingClass
+ matchingClass,
)
assertEquals(it[0], groupTask1.task1)
assertEquals(
"ComponentName package mismatched",
it[1].key.baseIntent.component?.packageName,
- matchingPackage
+ matchingPackage,
)
assertEquals(
"ComponentName class mismatched",
it[1].key.baseIntent.component?.className,
- matchingClass
+ matchingClass,
)
assertEquals(it[1], groupTask2.task2)
}
@@ -498,7 +502,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(matchingComponent, matchingComponent),
false /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -524,12 +528,12 @@
val groupTask2 =
generateGroupTask(
ComponentName(matchingPackage2, matchingClass2),
- ComponentName(matchingPackage, matchingClass)
+ ComponentName(matchingPackage, matchingClass),
)
val groupTask3 =
generateGroupTask(
ComponentName("hotdog", "pie"),
- ComponentName(matchingPackage, matchingClass)
+ ComponentName(matchingPackage, matchingClass),
)
val tasks: ArrayList<GroupTask> = ArrayList()
tasks.add(groupTask3)
@@ -550,7 +554,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(matchingComponent2, matchingComponent),
true /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -567,7 +571,7 @@
-1 /*stagePosition*/,
ItemInfo(),
null /*splitEvent*/,
- 10 /*alreadyRunningTask*/
+ 10, /*alreadyRunningTask*/
)
assertTrue(splitSelectStateController.isSplitSelectActive)
}
@@ -579,21 +583,23 @@
-1 /*stagePosition*/,
ItemInfo(),
null /*splitEvent*/,
- -1 /*alreadyRunningTask*/
+ -1, /*alreadyRunningTask*/
)
assertTrue(splitSelectStateController.isSplitSelectActive)
}
@Test
fun resetAfterInitial() {
+ whenever(context.getOverviewPanel<RecentsView<*, *>>()).thenReturn(recentsView)
splitSelectStateController.setInitialTaskSelect(
Intent() /*intent*/,
-1 /*stagePosition*/,
ItemInfo(),
null /*splitEvent*/,
- -1
+ -1,
)
splitSelectStateController.resetState()
+ verify(recentsView, times(1)).resetDesktopTaskFromSplitSelectState()
assertFalse(splitSelectStateController.isSplitSelectActive)
}
@@ -622,7 +628,7 @@
// Generate GroupTask with default userId.
private fun generateGroupTask(
task1ComponentName: ComponentName,
- task2ComponentName: ComponentName
+ task2ComponentName: ComponentName,
): GroupTask {
val task1 = Task()
var taskInfo = ActivityManager.RunningTaskInfo()
@@ -642,7 +648,7 @@
return GroupTask(
task1,
task2,
- SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1, SNAP_TO_50_50)
+ SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1, SNAP_TO_2_50_50),
)
}
@@ -651,7 +657,7 @@
task1ComponentName: ComponentName,
userHandle1: UserHandle,
task2ComponentName: ComponentName,
- userHandle2: UserHandle
+ userHandle2: UserHandle,
): GroupTask {
val task1 = Task()
var taskInfo = ActivityManager.RunningTaskInfo()
@@ -674,7 +680,7 @@
return GroupTask(
task1,
task2,
- SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1, SNAP_TO_50_50)
+ SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1, SNAP_TO_2_50_50),
)
}
}
diff --git a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
index 885a7f6..231c113 100644
--- a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
@@ -24,6 +24,7 @@
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.launcher3.AbstractFloatingView
import com.android.launcher3.AbstractFloatingViewHelper
+import com.android.launcher3.Flags.enableRefactorTaskThumbnail
import com.android.launcher3.logging.StatsLogManager
import com.android.launcher3.logging.StatsLogManager.LauncherEvent
import com.android.launcher3.model.data.WorkspaceItemInfo
@@ -31,6 +32,7 @@
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.launcher3.util.TransformingTouchDelegate
import com.android.quickstep.TaskOverlayFactory.TaskOverlay
+import com.android.quickstep.task.thumbnail.TaskThumbnailView
import com.android.quickstep.views.LauncherRecentsView
import com.android.quickstep.views.TaskContainer
import com.android.quickstep.views.TaskThumbnailViewDeprecated
@@ -67,7 +69,6 @@
private val taskView: TaskView = mock()
private val workspaceItemInfo: WorkspaceItemInfo = mock()
private val abstractFloatingViewHelper: AbstractFloatingViewHelper = mock()
- private val thumbnailViewDeprecated: TaskThumbnailViewDeprecated = mock()
private val iconView: TaskViewIcon = mock()
private val transformingTouchDelegate: TransformingTouchDelegate = mock()
private val factory: TaskShortcutFactory =
@@ -175,7 +176,7 @@
.moveTaskToDesktop(
eq(taskContainer),
eq(DesktopModeTransitionSource.APP_FROM_OVERVIEW),
- any()
+ any(),
)
verify(statsLogger).withItemInfo(workspaceItemInfo)
verify(statsLogger).log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_DESKTOP_TAP)
@@ -188,16 +189,19 @@
}
private fun createTaskContainer(task: Task): TaskContainer {
+ val snapshotView =
+ if (enableRefactorTaskThumbnail()) mock<TaskThumbnailView>()
+ else mock<TaskThumbnailViewDeprecated>()
return TaskContainer(
taskView,
task,
- thumbnailViewDeprecated,
+ snapshotView,
iconView,
transformingTouchDelegate,
SplitConfigurationOptions.STAGE_POSITION_UNDEFINED,
digitalWellBeingToast = null,
showWindowsView = null,
- overlayFactory
+ overlayFactory,
)
}
}
diff --git a/res/layout/widgets_full_sheet.xml b/res/layout/widgets_full_sheet.xml
index 009359c..1f14f69 100644
--- a/res/layout/widgets_full_sheet.xml
+++ b/res/layout/widgets_full_sheet.xml
@@ -24,9 +24,7 @@
<com.android.launcher3.views.SpringRelativeLayout
android:id="@+id/container"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:focusable="true"
- android:importantForAccessibility="no">
+ android:layout_height="match_parent">
<View
android:id="@+id/collapse_handle"
@@ -74,4 +72,4 @@
android:clipToPadding="false" />
</com.android.launcher3.views.SpringRelativeLayout>
-</com.android.launcher3.widget.picker.WidgetsFullSheet>
\ No newline at end of file
+</com.android.launcher3.widget.picker.WidgetsFullSheet>
diff --git a/res/layout/widgets_two_pane_sheet.xml b/res/layout/widgets_two_pane_sheet.xml
index ce5eed9..8235875 100644
--- a/res/layout/widgets_two_pane_sheet.xml
+++ b/res/layout/widgets_two_pane_sheet.xml
@@ -23,9 +23,7 @@
<com.android.launcher3.views.SpringRelativeLayout
android:id="@+id/container"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:focusable="true"
- android:importantForAccessibility="no">
+ android:layout_height="match_parent">
<View
android:id="@+id/collapse_handle"
diff --git a/res/layout/widgets_two_pane_sheet_paged_view.xml b/res/layout/widgets_two_pane_sheet_paged_view.xml
index 1cbd2ba..71c77b5 100644
--- a/res/layout/widgets_two_pane_sheet_paged_view.xml
+++ b/res/layout/widgets_two_pane_sheet_paged_view.xml
@@ -20,16 +20,19 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="start"
- android:paddingHorizontal="@dimen/widget_list_horizontal_margin_two_pane"
android:layout_gravity="start"
android:clipChildren="false"
android:clipToPadding="false"
android:layout_alignParentStart="true">
+ <!-- Note: the paddingHorizontal has to be on WidgetPagedView level so that talkback
+ correctly orders the lists to be after the search and suggestions header. See b/209579563.
+ -->
<com.android.launcher3.widget.picker.WidgetPagedView
android:id="@+id/widgets_view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
+ android:paddingHorizontal="@dimen/widget_list_horizontal_margin_two_pane"
android:descendantFocusability="afterDescendants"
launcher:pageIndicator="@+id/tabs" >
@@ -48,11 +51,13 @@
</com.android.launcher3.widget.picker.WidgetPagedView>
<!-- SearchAndRecommendationsView without the tab layout as well -->
+ <!-- Note: the horizontal padding matches with the WidgetPagedView -->
<com.android.launcher3.views.StickyHeaderLayout
android:id="@+id/search_and_recommendations_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToOutline="true"
+ android:paddingHorizontal="@dimen/widget_list_horizontal_margin_two_pane"
android:orientation="vertical">
<LinearLayout
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index be91c0b..53dc4ba 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"የተከፈለ ማያ ገፅ"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"የመተግበሪያ መረጃ ለ%1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"የ%1$s የአጠቃቀም ቅንብሮች"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"አዲስ መስኮት"</string>
<string name="save_app_pair" msgid="5647523853662686243">"የመተግበሪያ ጥምረትን ያስቀምጡ"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"ይህ የመተግበሪያ ጥምረት በዚህ መሣሪያ ላይ አይደገፍም"</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 35ba183..002c800 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Podeljeni ekran"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacije o aplikaciji za: %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Podešavanja potrošnje za %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Novi prozor"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Sačuvaj par aplikacija"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Ovaj par aplikacija nije podržan na ovom uređaju"</string>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 1a2bcc1..0984f32 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Падзелены экран"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Інфармацыя пра праграму для: %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s: налады выкарыстання"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Новае акно"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Захаваць спалучэнне праграм"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Дадзенае спалучэнне праграм не падтрымліваецца на гэтай прыладзе"</string>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index e4bbd30..2b168f6 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Podijeljeni ekran"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacije o aplikaciji %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Postavke korištenja za: %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Novi prozor"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Sačuvaj par aplikacija"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Par aplikacija nije podržan na uređaju"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index aee49be..f7b04a3 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Split screen"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"App info for %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Usage settings for %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"New window"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Save app pair"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"This app pair isn\'t supported on this device"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 169afe5..ddcee65 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantalla dividida"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Información de la aplicación %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Ajustes de uso para %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Ventana nueva"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Guardar apps emparejadas"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"El dispositivo no admite esta aplicación emparejada"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index d841770..c0a6fe3 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -132,7 +132,7 @@
<string name="msg_missing_notification_access" msgid="281113995110910548">"برای نمایش «نقطههای اعلان»، اعلانهای برنامه را برای <xliff:g id="NAME">%1$s</xliff:g> روشن کنید"</string>
<string name="title_change_settings" msgid="1376365968844349552">"تغییر تنظیمات"</string>
<string name="notification_dots_service_title" msgid="4284221181793592871">"نمایش نقطههای اعلان"</string>
- <string name="developer_options_title" msgid="700788437593726194">"گزینههای برنامهنویس"</string>
+ <string name="developer_options_title" msgid="700788437593726194">"گزینههای توسعهدهندگان"</string>
<string name="auto_add_shortcuts_label" msgid="4926805029653694105">"افزودن نماد برنامهها به صفحه اصلی"</string>
<string name="auto_add_shortcuts_description" msgid="7117251166066978730">"برای برنامههای جدید"</string>
<string name="package_state_unknown" msgid="7592128424511031410">"نامشخص"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 3d5380a..a9fd14e 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Podijeljeni zaslon"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacije o aplikaciji %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Postavke upotrebe za %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Novi prozor"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Spremi par aplikacija"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Taj par aplikacija nije podržan na ovom uređaju"</string>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 7a87b26..0014317 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Skipta skjá"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Upplýsingar um forrit fyrir %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Notkunarstillingar fyrir %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Nýr gluggi"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Vista forritapar"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Þetta forritapar er ekki stutt í þessu tæki"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index cea0a08..fc89041 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"分割画面"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s のアプリ情報"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s の使用設定"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"新しいウィンドウ"</string>
<string name="save_app_pair" msgid="5647523853662686243">"アプリのペア設定を保存"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"このデバイスは、このアプリのペア設定に対応していません"</string>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index 2211a7d..f099bcd 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"ეკრანის გაყოფა"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s-ის აპის ინფო"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"გამოყენების პარამეტრები %1$s-ისთვის"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"ახალი ფანჯარა"</string>
<string name="save_app_pair" msgid="5647523853662686243">"აპთა წყვილის შენახვა"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"ამ მოწყობილობაზე აღნიშნული აპთა წყვილი არ არის მხარდაჭერილი"</string>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index ca26ea4..ebaacc9 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Экранды бөлу"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s қолданбасы туралы ақпарат"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s пайдалану параметрлері"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Жаңа терезе"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Қолданбаларды жұптау әрекетін сақтау"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Бұл құрылғы қолданбаларды жұптау функциясын қолдамайды."</string>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index dc4fd80..b05eeb0 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"មុខងារបំបែកអេក្រង់"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"ព័ត៌មានកម្មវិធីសម្រាប់ %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"ការកំណត់ការប្រើប្រាស់សម្រាប់ %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"វិនដូថ្មី"</string>
<string name="save_app_pair" msgid="5647523853662686243">"រក្សាទុកគូកម្មវិធី"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"មិនអាចប្រើគូកម្មវិធីនេះនៅលើឧបករណ៍នេះបានទេ"</string>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index b326181..753d2dd 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Экранды бөлүү"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s колдонмосу жөнүндө маалымат"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s колдонмосун пайдалануу параметрлери"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Жаңы терезе"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Колдонмолорду сактап коюу"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Бул эки колдонмону бул түзмөктө бир маалда пайдаланууга болбойт"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index a613cb1..6879862 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Išskaidyto ekrano režimas"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Programos „%1$s“ informacija"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"„%1$s“ naudojimo nustatymai"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Naujas langas"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Išsaugoti programų porą"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Ši programų pora šiame įrenginyje nepalaikoma"</string>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 2eb1b2b..b9e68e0 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"സ്ക്രീൻ വിഭജന മോഡ്"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s എന്നതിന്റെ ആപ്പ് വിവരങ്ങൾ"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s എന്നതിനുള്ള ഉപയോഗ ക്രമീകരണം"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"പുതിയ വിന്ഡോ"</string>
<string name="save_app_pair" msgid="5647523853662686243">"ആപ്പ് ജോടി സംരക്ഷിക്കുക"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"ഈ ഉപകരണത്തിൽ ഈ ആപ്പ് ജോടിക്ക് പിന്തുണയില്ല"</string>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index b548e67..5ddf8a2 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Skrin pisah"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Maklumat apl untuk %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Tetapan penggunaan sebanyak %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Tetingkap Baharu"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Simpan gandingan apl"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Gandingan apl ini tidak disokong pada peranti ini"</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 93c4339..4fae68b 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"स्प्लिट स्क्रिन"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s का हकमा एपसम्बन्धी जानकारी"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s को प्रयोगसम्बन्धी सेटिङ"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"नयाँ विन्डो"</string>
<string name="save_app_pair" msgid="5647523853662686243">"एपको पेयर सेभ गर्नुहोस्"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"यस डिभाइसमा यो एप पेयर प्रयोग गर्न मिल्दैन"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index c86c58d..d2b214e 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Ecrã dividido"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informações da app para %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Definições de utilização para %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Nova janela"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Guardar par de apps"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Este par de apps não é suportado neste dispositivo"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index f10b93a..f864b3e 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Tela dividida"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informações do app %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Configurações de uso de %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Nova janela"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Salvar par de apps"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Este Par de apps não está disponível no dispositivo"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index aea3d2b..126e3ad 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Разделить экран"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Сведения о приложении \"%1$s\""</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Настройки использования приложения \"%1$s\""</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Новое окно"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Сохранить приложения"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Одновременно использовать эти два приложения на устройстве нельзя."</string>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index a3b9a71..328f2f5 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"බෙදුම් තිරය"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s සඳහා යෙදුම් තතු"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s සඳහා භාවිත සැකසීම්"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"නව කවුළුව"</string>
<string name="save_app_pair" msgid="5647523853662686243">"යෙදුම් යුගල සුරකින්න"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"මෙම යෙදුම් යුගලය මෙම උපාංගයෙහි සහාය නොදක්වයි"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 51b0c7b..37a02e4 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Razdeljen zaslon"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Podatki o aplikaciji za: %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Nastavitve uporabe za »%1$s«"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Novo okno"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Shrani par aplikacij"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Ta par aplikacij ni podprt v tej napravi"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index c93519f..afaeb61 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Подељени екран"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Информације о апликацији за: %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Подешавања потрошње за %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Нови прозор"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Сачувај пар апликација"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Овај пар апликација није подржан на овом уређају"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index c92d6f0..547f60e 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Delad skärm"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Appinformation för %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Användningsinställningar för %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Nytt fönster"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Spara app-par"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"De här apparna som ska användas tillsammans stöds inte på den här enheten"</string>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 5348a22..75dc7f0 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"స్ప్లిట్ స్క్రీన్"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s కోసం యాప్ సమాచారం"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$sకు సంబంధించిన వినియోగ సెట్టింగ్లు"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"కొత్త విండో"</string>
<string name="save_app_pair" msgid="5647523853662686243">"యాప్ పెయిర్ను సేవ్ చేయండి"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"ఈ పరికరంలో ఈ యాప్ పెయిర్ సపోర్ట్ చేయదు"</string>
diff --git a/src/com/android/launcher3/Alarm.java b/src/com/android/launcher3/Alarm.java
index fb8088c..e516ad0 100644
--- a/src/com/android/launcher3/Alarm.java
+++ b/src/com/android/launcher3/Alarm.java
@@ -20,6 +20,8 @@
import android.os.Looper;
import android.os.SystemClock;
+import androidx.annotation.VisibleForTesting;
+
public class Alarm implements Runnable{
// if we reach this time and the alarm hasn't been cancelled, call the listener
private long mAlarmTriggerTime;
@@ -96,4 +98,13 @@
public long getLastSetTimeout() {
return mLastSetTimeout;
}
+
+ /** Simulates the alarm firing for tests. */
+ @VisibleForTesting
+ public void finishAlarm() {
+ if (!mAlarmPending) return;
+ mAlarmPending = false;
+ mHandler.removeCallbacks(this);
+ mAlarmListener.onAlarm(this);
+ }
}
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 483f5f8..4eca048 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -31,6 +31,8 @@
import static com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURCE_HANDLE;
import static com.android.launcher3.testing.shared.ResourceUtils.pxFromDp;
import static com.android.launcher3.testing.shared.ResourceUtils.roundPxValueFromFloat;
+import static com.android.wm.shell.Flags.enableBubbleBar;
+import static com.android.wm.shell.Flags.enableBubbleBarInPersistentTaskBar;
import static com.android.wm.shell.Flags.enableTinyTaskbar;
import android.annotation.SuppressLint;
@@ -64,8 +66,10 @@
import com.android.launcher3.responsive.ResponsiveSpec.DimensionType;
import com.android.launcher3.responsive.ResponsiveSpecsProvider;
import com.android.launcher3.util.CellContentDimensions;
+import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.Info;
import com.android.launcher3.util.IconSizeSteps;
+import com.android.launcher3.util.NavigationMode;
import com.android.launcher3.util.ResourceHelper;
import com.android.launcher3.util.WindowBounds;
import com.android.launcher3.util.window.WindowManagerProxy;
@@ -219,6 +223,8 @@
public int hotseatBarBottomSpacePx;
public int hotseatBarEndOffset;
public int hotseatQsbSpace;
+ public int inlineNavButtonsEndSpacingPx;
+ public int navButtonsLayoutWidthPx;
public int springLoadedHotseatBarTopMarginPx;
// These 2 values are only used for isVerticalBar
// Padding between edge of screen and hotseat
@@ -233,7 +239,6 @@
private final int mMinHotseatIconSpacePx;
private final int mMinHotseatQsbWidthPx;
private final int mMaxHotseatIconSpacePx;
- public final int inlineNavButtonsEndSpacingPx;
// Space required for the bubble bar between the hotseat and the edge of the screen. If there's
// not enough space, the hotseat will adjust itself for the bubble bar.
private final int mBubbleBarSpaceThresholdPx;
@@ -692,17 +697,12 @@
if (areNavButtonsInline && !isPhone) {
inlineNavButtonsEndSpacingPx =
res.getDimensionPixelSize(inv.inlineNavButtonsEndSpacing);
- /*
- * 3 nav buttons +
- * Spacing between nav buttons +
- * Space at the end for contextual buttons
- */
- hotseatBarEndOffset = 3 * res.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
- + 2 * res.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween)
- + inlineNavButtonsEndSpacingPx;
- } else {
- inlineNavButtonsEndSpacingPx = 0;
- hotseatBarEndOffset = 0;
+ /* 3 nav buttons + Spacing between nav buttons */
+ navButtonsLayoutWidthPx = 3 * res.getDimensionPixelSize(
+ R.dimen.taskbar_nav_buttons_size)
+ + 2 * res.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween);
+ /* nav buttons layout width + Space at the end for contextual buttons */
+ hotseatBarEndOffset = navButtonsLayoutWidthPx + inlineNavButtonsEndSpacingPx;
}
mBubbleBarSpaceThresholdPx =
@@ -2214,6 +2214,10 @@
mHotseatBarEdgePaddingPx));
writer.println(prefix + pxToDpStr("mHotseatBarWorkspaceSpacePx",
mHotseatBarWorkspaceSpacePx));
+ writer.println(prefix
+ + pxToDpStr("inlineNavButtonsEndSpacingPx", inlineNavButtonsEndSpacingPx));
+ writer.println(prefix
+ + pxToDpStr("navButtonsLayoutWidthPx", navButtonsLayoutWidthPx));
writer.println(prefix + pxToDpStr("hotseatBarEndOffset", hotseatBarEndOffset));
writer.println(prefix + pxToDpStr("hotseatQsbSpace", hotseatQsbSpace));
writer.println(prefix + pxToDpStr("hotseatQsbHeight", hotseatQsbHeight));
@@ -2328,6 +2332,25 @@
}
/**
+ * Returns whether Taskbar and Hotseat should adjust horizontally on bubble bar location update.
+ */
+ public boolean shouldAdjustHotseatOnBubblesLocationUpdate(Context context) {
+ return enableBubbleBar()
+ && enableBubbleBarInPersistentTaskBar()
+ && DisplayController.getNavigationMode(context)
+ == NavigationMode.THREE_BUTTONS;
+ }
+
+ /** Returns hotseat translation X for the bubble bar position. */
+ public int getHotseatTranslationXForBubbleBar(boolean isNavbarOnRight) {
+ if (isNavbarOnRight) {
+ return 0;
+ } else {
+ return navButtonsLayoutWidthPx;
+ }
+ }
+
+ /**
* Callback when a component changes the DeviceProfile associated with it, as a result of
* configuration change
*/
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 024dde4..ae4c122 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -34,8 +34,10 @@
import android.widget.FrameLayout;
import androidx.annotation.IntDef;
+import androidx.annotation.Nullable;
import com.android.launcher3.util.HorizontalInsettableView;
+import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
import com.android.launcher3.util.MultiTranslateDelegate;
import com.android.launcher3.util.MultiValueAlpha;
@@ -61,6 +63,14 @@
public @interface HotseatQsbAlphaId {
}
+ public static final int ICONS_TRANSLATION_X_NAV_BAR_ALIGNMENT = 0;
+ public static final int ICONS_TRANSLATION_X_CHANNELS_COUNT = 1;
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @IntDef({ICONS_TRANSLATION_X_NAV_BAR_ALIGNMENT})
+ public @interface IconsTranslationX {
+ }
+
// Ratio of empty space, qsb should take up to appear visually centered.
public static final float QSB_CENTER_FACTOR = .325f;
private static final int BUBBLE_BAR_ADJUSTMENT_ANIMATION_DURATION_MS = 250;
@@ -72,6 +82,10 @@
private final MultiValueAlpha mIconsAlphaChannels;
private final MultiValueAlpha mQsbAlphaChannels;
+ private @Nullable MultiProperty mQsbTranslationX;
+
+ private final MultiPropertyFactory mIconsTranslationXFactory;
+
private final View mQsb;
public Hotseat(Context context) {
@@ -88,9 +102,26 @@
addView(mQsb);
mIconsAlphaChannels = new MultiValueAlpha(getShortcutsAndWidgets(),
ALPHA_CHANNEL_CHANNELS_COUNT);
+ if (mQsb instanceof Reorderable qsbReorderable) {
+ mQsbTranslationX = qsbReorderable.getTranslateDelegate()
+ .getTranslationX(MultiTranslateDelegate.INDEX_NAV_BAR_ANIM);
+ }
+ mIconsTranslationXFactory = new MultiPropertyFactory<>(getShortcutsAndWidgets(),
+ VIEW_TRANSLATE_X, ICONS_TRANSLATION_X_CHANNELS_COUNT, Float::sum);
mQsbAlphaChannels = new MultiValueAlpha(mQsb, ALPHA_CHANNEL_CHANNELS_COUNT);
}
+ /** Provides translation X for hotseat icons for the channel. */
+ public MultiProperty getIconsTranslationX(@IconsTranslationX int channelId) {
+ return mIconsTranslationXFactory.get(channelId);
+ }
+
+ /** Provides translation X for hotseat Qsb. */
+ @Nullable
+ public MultiProperty getQsbTranslationX() {
+ return mQsbTranslationX;
+ }
+
/**
* Returns orientation specific cell X given invariant order in the hotseat
*/
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 54aea38..5ea7bd9 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -354,7 +354,7 @@
*/
@Deprecated
public void reset(Context context) {
- initGrid(context, getCurrentGridName(context));
+ initGrid(context, getDefaultGridName(context));
}
@VisibleForTesting
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 9698d20..9192e13 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -83,8 +83,8 @@
import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
import com.android.launcher3.graphics.TintedDrawableSpan;
import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.CacheableShortcutInfo;
import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.icons.ShortcutCachingLogic;
import com.android.launcher3.icons.ThemedIconDrawable;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
@@ -645,8 +645,7 @@
if (activityInfo == null) {
return null;
}
- mainIcon = appState.getIconProvider().getIcon(
- activityInfo, appState.getInvariantDeviceProfile().fillResIconDpi);
+ mainIcon = appState.getIconCache().getFullResIcon(activityInfo.getActivityInfo());
} else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
List<ShortcutInfo> siList = ShortcutKey.fromItemInfo(info)
.buildRequest(context)
@@ -655,7 +654,7 @@
return null;
} else {
ShortcutInfo si = siList.get(0);
- mainIcon = ShortcutCachingLogic.getIcon(context, si,
+ mainIcon = CacheableShortcutInfo.getIcon(context, si,
appState.getInvariantDeviceProfile().fillResIconDpi);
// Only fetch badge if the icon is on workspace
if (info.id != ItemInfo.NO_ID && badge == null) {
diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java
index e215cab..6a40121 100644
--- a/src/com/android/launcher3/allapps/PrivateProfileManager.java
+++ b/src/com/android/launcher3/allapps/PrivateProfileManager.java
@@ -66,6 +66,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Flags;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatedPropertySetter;
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.icons.BitmapInfo;
@@ -699,7 +700,9 @@
mAllApps.mAH.get(MAIN).mRecyclerView.removeItemDecoration(
mPrivateAppsSectionDecorator);
// Call onAppsUpdated() because it may be canceled when this animation occurs.
- mAllApps.getPersonalAppList().onAppsUpdated();
+ if (!Utilities.isRunningInTestHarness()) {
+ mAllApps.getPersonalAppList().onAppsUpdated();
+ }
if (isPrivateSpaceHidden()) {
// TODO (b/325455879): Figure out if we can avoid this.
getMainRecyclerView().getAdapter().notifyDataSetChanged();
diff --git a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
index 0a50e8b..9f6b40b 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.pm.InstallSessionHelper;
import com.android.launcher3.util.DaggerSingletonTracker;
import dagger.BindsInstance;
@@ -32,6 +33,8 @@
*/
public interface LauncherBaseAppComponent {
DaggerSingletonTracker getDaggerSingletonTracker();
+ InstallSessionHelper getInstallSessionHelper();
+
/** Builder for LauncherBaseAppComponent. */
interface Builder {
@BindsInstance Builder appContext(@ApplicationContext Context context);
diff --git a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
index 0f3cad6..cc5e890 100644
--- a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
+++ b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
@@ -69,7 +69,8 @@
public PinShortcutRequestActivityInfo(
ShortcutInfo si, Supplier<PinItemRequest> requestSupplier, Context context) {
- super(new ComponentName(si.getPackage(), STUB_COMPONENT_CLASS), si.getUserHandle());
+ super(new ComponentName(si.getPackage(), STUB_COMPONENT_CLASS),
+ si.getUserHandle(), context);
mRequestSupplier = requestSupplier;
mInfo = si;
mContext = context;
diff --git a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
index 531cdfd..27ec838 100644
--- a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
+++ b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
@@ -30,14 +30,11 @@
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
-import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.Message;
import android.os.Messenger;
import android.text.TextUtils;
-import android.util.ArrayMap;
import android.util.Log;
-import android.util.Pair;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.InvariantDeviceProfile.GridOption;
@@ -47,8 +44,12 @@
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.util.RunnableList;
import com.android.systemui.shared.Flags;
+import java.util.Collections;
+import java.util.Set;
+import java.util.WeakHashMap;
import java.util.concurrent.ExecutionException;
/**
@@ -95,11 +96,9 @@
private static final int MESSAGE_ID_UPDATE_PREVIEW = 1337;
private static final int MESSAGE_ID_UPDATE_GRID = 7414;
- /**
- * Here we use the IBinder and the screen ID as the key of the active previews.
- */
- private final ArrayMap<Pair<IBinder, Integer>, PreviewLifecycleObserver> mActivePreviews =
- new ArrayMap<>();
+ // Set of all active previews used to track duplicate memory allocations
+ private final Set<PreviewLifecycleObserver> mActivePreviews =
+ Collections.newSetFromMap(new WeakHashMap<>());
@Override
public boolean onCreate() {
@@ -231,16 +230,19 @@
}
private synchronized Bundle getPreview(Bundle request) {
- PreviewLifecycleObserver observer = null;
+ RunnableList lifeCycleTracker = new RunnableList();
try {
- PreviewSurfaceRenderer renderer = new PreviewSurfaceRenderer(getContext(), request);
+ PreviewSurfaceRenderer renderer = new PreviewSurfaceRenderer(
+ getContext(), lifeCycleTracker, request);
+ PreviewLifecycleObserver observer =
+ new PreviewLifecycleObserver(lifeCycleTracker, renderer);
- observer = new PreviewLifecycleObserver(renderer);
- // Destroy previous
- destroyObserver(mActivePreviews.get(observer.getIdentifier()));
- mActivePreviews.put(observer.getIdentifier(), observer);
+ // Destroy previous renderers to avoid any duplicate memory
+ mActivePreviews.stream().filter(observer::isSameRenderer).forEach(o ->
+ MAIN_EXECUTOR.execute(o.lifeCycleTracker::executeAllAndDestroy));
renderer.loadAsync();
+ lifeCycleTracker.add(() -> renderer.getHostToken().unlinkToDeath(observer, 0));
renderer.getHostToken().linkToDeath(observer, 0);
Bundle result = new Bundle();
@@ -254,33 +256,21 @@
return result;
} catch (Exception e) {
Log.e(TAG, "Unable to generate preview", e);
- if (observer != null) {
- destroyObserver(observer);
- }
+ MAIN_EXECUTOR.execute(lifeCycleTracker::executeAllAndDestroy);
return null;
}
}
- private synchronized void destroyObserver(PreviewLifecycleObserver observer) {
- if (observer == null || observer.destroyed) {
- return;
- }
- observer.destroyed = true;
- observer.renderer.getHostToken().unlinkToDeath(observer, 0);
- MAIN_EXECUTOR.execute(observer.renderer::destroy);
- PreviewLifecycleObserver cached = mActivePreviews.get(observer.getIdentifier());
- if (cached == observer) {
- mActivePreviews.remove(observer.getIdentifier());
- }
- }
+ private static class PreviewLifecycleObserver implements Handler.Callback, DeathRecipient {
- private class PreviewLifecycleObserver implements Handler.Callback, DeathRecipient {
-
+ public final RunnableList lifeCycleTracker;
public final PreviewSurfaceRenderer renderer;
public boolean destroyed = false;
- PreviewLifecycleObserver(PreviewSurfaceRenderer renderer) {
+ PreviewLifecycleObserver(RunnableList lifeCycleTracker, PreviewSurfaceRenderer renderer) {
+ this.lifeCycleTracker = lifeCycleTracker;
this.renderer = renderer;
+ lifeCycleTracker.add(() -> destroyed = true);
}
@Override
@@ -300,7 +290,9 @@
}
break;
default:
- destroyObserver(this);
+ // Unknown command, destroy lifecycle
+ Log.d(TAG, "Unknown preview command: " + message.what + ", destroying preview");
+ MAIN_EXECUTOR.execute(lifeCycleTracker::executeAllAndDestroy);
break;
}
@@ -309,16 +301,16 @@
@Override
public void binderDied() {
- destroyObserver(this);
+ MAIN_EXECUTOR.execute(lifeCycleTracker::executeAllAndDestroy);
}
/**
- * Returns a key that should make the PreviewSurfaceRenderer unique and if two of them have
- * the same key they will be treated as the same PreviewSurfaceRenderer. Primary this is
- * used to prevent memory leaks by removing the old PreviewSurfaceRenderer.
+ * Two renderers are considered same if they have the same host token and display Id
*/
- public Pair<IBinder, Integer> getIdentifier() {
- return new Pair<>(renderer.getHostToken(), renderer.getDisplayId());
+ public boolean isSameRenderer(PreviewLifecycleObserver plo) {
+ return plo != null
+ && plo.renderer.getHostToken().equals(renderer.getHostToken())
+ && plo.renderer.getDisplayId() == renderer.getDisplayId();
}
}
}
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index 56c4ca4..1b23d75 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -91,7 +91,7 @@
private final int mDisplayId;
private final Display mDisplay;
private final WallpaperColors mWallpaperColors;
- private final RunnableList mOnDestroyCallbacks = new RunnableList();
+ private final RunnableList mLifeCycleTracker;
private final SurfaceControlViewHost mSurfaceControlViewHost;
@@ -100,8 +100,10 @@
private boolean mHideQsb;
@Nullable private FrameLayout mViewRoot = null;
- public PreviewSurfaceRenderer(Context context, Bundle bundle) throws Exception {
+ public PreviewSurfaceRenderer(
+ Context context, RunnableList lifecycleTracker, Bundle bundle) throws Exception {
mContext = context;
+ mLifeCycleTracker = lifecycleTracker;
mGridName = bundle.getString("name");
bundle.remove("name");
if (mGridName == null) {
@@ -120,11 +122,13 @@
throw new IllegalArgumentException("Display ID does not match any displays.");
}
- mSurfaceControlViewHost = MAIN_EXECUTOR.submit(() ->
- new SurfaceControlViewHost(mContext, context.getSystemService(DisplayManager.class)
- .getDisplay(DEFAULT_DISPLAY), mHostToken)
- ).get(5, TimeUnit.SECONDS);
- mOnDestroyCallbacks.add(mSurfaceControlViewHost::release);
+ mSurfaceControlViewHost = MAIN_EXECUTOR.submit(() -> new MySurfaceControlViewHost(
+ mContext,
+ context.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY),
+ mHostToken,
+ mLifeCycleTracker))
+ .get(5, TimeUnit.SECONDS);
+ mLifeCycleTracker.add(this::destroy);
}
public int getDisplayId() {
@@ -139,25 +143,18 @@
return mSurfaceControlViewHost.getSurfacePackage();
}
- /**
- * Destroys the preview and all associated data
- */
- @UiThread
- public void destroy() {
+ private void destroy() {
mDestroyed = true;
- mOnDestroyCallbacks.executeAllAndDestroy();
}
/**
* A function that queries for the launcher app widget span info
*
- * @param context The context to get the content resolver from, should be related to launcher
* @return A SparseArray with the app widget id being the key and the span info being the values
*/
@WorkerThread
@Nullable
- public SparseArray<Size> getLoadedLauncherWidgetInfo(
- @NonNull final Context context) {
+ public SparseArray<Size> getLoadedLauncherWidgetInfo() {
final SparseArray<Size> widgetInfo = new SparseArray<>();
final String query = LauncherSettings.Favorites.ITEM_TYPE + " = "
+ LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
@@ -276,13 +273,11 @@
}
loadWorkspace(new ArrayList<>(), query, null, null);
- final SparseArray<Size> spanInfo =
- getLoadedLauncherWidgetInfo(previewContext.getBaseContext());
-
+ final SparseArray<Size> spanInfo = getLoadedLauncherWidgetInfo();
MAIN_EXECUTOR.execute(() -> {
renderView(previewContext, mBgDataModel, mWidgetProvidersMap, spanInfo,
idp);
- mOnDestroyCallbacks.add(previewContext::onDestroy);
+ mLifeCycleTracker.add(previewContext::onDestroy);
});
}
}.run();
@@ -355,4 +350,24 @@
mViewRoot.addView(view);
}
}
+
+ private static class MySurfaceControlViewHost extends SurfaceControlViewHost {
+
+ private final RunnableList mLifecycleTracker;
+
+ MySurfaceControlViewHost(Context context, Display display, IBinder hostToken,
+ RunnableList lifeCycleTracker) {
+ super(context, display, hostToken);
+ mLifecycleTracker = lifeCycleTracker;
+ mLifecycleTracker.add(this::release);
+ }
+
+ @Override
+ public void release() {
+ super.release();
+ // RunnableList ensures that the callback is only called once
+ MAIN_EXECUTOR.execute(mLifecycleTracker::executeAllAndDestroy);
+ }
+ }
+
}
diff --git a/src/com/android/launcher3/icons/CacheableShortcutInfo.kt b/src/com/android/launcher3/icons/CacheableShortcutInfo.kt
new file mode 100644
index 0000000..c673bb3
--- /dev/null
+++ b/src/com/android/launcher3/icons/CacheableShortcutInfo.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.icons
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.LauncherActivityInfo
+import android.content.pm.LauncherApps
+import android.content.pm.PackageInfo
+import android.content.pm.ShortcutInfo
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import android.text.TextUtils
+import android.util.Log
+import com.android.launcher3.BuildConfig
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.icons.BaseIconFactory.IconOptions
+import com.android.launcher3.icons.cache.BaseIconCache
+import com.android.launcher3.icons.cache.CachingLogic
+import com.android.launcher3.shortcuts.ShortcutKey
+import com.android.launcher3.util.ApplicationInfoWrapper
+import com.android.launcher3.util.PackageUserKey
+import com.android.launcher3.util.Themes
+import kotlin.math.max
+
+/** Wrapper over ShortcutInfo to provide extra information related to ShortcutInfo */
+class CacheableShortcutInfo(val shortcutInfo: ShortcutInfo, val appInfo: ApplicationInfoWrapper) {
+
+ constructor(
+ info: ShortcutInfo,
+ ctx: Context,
+ ) : this(info, ApplicationInfoWrapper(ctx, info.getPackage(), info.userHandle))
+
+ companion object {
+ private const val TAG = "CacheableShortcutInfo"
+
+ /**
+ * Similar to [LauncherApps.getShortcutIconDrawable] with additional Launcher specific
+ * checks
+ */
+ @JvmStatic
+ fun getIcon(context: Context, shortcutInfo: ShortcutInfo, density: Int): Drawable? {
+ if (!BuildConfig.WIDGETS_ENABLED) {
+ return null
+ }
+ try {
+ return context
+ .getSystemService(LauncherApps::class.java)
+ .getShortcutIconDrawable(shortcutInfo, density)
+ } catch (e: Exception) {
+ Log.e(TAG, "Failed to get shortcut icon", e)
+ return null
+ }
+ }
+
+ /**
+ * Converts the provided list of Shortcuts to CacheableShortcuts by using the application
+ * info from the provided list of apps
+ */
+ @JvmStatic
+ fun convertShortcutsToCacheableShortcuts(
+ shortcuts: List<ShortcutInfo>,
+ activities: List<LauncherActivityInfo>,
+ ): List<CacheableShortcutInfo> {
+ // Create a map of package to applicationInfo
+ val appMap =
+ activities.associateBy(
+ { PackageUserKey(it.componentName.packageName, it.user) },
+ { it.applicationInfo },
+ )
+
+ return shortcuts.map {
+ CacheableShortcutInfo(
+ it,
+ ApplicationInfoWrapper(appMap[PackageUserKey(it.getPackage(), it.userHandle)]),
+ )
+ }
+ }
+ }
+}
+
+/** Caching logic for CacheableShortcutInfo. */
+object CacheableShortcutCachingLogic : CachingLogic<CacheableShortcutInfo> {
+
+ override fun getComponent(info: CacheableShortcutInfo): ComponentName =
+ ShortcutKey.fromInfo(info.shortcutInfo).componentName
+
+ override fun getUser(info: CacheableShortcutInfo): UserHandle = info.shortcutInfo.userHandle
+
+ override fun getLabel(info: CacheableShortcutInfo): CharSequence? = info.shortcutInfo.shortLabel
+
+ override fun getDescription(info: CacheableShortcutInfo, fallback: CharSequence): CharSequence =
+ info.shortcutInfo.longLabel.let { if (TextUtils.isEmpty(it)) fallback else it!! }
+
+ override fun getLastUpdatedTime(info: CacheableShortcutInfo?, packageInfo: PackageInfo) =
+ info?.let { max(info.shortcutInfo.lastChangedTimestamp, packageInfo.lastUpdateTime) }
+ ?: packageInfo.lastUpdateTime
+
+ override fun getApplicationInfo(info: CacheableShortcutInfo) = info.appInfo.getInfo()
+
+ override fun loadIcon(context: Context, cache: BaseIconCache, info: CacheableShortcutInfo) =
+ LauncherIcons.obtain(context).use { li ->
+ CacheableShortcutInfo.getIcon(
+ context,
+ info.shortcutInfo,
+ LauncherAppState.getIDP(context).fillResIconDpi,
+ )
+ ?.let { d ->
+ li.createBadgedIconBitmap(
+ d,
+ IconOptions().setExtractedColor(Themes.getColorAccent(context)),
+ )
+ } ?: BitmapInfo.LOW_RES_INFO
+ }
+}
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 587dc27..ffed1e8 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -36,7 +36,6 @@
import android.content.pm.ShortcutInfo;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
-import android.graphics.drawable.Drawable;
import android.os.Looper;
import android.os.Process;
import android.os.Trace;
@@ -66,7 +65,6 @@
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.CancellableTask;
import com.android.launcher3.util.InstantAppResolver;
import com.android.launcher3.util.PackageUserKey;
@@ -98,7 +96,6 @@
private final CachingLogic<ComponentWithLabel> mComponentWithLabelCachingLogic;
private final CachingLogic<LauncherActivityInfo> mLauncherActivityInfoCachingLogic;
- private final CachingLogic<ShortcutInfo> mShortcutCachingLogic;
private final LauncherApps mLauncherApps;
private final UserCache mUserManager;
@@ -113,10 +110,8 @@
IconProvider iconProvider) {
super(context, dbFileName, MODEL_EXECUTOR.getLooper(),
idp.fillResIconDpi, idp.iconBitmapSize, true /* inMemoryCache */, iconProvider);
- mComponentWithLabelCachingLogic = new CachedObjectCachingLogic(
- context, false /* loadIcons */, false /* addToMemCache */);
+ mComponentWithLabelCachingLogic = new CachedObjectCachingLogic(context);
mLauncherActivityInfoCachingLogic = LauncherActivityCachingLogic.INSTANCE;
- mShortcutCachingLogic = new ShortcutCachingLogic();
mLauncherApps = mContext.getSystemService(LauncherApps.class);
mUserManager = UserCache.INSTANCE.get(mContext);
mInstantAppResolver = InstantAppResolver.newInstance(mContext);
@@ -153,8 +148,7 @@
PackageManager.GET_UNINSTALLED_PACKAGES);
long userSerial = mUserManager.getSerialNumberForUser(user);
for (LauncherActivityInfo app : mLauncherApps.getActivityList(packageName, user)) {
- addIconToDBAndMemCache(app, mLauncherActivityInfoCachingLogic, info, userSerial,
- false /*replace existing*/);
+ addIconToDBAndMemCache(app, mLauncherActivityInfoCachingLogic, info, userSerial);
}
} catch (NameNotFoundException e) {
Log.d(TAG, "Package not found", e);
@@ -209,7 +203,7 @@
CancellableTask<ItemInfoWithIcon> request = new CancellableTask<>(
task, MAIN_EXECUTOR, caller::reapplyItemInfo, endRunnable);
- Utilities.postAsyncCallback(mWorkerHandler, request);
+ Utilities.postAsyncCallback(workerHandler, request);
return request;
}
@@ -224,28 +218,16 @@
* Updates {@param application} only if a valid entry is found.
*/
public synchronized void updateTitleAndIcon(AppInfo application) {
- boolean preferPackageIcon = application.isArchived();
CacheEntry entry = cacheLocked(application.componentName,
application.user, () -> null, mLauncherActivityInfoCachingLogic,
- false, application.usingLowResIcon());
- if (entry.bitmap == null || isDefaultIcon(entry.bitmap, application.user)) {
- return;
- }
-
- if (preferPackageIcon) {
- String packageName = application.getTargetPackage();
- CacheEntry packageEntry =
- cacheLocked(new ComponentName(packageName, packageName + EMPTY_CLASS_NAME),
- application.user, () -> null, mLauncherActivityInfoCachingLogic,
- true, application.usingLowResIcon());
- applyPackageEntry(packageEntry, application, entry);
- } else {
+ application.usingLowResIcon() ? LookupFlag.USE_LOW_RES : LookupFlag.DEFAULT);
+ if (entry.bitmap != null || !isDefaultIcon(entry.bitmap, application.user)) {
applyCacheEntry(entry, application);
}
}
/**
- * Fill in {@param info} with the icon and label for {@param activityInfo}
+ * Fill in {@code info} with the icon and label for {@code activityInfo}
*/
@SuppressWarnings("NewApi")
public synchronized void getTitleAndIcon(ItemInfoWithIcon info,
@@ -253,33 +235,45 @@
boolean isAppArchived = Flags.enableSupportForArchiving() && activityInfo != null
&& activityInfo.getActivityInfo().isArchived;
// If we already have activity info, no need to use package icon
- getTitleAndIcon(info, () -> activityInfo, isAppArchived, useLowResIcon,
- isAppArchived);
+ getTitleAndIcon(info, () -> activityInfo, isAppArchived, useLowResIcon);
}
/**
- * Fill in {@param info} with the icon for {@param si}
+ * Fill in {@code info} with the icon for {@code si}
*/
public void getShortcutIcon(ItemInfoWithIcon info, ShortcutInfo si) {
+ getShortcutIcon(info, new CacheableShortcutInfo(si, mContext));
+ }
+
+ /**
+ * Fill in {@code info} with the icon for {@code si}
+ */
+ public void getShortcutIcon(ItemInfoWithIcon info, CacheableShortcutInfo si) {
getShortcutIcon(info, si, mIsUsingFallbackOrNonDefaultIconCheck);
}
/**
- * Fill in {@param info} with the icon and label for {@param si}. If the icon is not
+ * Fill in {@code info} with the icon and label for {@code si}. If the icon is not
* available, and fallback check returns true, it keeps the old icon.
+ * Shortcut entries are not kept in memory since they are not frequently used
*/
- public <T extends ItemInfoWithIcon> void getShortcutIcon(T info, ShortcutInfo si,
+ public <T extends ItemInfoWithIcon> void getShortcutIcon(T info, CacheableShortcutInfo si,
@NonNull Predicate<T> fallbackIconCheck) {
- BitmapInfo bitmapInfo = cacheLocked(ShortcutKey.fromInfo(si).componentName,
- si.getUserHandle(), () -> si, mShortcutCachingLogic, false, false).bitmap;
+ UserHandle user = CacheableShortcutCachingLogic.INSTANCE.getUser(si);
+ BitmapInfo bitmapInfo = cacheLocked(
+ CacheableShortcutCachingLogic.INSTANCE.getComponent(si),
+ user,
+ () -> si,
+ CacheableShortcutCachingLogic.INSTANCE,
+ LookupFlag.SKIP_ADD_TO_MEM_CACHE).bitmap;
if (bitmapInfo.isNullOrLowRes()) {
- bitmapInfo = getDefaultIcon(si.getUserHandle());
+ bitmapInfo = getDefaultIcon(user);
}
- if (isDefaultIcon(bitmapInfo, si.getUserHandle()) && fallbackIconCheck.test(info)) {
+ if (isDefaultIcon(bitmapInfo, user) && fallbackIconCheck.test(info)) {
return;
}
- info.bitmap = bitmapInfo.withBadgeInfo(getShortcutInfoBadge(si));
+ info.bitmap = bitmapInfo.withBadgeInfo(getShortcutInfoBadge(si.getShortcutInfo()));
}
/**
@@ -333,7 +327,7 @@
} else {
Intent intent = info.getIntent();
getTitleAndIcon(info, () -> mLauncherApps.resolveActivity(intent, info.user),
- true, useLowResIcon, info.isArchived());
+ true, useLowResIcon);
}
}
@@ -342,8 +336,8 @@
*/
public synchronized String getTitleNoCache(ComponentWithLabel info) {
CacheEntry entry = cacheLocked(info.getComponent(), info.getUser(), () -> info,
- mComponentWithLabelCachingLogic, false /* usePackageIcon */,
- true /* useLowResIcon */);
+ mComponentWithLabelCachingLogic,
+ LookupFlag.USE_LOW_RES | LookupFlag.SKIP_ADD_TO_MEM_CACHE);
return Utilities.trim(entry.title);
}
@@ -354,40 +348,15 @@
@NonNull ItemInfoWithIcon infoInOut,
@NonNull Supplier<LauncherActivityInfo> activityInfoProvider,
boolean usePkgIcon, boolean useLowResIcon) {
+ int lookupFlags = LookupFlag.DEFAULT;
+ if (usePkgIcon) lookupFlags |= LookupFlag.USE_PACKAGE_ICON;
+ if (useLowResIcon) lookupFlags |= LookupFlag.USE_LOW_RES;
CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), infoInOut.user,
- activityInfoProvider, mLauncherActivityInfoCachingLogic, usePkgIcon,
- useLowResIcon);
+ activityInfoProvider, mLauncherActivityInfoCachingLogic, lookupFlags);
applyCacheEntry(entry, infoInOut);
}
/**
- * Fill in {@param mWorkspaceItemInfo} with the icon and label for {@param info}
- */
- public synchronized void getTitleAndIcon(
- @NonNull ItemInfoWithIcon infoInOut,
- @NonNull Supplier<LauncherActivityInfo> activityInfoProvider,
- boolean usePkgIcon, boolean useLowResIcon, boolean preferPackageEntry) {
- CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), infoInOut.user,
- activityInfoProvider, mLauncherActivityInfoCachingLogic, usePkgIcon,
- useLowResIcon);
- if (preferPackageEntry) {
- String packageName = infoInOut.getTargetPackage();
- CacheEntry packageEntry = cacheLocked(
- new ComponentName(packageName, packageName + EMPTY_CLASS_NAME),
- infoInOut.user, activityInfoProvider, mLauncherActivityInfoCachingLogic,
- usePkgIcon, useLowResIcon);
- applyPackageEntry(packageEntry, infoInOut, entry);
- } else if (useLowResIcon || !entry.bitmap.isNullOrLowRes()
- || infoInOut.bitmap.isNullOrLowRes()) {
- // Only use cache entry if it will not downgrade the current bitmap in infoInOut
- applyCacheEntry(entry, infoInOut);
- } else {
- Log.d(TAG, "getTitleAndIcon: Cache entry bitmap was a downgrade of existing bitmap"
- + " in ItemInfo. Skipping.");
- }
- }
-
- /**
* Creates an sql cursor for a query of a set of ItemInfoWithIcon icons and titles.
*
* @param iconRequestInfos List of IconRequestInfos representing titles and icons to query.
@@ -485,9 +454,8 @@
/* user = */ sectionKey.first,
() -> duplicateIconRequests.get(0).launcherActivityInfo,
mLauncherActivityInfoCachingLogic,
- c,
- /* usePackageIcon= */ false,
- /* useLowResIcons = */ sectionKey.second);
+ sectionKey.second ? LookupFlag.USE_LOW_RES : LookupFlag.DEFAULT,
+ c);
for (IconRequestInfo<T> iconRequest : duplicateIconRequests) {
applyCacheEntry(entry, iconRequest.itemInfo);
@@ -603,28 +571,30 @@
info.title = Utilities.trim(entry.title);
info.contentDescription = entry.contentDescription;
info.bitmap = entry.bitmap;
+ // Clear any previously set appTitle, if the packageOverride is no longer valid
+ info.appTitle = null;
if (entry.bitmap == null) {
// TODO: entry.bitmap can never be null, so this should not happen at all.
Log.wtf(TAG, "Cannot find bitmap from the cache, default icon was loaded.");
info.bitmap = getDefaultIcon(info.user);
}
- }
- protected void applyPackageEntry(@NonNull final CacheEntry packageEntry,
- @NonNull final ItemInfoWithIcon info, @NonNull final CacheEntry fallbackEntry) {
+ // apply package override
+ if (!Flags.enableSupportForArchiving() || !info.isArchived()) {
+ return;
+ }
+ String targetPackage = info.getTargetPackage();
+ if (targetPackage == null) {
+ return;
+ }
+ CacheEntry packageEntry = getInMemoryPackageEntryLocked(targetPackage, info.user);
+ if (packageEntry == null || packageEntry.bitmap.isLowRes()) {
+ return;
+ }
+ info.appTitle = Utilities.trim(info.title);
info.title = Utilities.trim(packageEntry.title);
- info.appTitle = Utilities.trim(fallbackEntry.title);
info.contentDescription = packageEntry.contentDescription;
info.bitmap = packageEntry.bitmap;
- if (packageEntry.bitmap == null) {
- // TODO: entry.bitmap can never be null, so this should not happen at all.
- Log.wtf(TAG, "Cannot find bitmap from the cache, default icon was loaded.");
- info.bitmap = getDefaultIcon(info.user);
- }
- }
-
- public Drawable getFullResIcon(LauncherActivityInfo info) {
- return mIconProvider.getIcon(info, mIconDpi);
}
public void updateSessionCache(PackageUserKey key, PackageInstaller.SessionInfo info) {
diff --git a/src/com/android/launcher3/icons/ShortcutCachingLogic.java b/src/com/android/launcher3/icons/ShortcutCachingLogic.java
deleted file mode 100644
index 7bb39e1..0000000
--- a/src/com/android/launcher3/icons/ShortcutCachingLogic.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.icons;
-
-import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.LauncherApps;
-import android.content.pm.PackageInfo;
-import android.content.pm.ShortcutInfo;
-import android.graphics.drawable.Drawable;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.icons.BaseIconFactory.IconOptions;
-import com.android.launcher3.icons.cache.BaseIconCache;
-import com.android.launcher3.icons.cache.CachingLogic;
-import com.android.launcher3.shortcuts.ShortcutKey;
-import com.android.launcher3.util.Themes;
-
-/**
- * Caching logic for shortcuts.
- */
-public class ShortcutCachingLogic implements CachingLogic<ShortcutInfo> {
-
- private static final String TAG = "ShortcutCachingLogic";
-
- @Override
- @NonNull
- public ComponentName getComponent(@NonNull ShortcutInfo info) {
- return ShortcutKey.fromInfo(info).componentName;
- }
-
- @NonNull
- @Override
- public UserHandle getUser(@NonNull ShortcutInfo info) {
- return info.getUserHandle();
- }
-
- @NonNull
- @Override
- public CharSequence getLabel(@NonNull ShortcutInfo info) {
- return info.getShortLabel();
- }
-
- @Override
- @NonNull
- public CharSequence getDescription(@NonNull ShortcutInfo object,
- @NonNull CharSequence fallback) {
- CharSequence label = object.getLongLabel();
- return TextUtils.isEmpty(label) ? fallback : label;
- }
-
- @NonNull
- @Override
- public BitmapInfo loadIcon(@NonNull Context context, @NonNull BaseIconCache cache,
- @NonNull ShortcutInfo info) {
- try (LauncherIcons li = LauncherIcons.obtain(context)) {
- Drawable unbadgedDrawable = ShortcutCachingLogic.getIcon(
- context, info, LauncherAppState.getIDP(context).fillResIconDpi);
- if (unbadgedDrawable == null) return BitmapInfo.LOW_RES_INFO;
- return li.createBadgedIconBitmap(unbadgedDrawable,
- new IconOptions().setExtractedColor(Themes.getColorAccent(context)));
- }
- }
-
- @Override
- public long getLastUpdatedTime(@Nullable ShortcutInfo shortcutInfo,
- @NonNull PackageInfo info) {
- if (shortcutInfo == null) {
- return info.lastUpdateTime;
- }
- return Math.max(shortcutInfo.getLastChangedTimestamp(), info.lastUpdateTime);
- }
-
- @Override
- public boolean addToMemCache() {
- return false;
- }
-
- /**
- * Similar to {@link LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)} with additional
- * Launcher specific checks
- */
- public static Drawable getIcon(Context context, ShortcutInfo shortcutInfo, int density) {
- if (!WIDGETS_ENABLED) {
- return null;
- }
- try {
- return context.getSystemService(LauncherApps.class)
- .getShortcutIconDrawable(shortcutInfo, density);
- } catch (SecurityException | IllegalStateException | NullPointerException e) {
- Log.e(TAG, "Failed to get shortcut icon", e);
- return null;
- }
- }
-}
diff --git a/src/com/android/launcher3/model/GridSizeMigrationUtil.java b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
index 942b97c..4c017e9 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationUtil.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
@@ -17,6 +17,7 @@
package com.android.launcher3.model;
import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
+import static com.android.launcher3.LauncherPrefs.IS_FIRST_LOAD_AFTER_RESTORE;
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.LauncherSettings.Favorites.TMP_TABLE;
import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
@@ -129,7 +130,8 @@
return true;
}
- if (Flags.enableGridMigrationFix()
+ if (LauncherPrefs.get(context).get(IS_FIRST_LOAD_AFTER_RESTORE)
+ && Flags.enableGridMigrationFix()
&& srcDeviceState.getColumns().equals(destDeviceState.getColumns())
&& srcDeviceState.getRows() < destDeviceState.getRows()) {
Log.i("b/360462379", "Grid migration fix entry point.");
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 609846f..dff5463 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -23,6 +23,7 @@
import static com.android.launcher3.LauncherPrefs.IS_FIRST_LOAD_AFTER_RESTORE;
import static com.android.launcher3.LauncherPrefs.SHOULD_SHOW_SMARTSPACE;
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
+import static com.android.launcher3.icons.CacheableShortcutInfo.convertShortcutsToCacheableShortcuts;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
@@ -69,9 +70,10 @@
import com.android.launcher3.folder.FolderGridOrganizer;
import com.android.launcher3.folder.FolderNameInfos;
import com.android.launcher3.folder.FolderNameProvider;
+import com.android.launcher3.icons.CacheableShortcutCachingLogic;
+import com.android.launcher3.icons.CacheableShortcutInfo;
import com.android.launcher3.icons.ComponentWithLabelAndIcon;
import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.icons.ShortcutCachingLogic;
import com.android.launcher3.icons.cache.CachedObjectCachingLogic;
import com.android.launcher3.icons.cache.IconCacheUpdateHandler;
import com.android.launcher3.icons.cache.LauncherActivityCachingLogic;
@@ -246,7 +248,7 @@
}
try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
- List<ShortcutInfo> allShortcuts = new ArrayList<>();
+ List<CacheableShortcutInfo> allShortcuts = new ArrayList<>();
loadWorkspace(allShortcuts, "", memoryLogger, restoreEventLogger);
// Sanitize data re-syncs widgets/shortcuts based on the workspace loaded from db.
@@ -304,7 +306,7 @@
verifyNotStopped();
logASplit("save shortcuts in icon cache");
- updateHandler.updateIcons(allShortcuts, new ShortcutCachingLogic(),
+ updateHandler.updateIcons(allShortcuts, CacheableShortcutCachingLogic.INSTANCE,
mApp.getModel()::onPackageIconsUpdated);
// Take a break
@@ -322,8 +324,10 @@
verifyNotStopped();
logASplit("save deep shortcuts in icon cache");
- updateHandler.updateIcons(allDeepShortcuts,
- new ShortcutCachingLogic(), (pkgs, user) -> { });
+ updateHandler.updateIcons(
+ convertShortcutsToCacheableShortcuts(allDeepShortcuts, allActivityList),
+ CacheableShortcutCachingLogic.INSTANCE,
+ (pkgs, user) -> { });
// Take a break
waitForIdle();
@@ -397,7 +401,7 @@
}
protected void loadWorkspace(
- List<ShortcutInfo> allDeepShortcuts,
+ List<CacheableShortcutInfo> allDeepShortcuts,
String selection,
LoaderMemoryLogger memoryLogger,
@Nullable LauncherRestoreEventLogger restoreEventLogger
@@ -423,7 +427,7 @@
}
private void loadWorkspaceImpl(
- List<ShortcutInfo> allDeepShortcuts,
+ List<CacheableShortcutInfo> allDeepShortcuts,
String selection,
@Nullable LoaderMemoryLogger memoryLogger,
@Nullable LauncherRestoreEventLogger restoreEventLogger) {
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index 55c4d30..b5a7382 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -24,6 +24,7 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel.ModelUpdateTask;
import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.icons.CacheableShortcutInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.shortcuts.ShortcutRequest;
@@ -79,9 +80,9 @@
}
if (!matchingWorkspaceItems.isEmpty()) {
+ ApplicationInfoWrapper infoWrapper =
+ new ApplicationInfoWrapper(context, mPackageName, mUser);
if (mShortcuts.isEmpty()) {
- ApplicationInfoWrapper infoWrapper =
- new ApplicationInfoWrapper(context, mPackageName, mUser);
// Verify that the app is indeed installed.
if (!infoWrapper.isInstalled() && !infoWrapper.isArchived()) {
// App is not installed or archived, ignoring package events
@@ -103,7 +104,6 @@
if (!fullDetails.isPinned()) {
continue;
}
-
String sid = fullDetails.getId();
nonPinnedIds.remove(sid);
matchingWorkspaceItems
@@ -111,7 +111,8 @@
.filter(itemInfo -> sid.equals(itemInfo.getDeepShortcutId()))
.forEach(workspaceItemInfo -> {
workspaceItemInfo.updateFromDeepShortcutInfo(fullDetails, context);
- app.getIconCache().getShortcutIcon(workspaceItemInfo, fullDetails);
+ app.getIconCache().getShortcutIcon(workspaceItemInfo,
+ new CacheableShortcutInfo(fullDetails, infoWrapper));
updatedWorkspaceItemInfos.add(workspaceItemInfo);
});
}
diff --git a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
index 18c7f95..c02336e 100644
--- a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
+++ b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
@@ -32,6 +32,7 @@
import com.android.launcher3.LauncherSettings.Favorites
import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError
import com.android.launcher3.config.FeatureFlags
+import com.android.launcher3.icons.CacheableShortcutInfo
import com.android.launcher3.logging.FileLog
import com.android.launcher3.model.data.AppInfo
import com.android.launcher3.model.data.AppPairInfo
@@ -76,7 +77,7 @@
private val pmHelper: PackageManagerHelper,
private val iconRequestInfos: MutableList<IconRequestInfo<WorkspaceItemInfo>>,
private val unlockedUsers: LongSparseArray<Boolean>,
- private val allDeepShortcuts: MutableList<ShortcutInfo>,
+ private val allDeepShortcuts: MutableList<CacheableShortcutInfo>,
) {
private val isSafeMode = app.isSafeModeEnabled
@@ -278,13 +279,14 @@
info = WorkspaceItemInfo(pinnedShortcut, app.context)
// If the pinned deep shortcut is no longer published,
// use the last saved icon instead of the default.
- iconCache.getShortcutIcon(info, pinnedShortcut, c::loadIcon)
+ val csi = CacheableShortcutInfo(pinnedShortcut, appInfoWrapper)
+ iconCache.getShortcutIcon(info, csi, c::loadIcon)
if (appInfoWrapper.isSuspended()) {
info.runtimeStatusFlags =
info.runtimeStatusFlags or ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED
}
intent = info.getIntent()
- allDeepShortcuts.add(pinnedShortcut)
+ allDeepShortcuts.add(csi)
} else {
// Create a shortcut info in disabled mode for now.
info = c.loadSimpleWorkspaceItem()
diff --git a/src/com/android/launcher3/pm/InstallSessionHelper.java b/src/com/android/launcher3/pm/InstallSessionHelper.java
index 124907f..f36f595 100644
--- a/src/com/android/launcher3/pm/InstallSessionHelper.java
+++ b/src/com/android/launcher3/pm/InstallSessionHelper.java
@@ -31,12 +31,17 @@
import com.android.launcher3.Flags;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.SessionCommitReceiver;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.dagger.LauncherBaseAppComponent;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.ItemInstallQueue;
import com.android.launcher3.util.ApplicationInfoWrapper;
+import com.android.launcher3.util.DaggerSingletonObject;
+import com.android.launcher3.util.DaggerSingletonTracker;
+import com.android.launcher3.util.ExecutorUtil;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
-import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.SafeCloseable;
@@ -47,10 +52,13 @@
import java.util.List;
import java.util.Objects;
+import javax.inject.Inject;
+
/**
* Utility class to tracking install sessions
*/
@SuppressWarnings("NewApi")
+@LauncherAppSingleton
public class InstallSessionHelper implements SafeCloseable {
@NonNull
@@ -64,8 +72,8 @@
private static final boolean DEBUG = false;
@NonNull
- public static final MainThreadInitializedObject<InstallSessionHelper> INSTANCE =
- new MainThreadInitializedObject<>(InstallSessionHelper::new);
+ public static final DaggerSingletonObject<InstallSessionHelper> INSTANCE =
+ new DaggerSingletonObject<>(LauncherBaseAppComponent::getInstallSessionHelper);
@Nullable
private final LauncherApps mLauncherApps;
@@ -82,10 +90,13 @@
@Nullable
private IntSet mPromiseIconIds;
- public InstallSessionHelper(@NonNull final Context context) {
+ @Inject
+ public InstallSessionHelper(@NonNull @ApplicationContext final Context context,
+ DaggerSingletonTracker tracker) {
mInstaller = context.getPackageManager().getPackageInstaller();
mAppContext = context.getApplicationContext();
mLauncherApps = context.getSystemService(LauncherApps.class);
+ ExecutorUtil.executeSyncOnMainOrFail(() -> tracker.addCloseable(this));
}
@Override
diff --git a/src/com/android/launcher3/pm/PinRequestHelper.java b/src/com/android/launcher3/pm/PinRequestHelper.java
index 47afeef..2ed6591 100644
--- a/src/com/android/launcher3/pm/PinRequestHelper.java
+++ b/src/com/android/launcher3/pm/PinRequestHelper.java
@@ -32,7 +32,8 @@
import androidx.annotation.Nullable;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.icons.ShortcutCachingLogic;
+import com.android.launcher3.icons.CacheableShortcutCachingLogic;
+import com.android.launcher3.icons.CacheableShortcutInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
public class PinRequestHelper {
@@ -78,7 +79,8 @@
// Apply the unbadged icon synchronously using the caching logic directly and
// fetch the actual icon asynchronously.
LauncherAppState app = LauncherAppState.getInstance(context);
- info.bitmap = new ShortcutCachingLogic().loadIcon(context, app.getIconCache(), si);
+ info.bitmap = CacheableShortcutCachingLogic.INSTANCE.loadIcon(
+ context, app.getIconCache(), new CacheableShortcutInfo(si, context));
app.getModel().updateAndBindWorkspaceItem(info, si);
return info;
} else {
diff --git a/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java b/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java
index 351ebce..3064abf 100644
--- a/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java
+++ b/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java
@@ -26,6 +26,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
+import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
@@ -43,6 +44,7 @@
import com.android.launcher3.icons.ComponentWithLabelAndIcon;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.util.ApplicationInfoWrapper;
import com.android.launcher3.util.PackageUserKey;
import java.util.ArrayList;
@@ -58,10 +60,20 @@
private final ComponentName mCn;
private final UserHandle mUser;
+ private final ApplicationInfoWrapper mInfoWrapper;
- protected ShortcutConfigActivityInfo(ComponentName cn, UserHandle user) {
+ protected ShortcutConfigActivityInfo(
+ ComponentName cn, UserHandle user, ApplicationInfoWrapper infoWrapper) {
mCn = cn;
mUser = user;
+ mInfoWrapper = infoWrapper;
+ }
+
+ protected ShortcutConfigActivityInfo(
+ ComponentName cn, UserHandle user, Context context) {
+ mCn = cn;
+ mUser = user;
+ mInfoWrapper = new ApplicationInfoWrapper(context, cn.getPackageName(), user);
}
@Override
@@ -89,6 +101,12 @@
return null;
}
+ @Nullable
+ @Override
+ public ApplicationInfo getApplicationInfo() {
+ return mInfoWrapper.getInfo();
+ }
+
public boolean startConfigActivity(Activity activity, int requestCode) {
Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT)
.setComponent(getComponent());
@@ -120,7 +138,8 @@
private final LauncherActivityInfo mInfo;
public ShortcutConfigActivityInfoVO(LauncherActivityInfo info) {
- super(info.getComponentName(), info.getUser());
+ super(info.getComponentName(), info.getUser(),
+ new ApplicationInfoWrapper(info.getApplicationInfo()));
mInfo = info;
}
@@ -131,7 +150,7 @@
@Override
public Drawable getFullResIcon(IconCache cache) {
- return cache.getFullResIcon(mInfo);
+ return cache.getFullResIcon(mInfo.getActivityInfo());
}
@Override
diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java
index aa24f60..755c3eb 100644
--- a/src/com/android/launcher3/popup/PopupPopulator.java
+++ b/src/com/android/launcher3/popup/PopupPopulator.java
@@ -27,11 +27,13 @@
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.icons.CacheableShortcutInfo;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.shortcuts.ShortcutRequest;
+import com.android.launcher3.util.ApplicationInfoWrapper;
import com.android.launcher3.views.ActivityContext;
import java.util.ArrayList;
@@ -113,6 +115,8 @@
final ComponentName activity = originalInfo.getTargetComponent();
final UserHandle user = originalInfo.user;
return () -> {
+ ApplicationInfoWrapper infoWrapper =
+ new ApplicationInfoWrapper(context, activity.getPackageName(), user);
List<ShortcutInfo> shortcuts = new ShortcutRequest(context, user)
.withContainer(activity)
.query(ShortcutRequest.PUBLISHED);
@@ -121,7 +125,7 @@
for (int i = 0; i < shortcuts.size() && i < shortcutViews.size(); i++) {
final ShortcutInfo shortcut = shortcuts.get(i);
final WorkspaceItemInfo si = new WorkspaceItemInfo(shortcut, context);
- cache.getShortcutIcon(si, shortcut);
+ cache.getShortcutIcon(si, new CacheableShortcutInfo(shortcut, infoWrapper));
si.rank = i;
si.container = CONTAINER_SHORTCUTS;
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 21897bf..775d248 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -75,7 +75,6 @@
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.LogConfig;
-import java.io.File;
import java.io.InvalidObjectException;
import java.util.Arrays;
import java.util.Collection;
@@ -127,12 +126,12 @@
if (Flags.enableNarrowGridRestore()) {
String oldPhoneFileName = idp.dbFile;
- List<String> previousDbs = existingDbs();
+ List<String> previousDbs = existingDbs(context);
removeOldDBs(context, oldPhoneFileName);
// The idp before this contains data about the old phone, after this it becomes the idp
// of the current phone.
idp.reset(context);
- trySettingPreviousGidAsCurrent(context, idp, oldPhoneFileName, previousDbs);
+ trySettingPreviousGridAsCurrent(context, idp, oldPhoneFileName, previousDbs);
} else {
idp.reinitializeAfterRestore(context);
}
@@ -143,7 +142,7 @@
* Try setting the gird used in the previous phone to the new one. If the current device doesn't
* support the previous grid option it will not be set.
*/
- private static void trySettingPreviousGidAsCurrent(Context context, InvariantDeviceProfile idp,
+ private static void trySettingPreviousGridAsCurrent(Context context, InvariantDeviceProfile idp,
String oldPhoneDbFileName, List<String> previousDbs) {
InvariantDeviceProfile.GridOption oldPhoneGridOption = idp.getGridOptionFromFileName(
context, oldPhoneDbFileName);
@@ -166,17 +165,19 @@
/**
* Returns a list of paths of the existing launcher dbs.
*/
- private static List<String> existingDbs() {
+ @VisibleForTesting
+ public static List<String> existingDbs(Context context) {
// At this point idp.dbFile contains the name of the dbFile from the previous phone
return LauncherFiles.GRID_DB_FILES.stream()
- .filter(dbName -> new File(dbName).exists())
+ .filter(dbName -> context.getDatabasePath(dbName).exists())
.toList();
}
/**
* Only keep the last database used on the previous device.
*/
- private static void removeOldDBs(Context context, String oldPhoneDbFileName) {
+ @VisibleForTesting
+ public static void removeOldDBs(Context context, String oldPhoneDbFileName) {
// At this point idp.dbFile contains the name of the dbFile from the previous phone
LauncherFiles.GRID_DB_FILES.stream()
.filter(dbName -> !dbName.equals(oldPhoneDbFileName))
diff --git a/src/com/android/launcher3/statemanager/BaseState.java b/src/com/android/launcher3/statemanager/BaseState.java
index b81729a..f6b610c 100644
--- a/src/com/android/launcher3/statemanager/BaseState.java
+++ b/src/com/android/launcher3/statemanager/BaseState.java
@@ -72,6 +72,13 @@
}
/**
+ * For this state, whether fullscreen and desktop quickswitch carousel are detached.
+ */
+ default boolean detachDesktopCarousel() {
+ return true;
+ }
+
+ /**
* For this state, whether member variables and other forms of data state should be preserved
* or wiped when the state is reapplied. (See {@link StateManager#reapplyState()})
*/
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index efd1f0d..74a0966 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -407,10 +407,7 @@
}
private void updateContextualEduStats(LauncherState targetState) {
- if (targetState == NORMAL) {
- ContextualEduStatsManager.INSTANCE.get(
- mLauncher).updateEduStats(mDetector.isTrackpadGesture(), GestureType.HOME);
- } else if (targetState == OVERVIEW) {
+ if (targetState == OVERVIEW) {
ContextualEduStatsManager.INSTANCE.get(
mLauncher).updateEduStats(mDetector.isTrackpadGesture(), GestureType.OVERVIEW);
} else if (targetState == ALL_APPS && !mDetector.isTrackpadGesture()) {
diff --git a/src/com/android/launcher3/util/MainThreadInitializedObject.java b/src/com/android/launcher3/util/MainThreadInitializedObject.java
index e12ccbc..a7d5c13 100644
--- a/src/com/android/launcher3/util/MainThreadInitializedObject.java
+++ b/src/com/android/launcher3/util/MainThreadInitializedObject.java
@@ -108,6 +108,25 @@
*/
<T extends SafeCloseable> T getObject(MainThreadInitializedObject<T> object);
+
+ /**
+ * Put a value into cache, can be used to put mocked MainThreadInitializedObject
+ * instances.
+ */
+ <T extends SafeCloseable> void putObject(MainThreadInitializedObject<T> object, T value);
+
+ /**
+ * Returns whether this context should cleanup all objects when its destroyed or leave it
+ * to the GC.
+ * These objects can have listeners attached to the system server and mey not be able to get
+ * GCed themselves when running on a device.
+ * Some environments like Robolectric tear down the whole system at the end of the test,
+ * so manual cleanup may not be required.
+ */
+ default boolean shouldCleanUpOnDestroy() {
+ return true;
+ }
+
@UiThread
default <T extends SafeCloseable> T createObject(MainThreadInitializedObject<T> object) {
return object.mProvider.get((Context) this);
@@ -138,7 +157,19 @@
return this;
}
+ @Override
+ public boolean shouldCleanUpOnDestroy() {
+ return (getBaseContext().getApplicationContext() instanceof SandboxApplication sa)
+ ? sa.shouldCleanUpOnDestroy() : true;
+ }
+
public void onDestroy() {
+ if (shouldCleanUpOnDestroy()) {
+ cleanUpObjects();
+ }
+ }
+
+ protected void cleanUpObjects() {
getAppComponent().getDaggerSingletonTracker().close();
synchronized (mDestroyLock) {
// Destroy in reverse order
@@ -174,10 +205,7 @@
}
}
- /**
- * Put a value into mObjectMap, can be used to put mocked MainThreadInitializedObject
- * instances into SandboxContext.
- */
+ @Override
public <T extends SafeCloseable> void putObject(
MainThreadInitializedObject<T> object, T value) {
mObjectMap.put(object, value);
diff --git a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
index ab42839..e100157 100644
--- a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
+++ b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
@@ -213,8 +213,8 @@
// Draw icon in the center.
try {
- Drawable icon = LauncherAppState.getInstance(mContext).getIconCache()
- .getFullResIcon(info.provider.getPackageName(), info.icon);
+ Drawable icon = info.getFullResIcon(
+ LauncherAppState.getInstance(mContext).getIconCache());
if (icon != null) {
int appIconSize = dp.iconSizePx;
int iconSize = (int) Math.min(appIconSize * scale,
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
index e77ba24..1db3b5a 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
@@ -3,6 +3,7 @@
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Point;
import android.graphics.Rect;
@@ -10,6 +11,8 @@
import android.os.Parcel;
import android.os.UserHandle;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
@@ -223,6 +226,12 @@
@Override
public Drawable getFullResIcon(IconCache cache) {
- return cache.getFullResIcon(provider.getPackageName(), icon);
+ return cache.getFullResIcon(getActivityInfo());
+ }
+
+ @Nullable
+ @Override
+ public ApplicationInfo getApplicationInfo() {
+ return getActivityInfo().applicationInfo;
}
}
\ No newline at end of file
diff --git a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java
index 2d96cbd..3008d18 100644
--- a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java
+++ b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java
@@ -37,8 +37,7 @@
* Controller for a search bar with an edit text and a cancel button.
*/
public class WidgetsSearchBarController implements TextWatcher,
- SearchCallback<WidgetsListBaseEntry>, ExtendedEditText.OnBackKeyListener,
- View.OnKeyListener {
+ SearchCallback<WidgetsListBaseEntry>, View.OnKeyListener {
private static final String TAG = "WidgetsSearchBarController";
private static final boolean DEBUG = false;
@@ -54,7 +53,6 @@
mSearchAlgorithm = algo;
mInput = editText;
mInput.addTextChangedListener(this);
- mInput.setOnBackKeyListener(this);
mInput.setOnKeyListener(this);
mCancelButton = cancelButton;
mCancelButton.setOnClickListener(v -> clearSearchResult());
@@ -108,12 +106,6 @@
}
@Override
- public boolean onBackKey() {
- clearFocus();
- return true;
- }
-
- @Override
public boolean onKey(View view, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_UP) {
clearFocus();
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index 4b926a8..68e493d 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -183,6 +183,7 @@
</activity>
<activity-alias android:name="Activity2"
android:label="TestActivity2"
+ android:icon="@drawable/test_icon"
android:exported="true"
android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
<intent-filter>
@@ -192,6 +193,7 @@
</activity-alias>
<activity-alias android:name="Activity3"
android:label="TestActivity3"
+ android:icon="@drawable/test_icon"
android:exported="true"
android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
<intent-filter>
@@ -201,6 +203,7 @@
</activity-alias>
<activity-alias android:name="Activity4"
android:label="TestActivity4"
+ android:icon="@drawable/test_icon"
android:exported="true"
android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
<intent-filter>
@@ -210,6 +213,7 @@
</activity-alias>
<activity-alias android:name="Activity5"
android:label="TestActivity5"
+ android:icon="@drawable/test_icon"
android:exported="true"
android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
<intent-filter>
@@ -219,6 +223,7 @@
</activity-alias>
<activity-alias android:name="Activity6"
android:label="TestActivity6"
+ android:icon="@drawable/test_icon"
android:exported="true"
android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
<intent-filter>
@@ -228,6 +233,7 @@
</activity-alias>
<activity-alias android:name="Activity7"
android:label="TestActivity7"
+ android:icon="@drawable/test_icon"
android:exported="true"
android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
<intent-filter>
@@ -237,6 +243,7 @@
</activity-alias>
<activity-alias android:name="Activity8"
android:label="TestActivity8"
+ android:icon="@drawable/test_icon"
android:exported="true"
android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
<intent-filter>
@@ -246,6 +253,7 @@
</activity-alias>
<activity-alias android:name="Activity9" android:exported="true"
android:label="TestActivity9"
+ android:icon="@drawable/test_icon"
android:targetActivity="com.android.launcher3.testcomponent.OtherBaseTestingActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -254,6 +262,7 @@
</activity-alias>
<activity-alias android:name="Activity10" android:exported="true"
android:label="TestActivity10"
+ android:icon="@drawable/test_icon"
android:targetActivity="com.android.launcher3.testcomponent.OtherBaseTestingActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -262,6 +271,7 @@
</activity-alias>
<activity-alias android:name="Activity11" android:exported="true"
android:label="TestActivity11"
+ android:icon="@drawable/test_icon"
android:targetActivity="com.android.launcher3.testcomponent.OtherBaseTestingActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -270,6 +280,7 @@
</activity-alias>
<activity-alias android:name="Activity12" android:exported="true"
android:label="TestActivity12"
+ android:icon="@drawable/test_icon"
android:targetActivity="com.android.launcher3.testcomponent.OtherBaseTestingActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -278,6 +289,7 @@
</activity-alias>
<activity-alias android:name="Activity13" android:exported="true"
android:label="TestActivity13"
+ android:icon="@drawable/test_icon"
android:targetActivity="com.android.launcher3.testcomponent.OtherBaseTestingActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -286,6 +298,7 @@
</activity-alias>
<activity-alias android:name="Activity14" android:exported="true"
android:label="TestActivity14"
+ android:icon="@drawable/test_icon"
android:targetActivity="com.android.launcher3.testcomponent.OtherBaseTestingActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -363,7 +376,7 @@
</activity>
<activity android:name="com.android.launcher3.testcomponent.ImeTestActivity"
android:label="ImeTestActivity"
- android:icon="@drawable/test_theme_icon"
+ android:icon="@drawable/test_icon"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -407,6 +420,36 @@
</intent-filter>
</activity>
+ <activity-alias android:name="AppIconActivity"
+ android:label="Application Icon"
+ android:exported="true"
+ android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity-alias>
+ <activity-alias android:name="DiffIconActivity"
+ android:label="Different icon"
+ android:exported="true"
+ android:icon="@drawable/test_different_activity_icon"
+ android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity-alias>
+ <activity-alias android:name="WrongIconActivity"
+ android:label="Wrong icon"
+ android:exported="true"
+ android:icon="@drawable/test_wrong_activity_icon"
+ android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity-alias>
+
<!-- Disable eager initialization of Jetpack libraries. See bug 197780098. -->
<provider
android:name="androidx.startup.InitializationProvider"
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait.txt
index 82a6310..4c366c3 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait.txt
@@ -77,6 +77,8 @@
hotseatBarBottomSpacePx: 126.0px (48.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
mHotseatBarWorkspaceSpacePx: 0.0px (0.0dp)
+ inlineNavButtonsEndSpacingPx: 0.0px (0.0dp)
+ navButtonsLayoutWidthPx: 0.0px (0.0dp)
hotseatBarEndOffset: 0.0px (0.0dp)
hotseatQsbSpace: 0.0px (0.0dp)
hotseatQsbHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait3Button.txt
index 4271105..6db9534 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait3Button.txt
@@ -77,6 +77,8 @@
hotseatBarBottomSpacePx: 147.0px (56.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
mHotseatBarWorkspaceSpacePx: 0.0px (0.0dp)
+ inlineNavButtonsEndSpacingPx: 0.0px (0.0dp)
+ navButtonsLayoutWidthPx: 0.0px (0.0dp)
hotseatBarEndOffset: 0.0px (0.0dp)
hotseatQsbSpace: 0.0px (0.0dp)
hotseatQsbHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar.txt
index 8bd6b99..6e76b13 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar.txt
@@ -77,6 +77,8 @@
hotseatBarBottomSpacePx: 0.0px (0.0dp)
mHotseatBarEdgePaddingPx: 63.0px (24.0dp)
mHotseatBarWorkspaceSpacePx: 42.0px (16.0dp)
+ inlineNavButtonsEndSpacingPx: 0.0px (0.0dp)
+ navButtonsLayoutWidthPx: 0.0px (0.0dp)
hotseatBarEndOffset: 0.0px (0.0dp)
hotseatQsbSpace: 0.0px (0.0dp)
hotseatQsbHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar3Button.txt
index 8dbb413..1af9215 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar3Button.txt
@@ -77,6 +77,8 @@
hotseatBarBottomSpacePx: 0.0px (0.0dp)
mHotseatBarEdgePaddingPx: 63.0px (24.0dp)
mHotseatBarWorkspaceSpacePx: 42.0px (16.0dp)
+ inlineNavButtonsEndSpacingPx: 0.0px (0.0dp)
+ navButtonsLayoutWidthPx: 0.0px (0.0dp)
hotseatBarEndOffset: 0.0px (0.0dp)
hotseatQsbSpace: 0.0px (0.0dp)
hotseatQsbHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt
index ab4b286..958597f 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt
@@ -77,6 +77,8 @@
hotseatBarBottomSpacePx: 80.0px (40.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
mHotseatBarWorkspaceSpacePx: 0.0px (0.0dp)
+ inlineNavButtonsEndSpacingPx: 0.0px (0.0dp)
+ navButtonsLayoutWidthPx: 0.0px (0.0dp)
hotseatBarEndOffset: 0.0px (0.0dp)
hotseatQsbSpace: 0.0px (0.0dp)
hotseatQsbHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt
index 80835bc..aad67b4 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt
@@ -77,6 +77,8 @@
hotseatBarBottomSpacePx: 80.0px (40.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
mHotseatBarWorkspaceSpacePx: 0.0px (0.0dp)
+ inlineNavButtonsEndSpacingPx: 0.0px (0.0dp)
+ navButtonsLayoutWidthPx: 0.0px (0.0dp)
hotseatBarEndOffset: 0.0px (0.0dp)
hotseatQsbSpace: 0.0px (0.0dp)
hotseatQsbHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt
index fc53107..090e54b 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt
@@ -77,6 +77,8 @@
hotseatBarBottomSpacePx: 152.0px (76.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
mHotseatBarWorkspaceSpacePx: 0.0px (0.0dp)
+ inlineNavButtonsEndSpacingPx: 0.0px (0.0dp)
+ navButtonsLayoutWidthPx: 0.0px (0.0dp)
hotseatBarEndOffset: 0.0px (0.0dp)
hotseatQsbSpace: 0.0px (0.0dp)
hotseatQsbHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt
index 836819f..43b1a65 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt
@@ -77,6 +77,8 @@
hotseatBarBottomSpacePx: 152.0px (76.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
mHotseatBarWorkspaceSpacePx: 0.0px (0.0dp)
+ inlineNavButtonsEndSpacingPx: 0.0px (0.0dp)
+ navButtonsLayoutWidthPx: 0.0px (0.0dp)
hotseatBarEndOffset: 0.0px (0.0dp)
hotseatQsbSpace: 0.0px (0.0dp)
hotseatQsbHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape.txt
index 108182f..fe5737e 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape.txt
@@ -77,6 +77,8 @@
hotseatBarBottomSpacePx: 126.0px (48.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
mHotseatBarWorkspaceSpacePx: 0.0px (0.0dp)
+ inlineNavButtonsEndSpacingPx: 0.0px (0.0dp)
+ navButtonsLayoutWidthPx: 0.0px (0.0dp)
hotseatBarEndOffset: 0.0px (0.0dp)
hotseatQsbSpace: 0.0px (0.0dp)
hotseatQsbHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button.txt
index 313d2a3..36e47a0 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button.txt
@@ -77,6 +77,8 @@
hotseatBarBottomSpacePx: 126.0px (48.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
mHotseatBarWorkspaceSpacePx: 0.0px (0.0dp)
+ inlineNavButtonsEndSpacingPx: 0.0px (0.0dp)
+ navButtonsLayoutWidthPx: 0.0px (0.0dp)
hotseatBarEndOffset: 0.0px (0.0dp)
hotseatQsbSpace: 0.0px (0.0dp)
hotseatQsbHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button_decoupleDepth.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button_decoupleDepth.txt
index 46cce24..52fea05 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button_decoupleDepth.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button_decoupleDepth.txt
@@ -77,6 +77,8 @@
hotseatBarBottomSpacePx: 126.0px (48.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
mHotseatBarWorkspaceSpacePx: 0.0px (0.0dp)
+ inlineNavButtonsEndSpacingPx: 0.0px (0.0dp)
+ navButtonsLayoutWidthPx: 0.0px (0.0dp)
hotseatBarEndOffset: 0.0px (0.0dp)
hotseatQsbSpace: 0.0px (0.0dp)
hotseatQsbHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape_decoupleDepth.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape_decoupleDepth.txt
index 44b99e9..6d972a8 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape_decoupleDepth.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape_decoupleDepth.txt
@@ -77,6 +77,8 @@
hotseatBarBottomSpacePx: 126.0px (48.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
mHotseatBarWorkspaceSpacePx: 0.0px (0.0dp)
+ inlineNavButtonsEndSpacingPx: 0.0px (0.0dp)
+ navButtonsLayoutWidthPx: 0.0px (0.0dp)
hotseatBarEndOffset: 0.0px (0.0dp)
hotseatQsbSpace: 0.0px (0.0dp)
hotseatQsbHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt
index fb392a8..417353d 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt
@@ -77,6 +77,8 @@
hotseatBarBottomSpacePx: 126.0px (48.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
mHotseatBarWorkspaceSpacePx: 0.0px (0.0dp)
+ inlineNavButtonsEndSpacingPx: 0.0px (0.0dp)
+ navButtonsLayoutWidthPx: 0.0px (0.0dp)
hotseatBarEndOffset: 0.0px (0.0dp)
hotseatQsbSpace: 0.0px (0.0dp)
hotseatQsbHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt
index 2c4b3c3..03dc23a 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt
@@ -77,6 +77,8 @@
hotseatBarBottomSpacePx: 126.0px (48.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
mHotseatBarWorkspaceSpacePx: 0.0px (0.0dp)
+ inlineNavButtonsEndSpacingPx: 0.0px (0.0dp)
+ navButtonsLayoutWidthPx: 0.0px (0.0dp)
hotseatBarEndOffset: 0.0px (0.0dp)
hotseatQsbSpace: 0.0px (0.0dp)
hotseatQsbHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button_decoupleDepth.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button_decoupleDepth.txt
index e7b72f2..45d3171 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button_decoupleDepth.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button_decoupleDepth.txt
@@ -77,6 +77,8 @@
hotseatBarBottomSpacePx: 126.0px (48.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
mHotseatBarWorkspaceSpacePx: 0.0px (0.0dp)
+ inlineNavButtonsEndSpacingPx: 0.0px (0.0dp)
+ navButtonsLayoutWidthPx: 0.0px (0.0dp)
hotseatBarEndOffset: 0.0px (0.0dp)
hotseatQsbSpace: 0.0px (0.0dp)
hotseatQsbHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait_decoupleDepth.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait_decoupleDepth.txt
index eae50f1..55322d6 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait_decoupleDepth.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait_decoupleDepth.txt
@@ -77,6 +77,8 @@
hotseatBarBottomSpacePx: 126.0px (48.0dp)
mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
mHotseatBarWorkspaceSpacePx: 0.0px (0.0dp)
+ inlineNavButtonsEndSpacingPx: 0.0px (0.0dp)
+ navButtonsLayoutWidthPx: 0.0px (0.0dp)
hotseatBarEndOffset: 0.0px (0.0dp)
hotseatQsbSpace: 0.0px (0.0dp)
hotseatQsbHeight: 0.0px (0.0dp)
diff --git a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java
index d7dd40b..4e9143e 100644
--- a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java
@@ -167,10 +167,7 @@
public static final String PERMANENT_DIAG_TAG = "TaplTarget";
public static final String ICON_MISSING = "b/282963545";
- public static final String UIOBJECT_STALE_ELEMENT = "b/319501259";
- public static final String TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE = "b/326908466";
public static final String WIDGET_CONFIG_NULL_EXTRA_INTENT = "b/324419890";
-
public static final String REQUEST_FLAG_ENABLE_GRID_ONLY_OVERVIEW = "enable-grid-only-overview";
public static final String REQUEST_FLAG_ENABLE_APP_PAIRS = "enable-app-pairs";
diff --git a/tests/multivalentTests/src/com/android/launcher3/RoboObjectInitializer.kt b/tests/multivalentTests/src/com/android/launcher3/RoboObjectInitializer.kt
deleted file mode 100644
index c5f9f86..0000000
--- a/tests/multivalentTests/src/com/android/launcher3/RoboObjectInitializer.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3
-
-import com.android.launcher3.util.MainThreadInitializedObject
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxApplication
-import com.android.launcher3.util.SafeCloseable
-
-/**
- * Initializes [MainThreadInitializedObject] instances for Robolectric tests.
- *
- * Unlike instrumentation tests, Robolectric creates a new application instance for each test, which
- * could cause the various static objects defined in [MainThreadInitializedObject] to leak. Thus, a
- * [SandboxApplication] for Robolectric tests can implement this interface to limit the lifecycle of
- * these objects to a single test.
- */
-interface RoboObjectInitializer {
-
- /** Overrides an object with [type] to [value]. */
- fun <T : SafeCloseable> initializeObject(type: MainThreadInitializedObject<T>, value: T)
-}
diff --git a/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheTest.java b/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheTest.java
index 495d583..1f0e750 100644
--- a/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheTest.java
@@ -15,21 +15,39 @@
*/
package com.android.launcher3.icons;
+import static android.os.Process.myUserHandle;
+
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.android.launcher3.icons.IconCache.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE;
+import static com.android.launcher3.icons.IconCacheUpdateHandlerTestKt.waitForUpdateHandlerToFinish;
+import static com.android.launcher3.model.data.AppInfo.makeLaunchIntent;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY;
+import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
+import static com.android.launcher3.util.TestUtil.runOnExecutorSync;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutInfo.Builder;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.drawable.Icon;
import android.os.PersistableBundle;
+import android.os.UserHandle;
import android.text.TextUtils;
import androidx.annotation.Nullable;
@@ -37,15 +55,30 @@
import androidx.test.filters.SmallTest;
import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.icons.cache.CachingLogic;
+import com.android.launcher3.icons.cache.IconCacheUpdateHandler;
+import com.android.launcher3.icons.cache.LauncherActivityCachingLogic;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.PackageItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.settings.SettingsActivity;
+import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.util.ApplicationInfoWrapper;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.util.RoboApiWrapper;
+
+import com.google.common.truth.Truth;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
public class IconCacheTest {
@@ -112,6 +145,102 @@
assertEquals(((PackageItemInfo) item).packageName, otherPackage);
}
+ @Test
+ public void launcherActivityInfo_cached_in_memory() {
+ RoboApiWrapper.INSTANCE.initialize();
+ ComponentName cn = new ComponentName(TEST_PACKAGE, TEST_ACTIVITY);
+ UserHandle user = myUserHandle();
+ ComponentKey cacheKey = new ComponentKey(cn, user);
+
+ LauncherActivityInfo lai = mContext.getSystemService(LauncherApps.class)
+ .resolveActivity(makeLaunchIntent(cn), user);
+ assertNotNull(lai);
+
+ WorkspaceItemInfo info = new WorkspaceItemInfo();
+ info.intent = makeLaunchIntent(cn);
+ runOnExecutorSync(MODEL_EXECUTOR,
+ () -> mIconCache.getTitleAndIcon(info, lai, false));
+ assertNotNull(info.bitmap);
+ assertFalse(info.bitmap.isLowRes());
+
+ // Verify that icon is in memory cache
+ runOnExecutorSync(MODEL_EXECUTOR,
+ () -> assertNotNull(mIconCache.getInMemoryEntryLocked(cacheKey)));
+
+ // Schedule async update and wait for it to complete
+ Set<PackageUserKey> updates =
+ executeIconUpdate(lai, LauncherActivityCachingLogic.INSTANCE);
+
+ // Verify that the icon was not updated and is still in memory cache
+ Truth.assertThat(updates).isEmpty();
+ runOnExecutorSync(MODEL_EXECUTOR,
+ () -> assertNotNull(mIconCache.getInMemoryEntryLocked(cacheKey)));
+ }
+
+ @Test
+ public void shortcutInfo_not_cached_in_memory() {
+ CacheableShortcutInfo si = mockShortcutInfo(0);
+ ShortcutKey cacheKey = ShortcutKey.fromInfo(si.getShortcutInfo());
+
+ WorkspaceItemInfo info = new WorkspaceItemInfo();
+ runOnExecutorSync(MODEL_EXECUTOR, () -> mIconCache.getShortcutIcon(info, si));
+ assertNotNull(info.bitmap);
+ assertFalse(info.bitmap.isLowRes());
+
+ // Verify that icon is in memory cache
+ runOnExecutorSync(MODEL_EXECUTOR,
+ () -> assertNull(mIconCache.getInMemoryEntryLocked(cacheKey)));
+
+ Set<PackageUserKey> updates =
+ executeIconUpdate(si, CacheableShortcutCachingLogic.INSTANCE);
+ // Verify that the icon was not updated and is still in memory cache
+ Truth.assertThat(updates).isEmpty();
+ runOnExecutorSync(MODEL_EXECUTOR,
+ () -> assertNull(mIconCache.getInMemoryEntryLocked(cacheKey)));
+
+ // Now update the shortcut with a newer version
+ updates = executeIconUpdate(
+ mockShortcutInfo(System.currentTimeMillis() + 2000),
+ CacheableShortcutCachingLogic.INSTANCE);
+
+ // Verify that icon was updated but it is still not in mem-cache
+ Truth.assertThat(updates).containsExactly(
+ new PackageUserKey(cacheKey.getPackageName(), cacheKey.user));
+ runOnExecutorSync(MODEL_EXECUTOR,
+ () -> assertNull(mIconCache.getInMemoryEntryLocked(cacheKey)));
+ }
+
+ /**
+ * Executes the icon update for the provided entry and returns the updated packages
+ */
+ private <T> Set<PackageUserKey> executeIconUpdate(T object, CachingLogic<T> cachingLogic) {
+ HashSet<PackageUserKey> updates = new HashSet<>();
+
+ runOnExecutorSync(MODEL_EXECUTOR, () -> {
+ IconCacheUpdateHandler updateHandler = mIconCache.getUpdateHandler();
+ updateHandler.updateIcons(
+ Collections.singletonList(object),
+ cachingLogic,
+ (a, b) -> a.forEach(p -> updates.add(new PackageUserKey(p, b))));
+ updateHandler.finish();
+ });
+ waitForUpdateHandlerToFinish(mIconCache);
+ return updates;
+ }
+
+ private CacheableShortcutInfo mockShortcutInfo(long updateTime) {
+ ShortcutInfo info = new ShortcutInfo.Builder(
+ getInstrumentation().getContext(), "test-shortcut")
+ .setIntent(new Intent(Intent.ACTION_VIEW))
+ .setShortLabel("Test")
+ .setIcon(Icon.createWithBitmap(Bitmap.createBitmap(200, 200, Config.ARGB_8888)))
+ .build();
+ ShortcutInfo spied = spy(info);
+ doReturn(updateTime).when(spied).getLastChangedTimestamp();
+ return new CacheableShortcutInfo(spied,
+ new ApplicationInfoWrapper(getInstrumentation().getContext().getApplicationInfo()));
+ }
+
private ItemInfoWithIcon getBadgingInfo(Context context,
@Nullable ComponentName cn, @Nullable String badgeOverride) throws Exception {
Builder builder = new Builder(context, "test-shortcut")
diff --git a/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheUpdateHandlerTest.kt b/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheUpdateHandlerTest.kt
index e27926f..b54636c 100644
--- a/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheUpdateHandlerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheUpdateHandlerTest.kt
@@ -25,6 +25,8 @@
import com.android.launcher3.icons.cache.BaseIconCache
import com.android.launcher3.icons.cache.CachingLogic
import com.android.launcher3.icons.cache.IconCacheUpdateHandler
+import com.android.launcher3.util.RoboApiWrapper
+import java.util.concurrent.FutureTask
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -51,7 +53,7 @@
System.currentTimeMillis(),
1,
1.0.toLong(),
- "stateOfConfusion"
+ "stateOfConfusion",
)
@Before
@@ -81,17 +83,32 @@
componentMap,
ignorePackages,
user,
- cachingLogic
+ cachingLogic,
)
assert(result == null)
}
}
+/** Utility method to wait for the icon update handler to finish */
+fun IconCache.waitForUpdateHandlerToFinish() {
+ var cacheUpdateInProgress = true
+ while (cacheUpdateInProgress) {
+ val cacheCheck = FutureTask {
+ // Check for pending message on the worker thread itself as some task may be
+ // running currently
+ workerHandler.hasMessages(0, iconUpdateToken)
+ }
+ workerHandler.postDelayed(cacheCheck, 10)
+ RoboApiWrapper.waitForLooperSync(workerHandler.looper)
+ cacheUpdateInProgress = cacheCheck.get()
+ }
+}
+
data class IconCacheRowData(
val component: String,
val lastUpdated: Long,
val version: Int,
val row: Long,
- val systemState: String
+ val systemState: String,
)
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt
index d002493..4ca47e3 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt
@@ -18,16 +18,16 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.launcher3.LauncherAppState
+import com.android.launcher3.icons.BitmapInfo
+import com.android.launcher3.icons.waitForUpdateHandlerToFinish
import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.util.Executors
import com.android.launcher3.util.LauncherLayoutBuilder
import com.android.launcher3.util.LauncherModelHelper
import com.android.launcher3.util.LauncherModelHelper.*
-import com.android.launcher3.util.RoboApiWrapper
import com.android.launcher3.util.TestUtil
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
-import java.util.concurrent.CountDownLatch
import org.junit.After
import org.junit.Before
import org.junit.Rule
@@ -58,7 +58,7 @@
TEST_ACTIVITY11,
TEST_ACTIVITY12,
TEST_ACTIVITY13,
- TEST_ACTIVITY14
+ TEST_ACTIVITY14,
)
@Before
@@ -146,14 +146,9 @@
// The first load initializes the DB, load again so that icons are now used from the DB
// Wait for the icon cache to be updated and then reload
val app = LauncherAppState.getInstance(modelHelper.sandboxContext)
- val cache = app.iconCache
- while (cache.isIconUpdateInProgress) {
- val wait = CountDownLatch(1)
- Executors.MODEL_EXECUTOR.handler.postDelayed({ wait.countDown() }, 10)
- RoboApiWrapper.waitForLooperSync(Executors.MODEL_EXECUTOR.handler.looper)
- wait.await()
- }
- TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) { cache.clearMemoryCache() }
+ app.iconCache.waitForUpdateHandlerToFinish()
+
+ TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) { app.iconCache.clearMemoryCache() }
// Reload again with correct icon state
app.model.forceReload()
modelHelper.loadModelSync()
@@ -169,6 +164,9 @@
assertWithMessage("Index $index was not highRes")
.that(items[index].bitmap.isNullOrLowRes)
.isFalse()
+ assertWithMessage("Index $index was the default icon")
+ .that(isDefaultIcon(items[index].bitmap))
+ .isFalse()
}
}
@@ -177,9 +175,17 @@
assertWithMessage("Index $index was not lowRes")
.that(items[index].bitmap.isNullOrLowRes)
.isTrue()
+ assertWithMessage("Index $index was the default icon")
+ .that(isDefaultIcon(items[index].bitmap))
+ .isFalse()
}
}
+ private fun isDefaultIcon(bitmap: BitmapInfo) =
+ LauncherAppState.getInstance(modelHelper.sandboxContext)
+ .iconCache
+ .isDefaultIcon(bitmap, modelHelper.sandboxContext.user)
+
/** Recreate DeviceProfiles after changing InvariantDeviceProfile */
private fun recreateSupportedDeviceProfiles() {
LauncherAppState.getIDP(modelHelper.sandboxContext).supportedProfiles =
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
index 7529ba9..c7abce6 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
@@ -40,6 +40,7 @@
import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError.Companion.MISSING_INFO
import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError.Companion.MISSING_WIDGET_PROVIDER
import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError.Companion.PROFILE_DELETED
+import com.android.launcher3.icons.CacheableShortcutInfo
import com.android.launcher3.model.data.FolderInfo
import com.android.launcher3.model.data.IconRequestInfo
import com.android.launcher3.model.data.ItemInfo
@@ -97,7 +98,7 @@
private var mUnlockedUsersArray: LongSparseArray<Boolean> = LongSparseArray()
private var mKeyToPinnedShortcutsMap: MutableMap<ShortcutKey, ShortcutInfo> = mutableMapOf()
private var mInstallingPkgs: HashMap<PackageUserKey, PackageInstaller.SessionInfo> = hashMapOf()
- private var mAllDeepShortcuts: MutableList<ShortcutInfo> = mutableListOf()
+ private var mAllDeepShortcuts: MutableList<CacheableShortcutInfo> = mutableListOf()
private var mWidgetProvidersMap: MutableMap<ComponentKey, AppWidgetProviderInfo?> =
mutableMapOf()
private var mPendingPackages: MutableSet<PackageUserKey> = mutableSetOf()
@@ -194,7 +195,7 @@
pendingPackages: MutableSet<PackageUserKey> = mPendingPackages,
unlockedUsers: LongSparseArray<Boolean> = mUnlockedUsersArray,
installingPkgs: HashMap<PackageUserKey, PackageInstaller.SessionInfo> = mInstallingPkgs,
- allDeepShortcuts: MutableList<ShortcutInfo> = mAllDeepShortcuts,
+ allDeepShortcuts: MutableList<CacheableShortcutInfo> = mAllDeepShortcuts,
) =
WorkspaceItemProcessor(
c = cursor,
@@ -387,7 +388,8 @@
.that(mockCursor.restoreFlag)
.isEqualTo(0)
assertThat(mIconRequestInfos).isEmpty()
- assertThat(mAllDeepShortcuts).containsExactly(expectedShortcutInfo)
+ assertThat(mAllDeepShortcuts.size).isEqualTo(1)
+ assertThat(mAllDeepShortcuts[0].shortcutInfo).isEqualTo(expectedShortcutInfo)
verify(mockCursor).markRestored()
verify(mockCursor).checkAndAddItem(any(), any(), anyOrNull())
}
@@ -452,7 +454,8 @@
.that(mockCursor.restoreFlag)
.isEqualTo(0)
assertThat(mIconRequestInfos).isEmpty()
- assertThat(mAllDeepShortcuts).containsExactly(expectedShortcutInfo)
+ assertThat(mAllDeepShortcuts.size).isEqualTo(1)
+ assertThat(mAllDeepShortcuts[0].shortcutInfo).isEqualTo(expectedShortcutInfo)
verify(mockCursor).markRestored()
verify(mockCursor).checkAndAddItem(any(), any(), anyOrNull())
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java b/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
index 2d53e29..748d376 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -22,6 +22,7 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.TestUtil.runOnExecutorSync;
+import static com.android.launcher3.util.TestUtil.grantWriteSecurePermission;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -185,6 +186,8 @@
*/
public LauncherModelHelper setupDefaultLayoutProvider(LauncherLayoutBuilder builder)
throws Exception {
+ grantWriteSecurePermission();
+
InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(sandboxContext);
if (idp.numRows == 0 && idp.numColumns == 0) {
idp.numRows = idp.numColumns = idp.numDatabaseHotseatIcons = DEFAULT_GRID_SIZE;
@@ -283,11 +286,11 @@
}
@Override
- public void onDestroy() {
+ protected void cleanUpObjects() {
if (deleteContents(mDbDir)) {
mDbDir.delete();
}
- super.onDestroy();
+ super.cleanUpObjects();
}
@Override
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/TestUtil.java b/tests/multivalentTests/src/com/android/launcher3/util/TestUtil.java
index 3646f0c..64035da 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/TestUtil.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/TestUtil.java
@@ -26,6 +26,7 @@
import static org.junit.Assert.assertTrue;
+import android.Manifest;
import android.app.Instrumentation;
import android.app.blob.BlobHandle;
import android.app.blob.BlobStoreManager;
@@ -169,6 +170,8 @@
}
String key = Base64.encodeToString(digest, NO_WRAP | NO_PADDING);
+
+ grantWriteSecurePermission();
Settings.Secure.putString(context.getContentResolver(), LAYOUT_DIGEST_KEY, key);
wait.await();
return () ->
@@ -224,6 +227,14 @@
assertTrue(message, failed);
}
+ /**
+ * Grants [WRITE_SECURE_SETTINGS] permission in runtime.
+ */
+ public static void grantWriteSecurePermission() {
+ getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity(Manifest.permission.WRITE_SECURE_SETTINGS);
+ }
+
/** Interface to indicate a runnable which can throw any exception. */
public interface UncheckedRunnable {
/** Method to run the task */
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 b2cb266..7adb2b1 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
@@ -296,7 +296,7 @@
private final class TestShortcutConfigActivityInfo extends ShortcutConfigActivityInfo {
TestShortcutConfigActivityInfo(ComponentName componentName, UserHandle user) {
- super(componentName, user);
+ super(componentName, user, mContext);
}
@Override
diff --git a/tests/res/drawable/test_app_info_icon.xml b/tests/res/drawable/test_app_info_icon.xml
new file mode 100644
index 0000000..2e824ac
--- /dev/null
+++ b/tests/res/drawable/test_app_info_icon.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+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.
+-->
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@android:color/white"/>
+ <foreground>
+ <color android:color="#FFFF0000" />
+ </foreground>
+</adaptive-icon>
diff --git a/tests/res/drawable/test_different_activity_icon.xml b/tests/res/drawable/test_different_activity_icon.xml
new file mode 100644
index 0000000..43d3611
--- /dev/null
+++ b/tests/res/drawable/test_different_activity_icon.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+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.
+-->
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@android:color/white"/>
+ <foreground>
+ <color android:color="#FFFFFF00" />
+ </foreground>
+</adaptive-icon>
diff --git a/tests/res/drawable/test_icon.xml b/tests/res/drawable/test_icon.xml
new file mode 100644
index 0000000..72ebfeb
--- /dev/null
+++ b/tests/res/drawable/test_icon.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+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.
+-->
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@android:color/white"/>
+ <foreground>
+ <color android:color="#FFFF0000" />
+ </foreground>
+ <monochrome>
+ <vector android:width="48dp" android:height="48dp" android:viewportWidth="48.0" android:viewportHeight="48.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M0,24L48,24 48,48, 0,48 Z"/>
+ </vector>
+ </monochrome>
+</adaptive-icon>
diff --git a/tests/res/drawable/test_wrong_activity_icon.xml b/tests/res/drawable/test_wrong_activity_icon.xml
new file mode 100644
index 0000000..c3ae9f0
--- /dev/null
+++ b/tests/res/drawable/test_wrong_activity_icon.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+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.
+-->
+<wrong-adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@android:color/white"/>
+ <foreground>
+ <color android:color="#FFFF0000" />
+ </foreground>
+</wrong-adaptive-icon>
diff --git a/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt b/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt
index 479b201..35ac0a1 100644
--- a/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt
+++ b/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt
@@ -23,6 +23,7 @@
import com.android.launcher3.Flags
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.model.ModelDbController
+import com.android.launcher3.provider.RestoreDbTask
import com.android.launcher3.util.Executors.MODEL_EXECUTOR
import com.android.launcher3.util.TestUtil
import com.android.launcher3.util.rule.BackAndRestoreRule
@@ -67,4 +68,13 @@
}
}
}
+
+ @Test
+ fun testExistingDbsAndRemovingDbs() {
+ var existingDbs = RestoreDbTask.existingDbs(getInstrumentation().targetContext)
+ assert(existingDbs.size == 4)
+ RestoreDbTask.removeOldDBs(getInstrumentation().targetContext, "launcher_4_by_4.db")
+ existingDbs = RestoreDbTask.existingDbs(getInstrumentation().targetContext)
+ assert(existingDbs.size == 1)
+ }
}
diff --git a/tests/src/com/android/launcher3/dragging/TaplDragTest.java b/tests/src/com/android/launcher3/dragging/TaplDragTest.java
index 8fe77ac..59e1f99 100644
--- a/tests/src/com/android/launcher3/dragging/TaplDragTest.java
+++ b/tests/src/com/android/launcher3/dragging/TaplDragTest.java
@@ -15,7 +15,6 @@
*/
package com.android.launcher3.dragging;
-import static com.android.launcher3.testing.shared.TestProtocol.TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE;
import static com.android.launcher3.util.TestConstants.AppNames.GMAIL_APP_NAME;
import static com.android.launcher3.util.TestConstants.AppNames.MAPS_APP_NAME;
import static com.android.launcher3.util.TestConstants.AppNames.PHOTOS_APP_NAME;
@@ -229,11 +228,6 @@
final HomeAppIcon launcherTestAppIcon = createShortcutInCenterIfNotExist(TEST_APP_NAME);
for (Point target : targets) {
startTime = SystemClock.uptimeMillis();
- Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
- "TaplDragTest.java.testDragAppIconToMultipleWorkspaceCells: shortcut name: "
- + launcherTestAppIcon.getIconName()
- + " | target cell coordinates: (" + target.x + ", " + target.y
- + ") | start time: " + startTime);
launcherTestAppIcon.dragToWorkspace(target.x, target.y);
endTime = SystemClock.uptimeMillis();
elapsedTime = endTime - startTime;
diff --git a/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
index 7c87c65..44b8ff8 100644
--- a/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
+++ b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
@@ -16,14 +16,11 @@
package com.android.launcher3.dragging;
import static com.android.launcher3.testing.shared.TestProtocol.ICON_MISSING;
-import static com.android.launcher3.testing.shared.TestProtocol.UIOBJECT_STALE_ELEMENT;
import static com.android.launcher3.util.TestConstants.AppNames.DUMMY_APP_NAME;
import static com.android.launcher3.util.TestConstants.AppNames.GMAIL_APP_NAME;
import static com.android.launcher3.util.TestConstants.AppNames.MAPS_APP_NAME;
import static com.android.launcher3.util.TestConstants.AppNames.STORE_APP_NAME;
import static com.android.launcher3.util.TestConstants.AppNames.TEST_APP_NAME;
-import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
-import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
import static com.google.common.truth.Truth.assertThat;
@@ -40,7 +37,6 @@
import com.android.launcher3.util.TestUtil;
import com.android.launcher3.util.Wait;
import com.android.launcher3.util.rule.ScreenRecordRule;
-import com.android.launcher3.util.rule.TestStabilityRule;
import org.junit.Test;
@@ -150,7 +146,6 @@
0, Math.min(gridPositions.length, appNameCandidates.length));
for (int i = 0; i < appNames.length; ++i) {
- Log.d(UIOBJECT_STALE_ELEMENT, "creatingShortcut for: " + appNames[i]);
createShortcutIfNotExist(appNames[i], gridPositions[i]);
}
diff --git a/tests/src/com/android/launcher3/icons/IconProviderTest.kt b/tests/src/com/android/launcher3/icons/IconProviderTest.kt
new file mode 100644
index 0000000..5517fce
--- /dev/null
+++ b/tests/src/com/android/launcher3/icons/IconProviderTest.kt
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.icons
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.LauncherActivityInfo
+import android.content.pm.LauncherApps
+import android.content.pm.PackageItemInfo
+import android.content.pm.PackageManager
+import android.graphics.drawable.AdaptiveIconDrawable
+import android.graphics.drawable.Drawable
+import android.os.Parcel
+import android.os.Parcelable.Creator
+import android.os.Process
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.model.data.AppInfo
+import com.android.launcher3.widget.WidgetManagerHelper
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Tests for IconProvider */
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class IconProviderTest {
+
+ lateinit var context: Context
+ lateinit var pm: PackageManager
+ lateinit var iconProvider: IconProvider
+
+ lateinit var testContext: Context
+
+ @Before
+ fun setup() {
+ context = InstrumentationRegistry.getInstrumentation().targetContext
+ pm = context.packageManager
+ iconProvider = IconProvider(context)
+
+ testContext = InstrumentationRegistry.getInstrumentation().context
+ }
+
+ @Test
+ fun launcherActivityInfo_activity_icon() {
+ val icon = iconProvider.getIcon(getLauncherActivityInfo(DiffIconActivity).activityInfo)
+ assertNotNull(icon)
+ verifyIconResName(icon, ICON_DIFFERENT_ACTIVITY)
+ }
+
+ @Test
+ fun packageActivityInfo_activity_icon() {
+ val icon = iconProvider.getIcon(getPackageActivityInfo(DiffIconActivity))
+ assertNotNull(icon)
+ verifyIconResName(icon, ICON_DIFFERENT_ACTIVITY)
+ }
+
+ @Test
+ fun launcherActivityInfo_wrong_icon() {
+ val ai =
+ getLauncherActivityInfo(WrongIconActivity)
+ .activityInfo
+ .overrideAppIcon(ActivityInfo.CREATOR)
+ assertEquals(ai.icon.toResourceName(), ICON_WRONG_DRAWABLE)
+ val icon = iconProvider.getIcon(ai)
+ assertNotNull(icon)
+ // App icon is loaded if the drawable is not found
+ verifyIconResName(icon, ICON_APP_INFO)
+ }
+
+ @Test
+ fun packageActivityInfo_wrong_icon() {
+ val ai = getPackageActivityInfo(WrongIconActivity)
+ assertEquals(ai.icon.toResourceName(), ICON_WRONG_DRAWABLE)
+ assertNotEquals(ai.icon, 0)
+ val icon = iconProvider.getIcon(ai)
+ assertNotNull(icon)
+ // App icon is loaded if the drawable is not found
+ verifyIconResName(icon, ICON_APP_INFO)
+ }
+
+ @Test
+ fun launcherActivityInfo_fallback_to_icon() {
+ val ai =
+ getLauncherActivityInfo(AppIconActivity)
+ .activityInfo
+ .overrideAppIcon(ActivityInfo.CREATOR)
+ assertEquals(ai.icon, 0)
+ val icon = iconProvider.getIcon(ai)
+ assertNotNull(icon)
+ // App icon is loaded if component icon is not defined
+ verifyIconResName(icon, ICON_APP_INFO)
+ }
+
+ @Test
+ fun packageActivityInfo_fallback_to_icon() {
+ val ai = getPackageActivityInfo(AppIconActivity)
+ assertEquals(ai.icon, 0)
+ val icon = iconProvider.getIcon(ai)
+ assertNotNull(icon)
+ // App icon is loaded if component icon is not defined
+ verifyIconResName(icon, ICON_APP_INFO)
+ }
+
+ @Test
+ fun applicationInfo_icon() {
+ val appInfo =
+ getLauncherActivityInfo(AppIconActivity)
+ .applicationInfo
+ .overrideAppIcon(ApplicationInfo.CREATOR)
+ val icon = iconProvider.getIcon(appInfo)
+ assertNotNull(icon)
+ verifyIconResName(icon, ICON_APP_INFO)
+ }
+
+ @Test
+ fun applicationInfo_wrong_icon() {
+ val appInfo =
+ getLauncherActivityInfo(AppIconActivity)
+ .applicationInfo
+ .overrideAppIcon(ApplicationInfo.CREATOR)
+ appInfo.icon = 0
+
+ val icon = iconProvider.getIcon(appInfo)
+ assertNotNull(icon)
+ // Fallback is loaded if the drawable is defined
+ assertTrue(pm.isDefaultApplicationIcon(icon))
+ }
+
+ @Test
+ fun appwidgetProviderInfo_icon() {
+ val widgetInfo =
+ WidgetManagerHelper(context)
+ .findProvider(ComponentName(testContext, AppWidgetNoConfig), Process.myUserHandle())
+ assertNotNull(widgetInfo)
+
+ val icon = iconProvider.getIcon(widgetInfo.activityInfo)
+ assertNotNull(icon)
+ verifyIconResName(icon, ICON_WIDGET_NO_CONFIG)
+ }
+
+ private fun verifyIconResName(icon: Drawable, resName: String) {
+ assertTrue(icon is AdaptiveIconDrawable)
+ assertEquals(resName, (icon as AdaptiveIconDrawable).sourceDrawableResId.toResourceName())
+ }
+
+ private fun Int.toResourceName() = testContext.resources.getResourceEntryName(this)
+
+ private fun getLauncherActivityInfo(className: String): LauncherActivityInfo =
+ context
+ .getSystemService(LauncherApps::class.java)!!
+ .resolveActivity(getActivityIntent(className), Process.myUserHandle())
+
+ private fun getPackageActivityInfo(className: String): ActivityInfo =
+ pm.resolveActivity(getActivityIntent(className), 0)!!
+ .activityInfo
+ .overrideAppIcon(ActivityInfo.CREATOR)
+
+ private fun <T : PackageItemInfo> PackageItemInfo.overrideAppIcon(creator: Creator<T>): T {
+ // Clone the obj since it may have been cached by the system
+ val p = Parcel.obtain()
+ writeToParcel(p, 0)
+ p.setDataPosition(0)
+ val result = creator.createFromParcel(p)
+ p.recycle()
+ result.applicationInfo.icon =
+ testContext.resources.getIdentifier(ICON_APP_INFO, "drawable", testContext.packageName)
+ return result
+ }
+
+ private fun getActivityIntent(className: String) =
+ AppInfo.makeLaunchIntent(ComponentName(testContext, className))
+
+ companion object {
+ private const val AppIconActivity = "com.android.launcher3.tests.AppIconActivity"
+ private const val DiffIconActivity = "com.android.launcher3.tests.DiffIconActivity"
+ private const val WrongIconActivity = "com.android.launcher3.tests.WrongIconActivity"
+ private const val AppWidgetNoConfig =
+ "com.android.launcher3.testcomponent.AppWidgetNoConfig"
+
+ private const val ICON_DIFFERENT_ACTIVITY = "test_different_activity_icon"
+ private const val ICON_APP_INFO = "test_app_info_icon"
+ private const val ICON_WRONG_DRAWABLE = "test_wrong_activity_icon"
+ private const val ICON_WIDGET_NO_CONFIG = "test_widget_no_config_icon"
+ }
+}
diff --git a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
index d16674c..0dd13a9 100644
--- a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
+++ b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
@@ -22,6 +22,7 @@
import com.android.launcher3.util.Executors.MODEL_EXECUTOR
import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
import com.android.launcher3.util.LooperIdleLock
+import com.android.launcher3.util.TestUtil
import com.android.launcher3.util.UserIconInfo
import com.google.common.truth.Truth
import java.util.concurrent.CountDownLatch
@@ -116,6 +117,8 @@
`when`(idleLock.awaitLocked(1000)).thenReturn(false)
`when`(iconCache.updateHandler).thenReturn(iconCacheUpdateHandler)
context.putObject(UserCache.INSTANCE, userCache)
+
+ TestUtil.grantWriteSecurePermission()
}
@After
diff --git a/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt b/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt
index f1b6271..d553f47 100644
--- a/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt
+++ b/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt
@@ -35,6 +35,7 @@
import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
+import com.android.launcher3.icons.CacheableShortcutInfo
import com.android.launcher3.model.data.IconRequestInfo
import com.android.launcher3.model.data.LauncherAppWidgetInfo
import com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_RESTORE_STARTED
@@ -84,7 +85,7 @@
private var mUnlockedUsersArray: LongSparseArray<Boolean> = LongSparseArray()
private var mKeyToPinnedShortcutsMap: MutableMap<ShortcutKey, ShortcutInfo> = mutableMapOf()
private var mInstallingPkgs: HashMap<PackageUserKey, PackageInstaller.SessionInfo> = hashMapOf()
- private var mAllDeepShortcuts: MutableList<ShortcutInfo> = mutableListOf()
+ private var mAllDeepShortcuts: MutableList<CacheableShortcutInfo> = mutableListOf()
private var mWidgetProvidersMap: MutableMap<ComponentKey, AppWidgetProviderInfo?> =
mutableMapOf()
private var mPendingPackages: MutableSet<PackageUserKey> = mutableSetOf()
@@ -290,7 +291,7 @@
pendingPackages: MutableSet<PackageUserKey> = mPendingPackages,
unlockedUsers: LongSparseArray<Boolean> = mUnlockedUsersArray,
installingPkgs: HashMap<PackageUserKey, PackageInstaller.SessionInfo> = mInstallingPkgs,
- allDeepShortcuts: MutableList<ShortcutInfo> = mAllDeepShortcuts,
+ allDeepShortcuts: MutableList<CacheableShortcutInfo> = mAllDeepShortcuts,
) =
WorkspaceItemProcessor(
c = cursor,
diff --git a/tests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt b/tests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt
index ca2ef42..a991981 100644
--- a/tests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt
+++ b/tests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt
@@ -28,6 +28,7 @@
import androidx.test.filters.SmallTest
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.LauncherPrefs.Companion.PROMISE_ICON_IDS
+import com.android.launcher3.util.DaggerSingletonTracker
import com.android.launcher3.util.Executors.MODEL_EXECUTOR
import com.android.launcher3.util.IntArray
import com.android.launcher3.util.LauncherModelHelper
@@ -53,6 +54,7 @@
private val expectedAppPackage = "expectedAppPackage"
private val expectedInstallerPackage = "expectedInstallerPackage"
private val mockPackageInstaller: PackageInstaller = mock()
+ private val mTracker: DaggerSingletonTracker = mock()
private lateinit var installSessionHelper: InstallSessionHelper
private lateinit var launcherApps: LauncherApps
@@ -62,7 +64,7 @@
whenever(packageManager.packageInstaller).thenReturn(mockPackageInstaller)
whenever(sandboxContext.packageName).thenReturn(expectedInstallerPackage)
launcherApps = sandboxContext.spyService(LauncherApps::class.java)
- installSessionHelper = InstallSessionHelper(sandboxContext)
+ installSessionHelper = InstallSessionHelper(sandboxContext, mTracker)
}
@Test
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 68004bb..cee88ac 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -26,6 +26,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -103,6 +104,8 @@
public static final long DEFAULT_UI_TIMEOUT = TestUtil.DEFAULT_UI_TIMEOUT;
private static final String TAG = "AbstractLauncherUiTest";
+ private static final long BYTES_PER_MEGABYTE = 1 << 20;
+
private static boolean sDumpWasGenerated = false;
private static boolean sActivityLeakReported = false;
private static boolean sSeenKeyguard = false;
@@ -124,6 +127,10 @@
protected String mTargetPackage;
private int mLauncherPid;
+ private final ActivityManager.MemoryInfo mMemoryInfo = new ActivityManager.MemoryInfo();
+ private final ActivityManager mActivityManager;
+ private long mMemoryBefore;
+
/** Detects activity leaks and throws an exception if a leak is found. */
public static void checkDetectedLeaks(LauncherInstrumentation launcher) {
checkDetectedLeaks(launcher, false);
@@ -192,6 +199,8 @@
}
protected AbstractLauncherUiTest() {
+ mActivityManager = InstrumentationRegistry.getContext()
+ .getSystemService(ActivityManager.class);
mLauncher.enableCheckEventsForSuccessfulGestures();
mLauncher.setAnomalyChecker(AbstractLauncherUiTest::verifyKeyguardInvisible);
try {
@@ -311,6 +320,26 @@
initialize(this);
}
+ private long getAvailableMemory() {
+ mActivityManager.getMemoryInfo(mMemoryInfo);
+
+ return Math.divideExact(mMemoryInfo.availMem, BYTES_PER_MEGABYTE);
+ }
+
+ @Before
+ public void saveMemoryBefore() {
+ mMemoryBefore = getAvailableMemory();
+ }
+
+ @After
+ public void logMemoryAfter() {
+ long memoryAfter = getAvailableMemory();
+
+ Log.d(TAG, "Available memory: before=" + mMemoryBefore
+ + "MB, after=" + memoryAfter
+ + "MB, delta=" + (memoryAfter - mMemoryBefore) + "MB");
+ }
+
/** Method that should be called when a test starts. */
public static void onTestStart() {
waitForSetupWizardDismissal();
diff --git a/tests/src/com/android/launcher3/util/rule/ExtendedLongPressTimeoutRule.java b/tests/src/com/android/launcher3/util/rule/ExtendedLongPressTimeoutRule.java
index 702988c..8a9ff3e 100644
--- a/tests/src/com/android/launcher3/util/rule/ExtendedLongPressTimeoutRule.java
+++ b/tests/src/com/android/launcher3/util/rule/ExtendedLongPressTimeoutRule.java
@@ -16,6 +16,9 @@
package com.android.launcher3.util.rule;
+import static com.android.launcher3.util.TestUtil.grantWriteSecurePermission;
+
+import android.app.Instrumentation;
import android.content.ContentResolver;
import android.provider.Settings;
import android.util.Log;
@@ -51,6 +54,7 @@
try {
Log.d(TAG, "In try-block: Setting long press timeout from "
+ prevLongPressTimeout + "ms to " + newLongPressTimeout + "ms");
+ grantWriteSecurePermission();
Settings.Secure.putInt(
contentResolver,
Settings.Secure.LONG_PRESS_TIMEOUT,
@@ -63,6 +67,7 @@
} finally {
Log.d(TAG, "In finally-block: resetting long press timeout to "
+ prevLongPressTimeout + "ms");
+ grantWriteSecurePermission();
Settings.Secure.putInt(
contentResolver,
Settings.Secure.LONG_PRESS_TIMEOUT,
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index 02a862d..9294755 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -16,10 +16,8 @@
package com.android.launcher3.tapl;
-import static com.android.launcher3.testing.shared.TestProtocol.TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE;
import android.graphics.Point;
-import android.util.Log;
import android.widget.TextView;
import androidx.annotation.NonNull;
@@ -99,8 +97,6 @@
@Override
protected void waitForLongPressConfirmation() {
- Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
- "AppIcon.waitForLongPressConfirmation, resName: popupContainer");
mLauncher.waitForLauncherObject("popup_container");
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index 9d3bc6e..c40e5a9 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -17,10 +17,8 @@
package com.android.launcher3.tapl;
import static com.android.launcher3.testing.shared.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
-import static com.android.launcher3.testing.shared.TestProtocol.TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE;
import android.graphics.Point;
-import android.util.Log;
import android.view.MotionEvent;
import androidx.test.uiautomator.UiObject2;
@@ -115,10 +113,6 @@
iconCenter.y - getStartDragThreshold());
if (runToSpringLoadedState) {
- Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
- "Launchable.startDrag: actionName: long-pressing and triggering drag start"
- + " iconCenter: " + iconCenter + " dragStartCenter: "
- + dragStartCenter);
mLauncher.runToState(() -> movePointerForStartDrag(
downTime,
iconCenter,
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 78627e5..08c5552 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -31,7 +31,6 @@
import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_GET_SPLIT_SELECTION_ACTIVE;
import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_NUM_ALL_APPS_COLUMNS;
-import static com.android.launcher3.testing.shared.TestProtocol.TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE;
import static com.android.launcher3.testing.shared.TestProtocol.TEST_INFO_RESPONSE_FIELD;
import android.app.ActivityManager;
@@ -1212,11 +1211,6 @@
log("Hierarchy before clicking home:");
dumpViewHierarchy();
action = "clicking home button";
- Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
- "LauncherInstrumentation.goHome: isThreeFingerTrackpadGesture: "
- + isThreeFingerTrackpadGesture
- + "getNavigationModel() == NavigationModel.ZERO_BUTTON: " + (
- getNavigationModel() == NavigationModel.ZERO_BUTTON));
runToState(
getHomeButton()::click,
NORMAL_STATE_ORDINAL,
@@ -1567,8 +1561,6 @@
@NonNull
UiObject2 waitForLauncherObject(String resName) {
- Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
- "LauncherInstrumentation.waitForLauncherObject");
return waitForObjectBySelector(getLauncherObjectSelector(resName));
}
@@ -1598,16 +1590,12 @@
@NonNull
List<UiObject2> waitForObjectsBySelector(BySelector selector) {
- Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
- "LauncherInstrumentation.waitForObjectsBySelector");
final List<UiObject2> objects = mDevice.wait(Until.findObjects(selector), WAIT_TIME_MS);
assertNotNull("Can't find any view in Launcher, selector: " + selector, objects);
return objects;
}
UiObject2 waitForObjectBySelector(BySelector selector) {
- Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
- "LauncherInstrumentation.waitForObjectBySelector");
final UiObject2 object = mDevice.wait(Until.findObject(selector), WAIT_TIME_MS);
assertNotNull("Can't find a view in Launcher, selector: " + selector, object);
return object;
@@ -1650,9 +1638,6 @@
void runToState(Runnable command, int expectedState, boolean requireEvent, String actionName) {
if (requireEvent) {
- Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
- "LauncherInstrumentation.runToState: command: " + command + " expectedState: "
- + expectedState + " actionName: " + actionName + "requireEvent: true");
runToState(command, expectedState, actionName);
} else {
command.run();
@@ -2052,15 +2037,11 @@
mPointerCount = 1;
pointerCount = mPointerCount;
}
- Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
- "LauncherInstrumentation.sendPointer: ACTION_DOWN");
break;
case MotionEvent.ACTION_UP:
if (hasTIS && gestureScope == GestureScope.EXPECT_PILFER) {
expectEvent(TestProtocol.SEQUENCE_PILFER, EVENT_PILFER_POINTERS);
}
- Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
- "LauncherInstrumentation.sendPointer: ACTION_UP");
break;
case MotionEvent.ACTION_POINTER_DOWN:
mPointerCount++;
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 748d576..a29362f 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -25,8 +25,6 @@
import static com.android.launcher3.testing.shared.TestProtocol.ALL_APPS_STATE_ORDINAL;
import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL;
-import static com.android.launcher3.testing.shared.TestProtocol.TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE;
-import static com.android.launcher3.testing.shared.TestProtocol.UIOBJECT_STALE_ELEMENT;
import static junit.framework.TestCase.assertNotNull;
import static junit.framework.TestCase.assertTrue;
@@ -34,7 +32,6 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.os.SystemClock;
-import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -371,14 +368,9 @@
.collect(
Collectors.toMap(
/* keyMapper= */ uiObject21 -> {
- Log.d(UIOBJECT_STALE_ELEMENT, "keyText: "
- + uiObject21.getText());
return uiObject21.getText();
},
/* valueMapper= */ uiObject2 -> {
- Log.d(UIOBJECT_STALE_ELEMENT, uiObject2.getText() +
- " dispId" + uiObject2.getDisplayId() +
- " parent" + uiObject2.getParent());
return uiObject2.getVisibleCenter();
},
/* mergeFunction= */ (p1, p2) -> p1.x < p2.x ? p1 : p2));
@@ -646,8 +638,6 @@
try (LauncherInstrumentation.Closable ignored = launcher.addContextLayer(
"want to drag icon to workspace")) {
final long downTime = SystemClock.uptimeMillis();
- Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
- "Workspace.dragIconToWorkspace: starting drag | downtime: " + downTime);
Point dragStart = launchable.startDrag(
downTime,
expectLongClickEvents,
diff --git a/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java b/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java
index b42d43b..e5a2a2e 100644
--- a/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java
+++ b/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java
@@ -15,10 +15,7 @@
*/
package com.android.launcher3.tapl;
-import static com.android.launcher3.testing.shared.TestProtocol.TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE;
-
import android.graphics.Point;
-import android.util.Log;
import java.util.function.Supplier;
@@ -79,9 +76,6 @@
LauncherInstrumentation.Closable c = launcher.addContextLayer(
String.format("want to drag the icon to cell(%d, %d)", cellX, cellY))) {
final Supplier<Point> dest = () -> Workspace.getCellCenter(launcher, cellX, cellY);
- Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
- "WorkspaceDragSource.dragToWorkspace: dragging icon to workspace | dest: "
- + dest.get());
Workspace.dragIconToWorkspace(
launcher,
launchable,