Merge "Fix overview animation after reboot." into main
diff --git a/Android.bp b/Android.bp
index bcbd362..9d7aa73 100644
--- a/Android.bp
+++ b/Android.bp
@@ -17,7 +17,7 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-min_launcher3_sdk_version = "30"
+min_launcher3_sdk_version = "31"
// Targets that don't inherit framework aconfig libs (i.e., those that don't set
// `platform_apis: true`) must manually link them.
@@ -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 9c92655..9fb5b7b 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -418,4 +418,29 @@
namespace: "launcher"
description: "Support changing quiet mode for user profiles in taskbar."
bug: "345760034"
-}
\ No newline at end of file
+}
+
+flag {
+ name: "taskbar_overflow"
+ namespace: "launcher"
+ 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/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index bf198b6..57bfb4a 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -54,6 +54,7 @@
android:protectionLevel="signature|privileged" />
<application android:backupAgent="com.android.launcher3.LauncherBackupAgent"
+ android:enableOnBackInvokedCallback="true"
android:fullBackupOnly="true"
android:fullBackupContent="@xml/backupscheme"
android:hardwareAccelerated="true"
diff --git a/quickstep/dagger/LauncherAppComponent.java b/quickstep/dagger/LauncherAppComponent.java
index bd6008e..068f01c 100644
--- a/quickstep/dagger/LauncherAppComponent.java
+++ b/quickstep/dagger/LauncherAppComponent.java
@@ -18,6 +18,7 @@
import com.android.quickstep.dagger.QuickStepModule;
+import com.android.quickstep.dagger.QuickstepBaseAppComponent;
import dagger.Component;
@@ -26,7 +27,7 @@
*/
@LauncherAppSingleton
@Component(modules = QuickStepModule.class)
-public interface LauncherAppComponent extends LauncherBaseAppComponent {
+public interface LauncherAppComponent extends QuickstepBaseAppComponent {
/** Builder for quickstep LauncherAppComponent. */
@Component.Builder
interface Builder extends LauncherBaseAppComponent.Builder {
diff --git a/quickstep/res/drawable/taskbar_overflow_icon.xml b/quickstep/res/drawable/taskbar_overflow_icon.xml
new file mode 100644
index 0000000..b93a70e
--- /dev/null
+++ b/quickstep/res/drawable/taskbar_overflow_icon.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@color/taskbar_divider_background"
+ android:pathData="M80,605v-250q0,-28.88 20.59,-49.44t49.5,-20.56q28.91,0 49.41,20.56Q220,326.12 220,355v250q0,28.87 -20.59,49.44Q178.82,675 149.91,675t-49.41,-20.56Q80,633.87 80,605ZM340,760q-24,0 -42,-18t-18,-42v-440q0,-24 18,-42t42,-18h280q24,0 42,18t18,42v440q0,24 -18,42t-42,18L340,760ZM740,605v-250q0,-28.88 20.59,-49.44t49.5,-20.56q28.91,0 49.41,20.56Q880,326.12 880,355v250q0,28.87 -20.59,49.44Q838.82,675 809.91,675t-49.41,-20.56Q740,633.87 740,605ZM340,700h280v-440L340,260v440ZM480,480Z"/>
+</vector>
diff --git a/quickstep/res/layout/activity_allset.xml b/quickstep/res/layout/activity_allset.xml
index 685a151..625d9b3 100644
--- a/quickstep/res/layout/activity_allset.xml
+++ b/quickstep/res/layout/activity_allset.xml
@@ -96,7 +96,6 @@
style="@style/TextAppearance.GestureTutorial.Feedback.Subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/allset_hint"
android:textSize="@dimen/allset_page_swipe_up_text_size"
android:gravity="center_horizontal"
diff --git a/quickstep/res/layout/bubblebar_flyout.xml b/quickstep/res/layout/bubblebar_flyout.xml
index ff5047f..fc1e914 100644
--- a/quickstep/res/layout/bubblebar_flyout.xml
+++ b/quickstep/res/layout/bubblebar_flyout.xml
@@ -20,11 +20,12 @@
<ImageView
android:id="@+id/bubble_flyout_avatar"
- android:layout_width="36dp"
+ android:layout_width="50dp"
android:layout_height="36dp"
- android:padding="@dimen/bubblebar_flyout_avatar_message_space"
+ android:paddingEnd="@dimen/bubblebar_flyout_avatar_message_space"
android:scaleType="centerInside"
app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:src="#ff0000"/>
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/layout/taskbar_overflow_button.xml b/quickstep/res/layout/taskbar_overflow_button.xml
new file mode 100644
index 0000000..20104f2
--- /dev/null
+++ b/quickstep/res/layout/taskbar_overflow_button.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<!-- Note: The actual size will match the taskbar icon sizes in TaskbarView#onLayout(). -->
+<com.android.launcher3.views.IconButtonView xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/BaseIcon.Workspace.Taskbar"
+ android:layout_width="@dimen/taskbar_icon_min_touch_size"
+ android:layout_height="@dimen/taskbar_icon_min_touch_size"
+ android:backgroundTint="@android:color/transparent"
+ android:contentDescription="@string/taskbar_overflow_a11y_title" />
diff --git a/quickstep/res/values-af/strings.xml b/quickstep/res/values-af/strings.xml
index 4122637..7d65403 100644
--- a/quickstep/res/values-af/strings.xml
+++ b/quickstep/res/values-af/strings.xml
@@ -139,6 +139,8 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Wys altyd Taakbalk"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Verander navigasiemodus"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Taakbalkverdeler"</string>
+ <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
+ <skip />
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Skuif na links bo"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Skuif na regs onder"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{meer app}other{meer apps}}"</string>
diff --git a/quickstep/res/values-am/strings.xml b/quickstep/res/values-am/strings.xml
index 956767ee..0848ddd 100644
--- a/quickstep/res/values-am/strings.xml
+++ b/quickstep/res/values-am/strings.xml
@@ -139,6 +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>
+ <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-ar/strings.xml b/quickstep/res/values-ar/strings.xml
index e691663..6ba2e5d 100644
--- a/quickstep/res/values-ar/strings.xml
+++ b/quickstep/res/values-ar/strings.xml
@@ -139,6 +139,8 @@
<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="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{تطبيق واحد آخر}zero{تطبيق آخر}two{تطبيقان آخران}few{تطبيقات أخرى}many{تطبيقًا آخر}other{تطبيق آخر}}"</string>
diff --git a/quickstep/res/values-as/strings.xml b/quickstep/res/values-as/strings.xml
index 912003b..486dc09 100644
--- a/quickstep/res/values-as/strings.xml
+++ b/quickstep/res/values-as/strings.xml
@@ -139,6 +139,8 @@
<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="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-az/strings.xml b/quickstep/res/values-az/strings.xml
index b89b707..c483db3 100644
--- a/quickstep/res/values-az/strings.xml
+++ b/quickstep/res/values-az/strings.xml
@@ -139,6 +139,8 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"İşləmə paneli həmişə görünsün"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Naviqasiya rejimini dəyişin"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"İşləmə paneli ayırıcısı"</string>
+ <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
+ <skip />
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Yuxarı/sola köçürün"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Aşağı/sağa köçürün"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{əlavə tətbiq}other{əlavə tətbiq}}"</string>
diff --git a/quickstep/res/values-b+sr+Latn/strings.xml b/quickstep/res/values-b+sr+Latn/strings.xml
index 4ef487e..aa16f3c 100644
--- a/quickstep/res/values-b+sr+Latn/strings.xml
+++ b/quickstep/res/values-b+sr+Latn/strings.xml
@@ -139,6 +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>
+ <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 c506acb..4dcfe62 100644
--- a/quickstep/res/values-be/strings.xml
+++ b/quickstep/res/values-be/strings.xml
@@ -139,6 +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>
+ <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-bg/strings.xml b/quickstep/res/values-bg/strings.xml
index d03e4f7..3c0d840 100644
--- a/quickstep/res/values-bg/strings.xml
+++ b/quickstep/res/values-bg/strings.xml
@@ -139,6 +139,8 @@
<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="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-bn/strings.xml b/quickstep/res/values-bn/strings.xml
index 0c568d9..a224dac 100644
--- a/quickstep/res/values-bn/strings.xml
+++ b/quickstep/res/values-bn/strings.xml
@@ -139,6 +139,8 @@
<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="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-bs/strings.xml b/quickstep/res/values-bs/strings.xml
index 922883e..3357a6e 100644
--- a/quickstep/res/values-bs/strings.xml
+++ b/quickstep/res/values-bs/strings.xml
@@ -139,6 +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>
+ <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-ca/strings.xml b/quickstep/res/values-ca/strings.xml
index fe7933b..abb2984 100644
--- a/quickstep/res/values-ca/strings.xml
+++ b/quickstep/res/values-ca/strings.xml
@@ -139,6 +139,8 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Barra de tasques sempre visible"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Canvia el mode de navegació"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Separador de la Barra de tasques"</string>
+ <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
+ <skip />
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Mou a la part superior o a l\'esquerra"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mou a la part inferior o a la dreta"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{aplicació més}other{aplicacions més}}"</string>
diff --git a/quickstep/res/values-cs/strings.xml b/quickstep/res/values-cs/strings.xml
index 3047d05..324b02a 100644
--- a/quickstep/res/values-cs/strings.xml
+++ b/quickstep/res/values-cs/strings.xml
@@ -139,6 +139,8 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Vždy zobrazovat panel aplikací"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Změnit režim navigace"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Rozdělovač panelu aplikací"</string>
+ <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
+ <skip />
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Přesunout doleva nahoru"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Přesunout doprava dolů"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{další aplikace}few{další aplikace}many{další aplikace}other{dalších aplikací}}"</string>
diff --git a/quickstep/res/values-da/strings.xml b/quickstep/res/values-da/strings.xml
index 6e2130c..ca30dda 100644
--- a/quickstep/res/values-da/strings.xml
+++ b/quickstep/res/values-da/strings.xml
@@ -139,6 +139,8 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Vis altid proceslinjen"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Skift navigationstilstand"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Opdeling af proceslinjen"</string>
+ <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
+ <skip />
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Flyt til toppen eller venstre side"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Flyt til bunden eller højre side"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{yderligere app}one{yderligere app}other{yderligere apps}}"</string>
diff --git a/quickstep/res/values-de/strings.xml b/quickstep/res/values-de/strings.xml
index 54961fe..f14eddf 100644
--- a/quickstep/res/values-de/strings.xml
+++ b/quickstep/res/values-de/strings.xml
@@ -139,6 +139,8 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Taskleiste immer anzeigen"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Navigationsmodus ändern"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Taskleisten-Teiler"</string>
+ <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
+ <skip />
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Nach oben / Nach links verschieben"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Nach unten / Nach rechts verschieben"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{weitere App}other{weitere Apps}}"</string>
diff --git a/quickstep/res/values-el/strings.xml b/quickstep/res/values-el/strings.xml
index 6cbb833..e767c74 100644
--- a/quickstep/res/values-el/strings.xml
+++ b/quickstep/res/values-el/strings.xml
@@ -139,6 +139,8 @@
<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="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-en-rAU/strings.xml b/quickstep/res/values-en-rAU/strings.xml
index dcbaa7a..a74a17b 100644
--- a/quickstep/res/values-en-rAU/strings.xml
+++ b/quickstep/res/values-en-rAU/strings.xml
@@ -139,6 +139,8 @@
<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="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-en-rCA/strings.xml b/quickstep/res/values-en-rCA/strings.xml
index c00e6cd..e0787ca 100644
--- a/quickstep/res/values-en-rCA/strings.xml
+++ b/quickstep/res/values-en-rCA/strings.xml
@@ -139,6 +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>
+ <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-en-rGB/strings.xml b/quickstep/res/values-en-rGB/strings.xml
index dcbaa7a..04b04dd 100644
--- a/quickstep/res/values-en-rGB/strings.xml
+++ b/quickstep/res/values-en-rGB/strings.xml
@@ -139,6 +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>
+ <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-en-rIN/strings.xml b/quickstep/res/values-en-rIN/strings.xml
index dcbaa7a..a74a17b 100644
--- a/quickstep/res/values-en-rIN/strings.xml
+++ b/quickstep/res/values-en-rIN/strings.xml
@@ -139,6 +139,8 @@
<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="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-en-rXC/strings.xml b/quickstep/res/values-en-rXC/strings.xml
index 2abef91..8fcbe2b 100644
--- a/quickstep/res/values-en-rXC/strings.xml
+++ b/quickstep/res/values-en-rXC/strings.xml
@@ -139,6 +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>
+ <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-rUS/strings.xml b/quickstep/res/values-es-rUS/strings.xml
index f09fae6..e6efd43 100644
--- a/quickstep/res/values-es-rUS/strings.xml
+++ b/quickstep/res/values-es-rUS/strings.xml
@@ -139,6 +139,8 @@
<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 la Barra de tareas"</string>
+ <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
+ <skip />
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Mover a la parte superior o izquierda"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mover a la parte inferior o derecha"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{app más}other{apps más}}"</string>
diff --git a/quickstep/res/values-es/strings.xml b/quickstep/res/values-es/strings.xml
index 356a38b..d8bbc55 100644
--- a/quickstep/res/values-es/strings.xml
+++ b/quickstep/res/values-es/strings.xml
@@ -139,6 +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>
+ <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-et/strings.xml b/quickstep/res/values-et/strings.xml
index fccbeda..0e222bc 100644
--- a/quickstep/res/values-et/strings.xml
+++ b/quickstep/res/values-et/strings.xml
@@ -139,6 +139,8 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Kuva tegumiriba alati"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Navigeerimisrežiimi muutmine"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Tegumiriba jagaja"</string>
+ <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
+ <skip />
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Teisalda üles/vasakule"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Teisalda alla/paremale"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{rakendus veel}other{rakendust veel}}"</string>
diff --git a/quickstep/res/values-eu/strings.xml b/quickstep/res/values-eu/strings.xml
index a1d8cc3..7764cb8 100644
--- a/quickstep/res/values-eu/strings.xml
+++ b/quickstep/res/values-eu/strings.xml
@@ -139,6 +139,8 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Erakutsi beti zereginen barra"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Aldatu nabigazio modua"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Zereginen barraren zatitzailea"</string>
+ <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
+ <skip />
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Eraman gora, ezkerretara"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Eraman behera, eskuinetara"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{aplikazio gehiago}other{aplikazio gehiago}}"</string>
diff --git a/quickstep/res/values-fa/strings.xml b/quickstep/res/values-fa/strings.xml
index 8f37686..05cf83e 100644
--- a/quickstep/res/values-fa/strings.xml
+++ b/quickstep/res/values-fa/strings.xml
@@ -139,6 +139,8 @@
<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="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-fi/strings.xml b/quickstep/res/values-fi/strings.xml
index 175a896..73b1ba4 100644
--- a/quickstep/res/values-fi/strings.xml
+++ b/quickstep/res/values-fi/strings.xml
@@ -139,6 +139,8 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Näytä tehtäväpalkki aina"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Vaihda navigointitilaa"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Tehtäväpalkin jakaja"</string>
+ <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
+ <skip />
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Siirrä ylös tai vasemmalle"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Siirrä alas tai oikealle"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{muu sovellus}other{muuta sovellusta}}"</string>
diff --git a/quickstep/res/values-fr-rCA/strings.xml b/quickstep/res/values-fr-rCA/strings.xml
index 6d6c67c..a554881 100644
--- a/quickstep/res/values-fr-rCA/strings.xml
+++ b/quickstep/res/values-fr-rCA/strings.xml
@@ -139,6 +139,8 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Tjrs afficher barre des tâches"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Changer de mode de navigation"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Séparateur de la barre des tâches"</string>
+ <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
+ <skip />
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Déplacer vers le coin supérieur gauche de l\'écran"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Déplacer vers le coin inférieur droit de l\'écran"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{autre appli}one{autre appli}other{autres applis}}"</string>
diff --git a/quickstep/res/values-fr/strings.xml b/quickstep/res/values-fr/strings.xml
index a395266..3711dcd 100644
--- a/quickstep/res/values-fr/strings.xml
+++ b/quickstep/res/values-fr/strings.xml
@@ -139,6 +139,8 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Barre des tâches tjs visible"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Modifier le mode de navigation"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Séparateur de barre des tâches"</string>
+ <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
+ <skip />
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Déplacer en haut ou à gauche"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Déplacer en bas ou à droite"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{autre application}one{autre application}other{autres applications}}"</string>
diff --git a/quickstep/res/values-gl/strings.xml b/quickstep/res/values-gl/strings.xml
index 5b04960..a2c4b73 100644
--- a/quickstep/res/values-gl/strings.xml
+++ b/quickstep/res/values-gl/strings.xml
@@ -139,6 +139,8 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Ver sempre a barra de tarefas"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Cambiar modo de navegación"</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="move_drop_target_top_or_left" msgid="2988702185049595807">"Mover á parte superior ou á esquerda"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mover á parte inferior ou á dereita"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{aplicación máis}other{aplicacións máis}}"</string>
diff --git a/quickstep/res/values-gu/strings.xml b/quickstep/res/values-gu/strings.xml
index fc253bd..dea52c3 100644
--- a/quickstep/res/values-gu/strings.xml
+++ b/quickstep/res/values-gu/strings.xml
@@ -139,6 +139,8 @@
<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="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-hi/strings.xml b/quickstep/res/values-hi/strings.xml
index 30a17db..e81e942 100644
--- a/quickstep/res/values-hi/strings.xml
+++ b/quickstep/res/values-hi/strings.xml
@@ -139,6 +139,8 @@
<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="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-hr/strings.xml b/quickstep/res/values-hr/strings.xml
index 06511e9..2514aa1 100644
--- a/quickstep/res/values-hr/strings.xml
+++ b/quickstep/res/values-hr/strings.xml
@@ -139,6 +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>
+ <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-hu/strings.xml b/quickstep/res/values-hu/strings.xml
index 9bd9478..4dc9974 100644
--- a/quickstep/res/values-hu/strings.xml
+++ b/quickstep/res/values-hu/strings.xml
@@ -139,6 +139,8 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Mindig megjelenő Feladatsáv"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Navigációs mód módosítása"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Feladatsáv-elválasztó"</string>
+ <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
+ <skip />
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Mozgatás felülre vagy a bal oldalra"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mozgatás alulra vagy a jobb oldalra"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{további alkalmazás}other{további alkalmazás}}"</string>
diff --git a/quickstep/res/values-hy/strings.xml b/quickstep/res/values-hy/strings.xml
index e1481ec..f4fcacf 100644
--- a/quickstep/res/values-hy/strings.xml
+++ b/quickstep/res/values-hy/strings.xml
@@ -139,6 +139,8 @@
<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="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-in/strings.xml b/quickstep/res/values-in/strings.xml
index f8345b5..519dced 100644
--- a/quickstep/res/values-in/strings.xml
+++ b/quickstep/res/values-in/strings.xml
@@ -139,6 +139,8 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Selalu tampilkan Taskbar"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Ubah mode navigasi"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Pemisah Taskbar"</string>
+ <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
+ <skip />
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Pindahkan ke atas/kiri"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Pindahkan ke bawah/kanan"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{aplikasi lainnya}other{aplikasi lainnya}}"</string>
diff --git a/quickstep/res/values-is/strings.xml b/quickstep/res/values-is/strings.xml
index 017d108..3aec0ce 100644
--- a/quickstep/res/values-is/strings.xml
+++ b/quickstep/res/values-is/strings.xml
@@ -139,6 +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>
+ <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-it/strings.xml b/quickstep/res/values-it/strings.xml
index 1983ee3..445474e 100644
--- a/quickstep/res/values-it/strings.xml
+++ b/quickstep/res/values-it/strings.xml
@@ -139,6 +139,8 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Mostra sempre barra app"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Cambia modalità di navigazione"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Divisore barra delle app"</string>
+ <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
+ <skip />
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Sposta in alto/a sinistra"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Sposta in basso/a destra"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{altra app}other{altre app}}"</string>
diff --git a/quickstep/res/values-iw/strings.xml b/quickstep/res/values-iw/strings.xml
index a7115e3..5bb51ce 100644
--- a/quickstep/res/values-iw/strings.xml
+++ b/quickstep/res/values-iw/strings.xml
@@ -139,6 +139,8 @@
<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="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{אפליקציות נוספות}two{אפליקציות נוספות}other{אפליקציות נוספות}}"</string>
diff --git a/quickstep/res/values-ja/strings.xml b/quickstep/res/values-ja/strings.xml
index fa3c01d..28b7746 100644
--- a/quickstep/res/values-ja/strings.xml
+++ b/quickstep/res/values-ja/strings.xml
@@ -139,6 +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>
+ <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 a032731..d84d53e 100644
--- a/quickstep/res/values-ka/strings.xml
+++ b/quickstep/res/values-ka/strings.xml
@@ -139,6 +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>
+ <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 e1a9e8e..4cdbfc4 100644
--- a/quickstep/res/values-kk/strings.xml
+++ b/quickstep/res/values-kk/strings.xml
@@ -139,6 +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>
+ <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 2eb3114..5cf1b92 100644
--- a/quickstep/res/values-km/strings.xml
+++ b/quickstep/res/values-km/strings.xml
@@ -139,6 +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>
+ <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-kn/strings.xml b/quickstep/res/values-kn/strings.xml
index bb60620..9a04ffd 100644
--- a/quickstep/res/values-kn/strings.xml
+++ b/quickstep/res/values-kn/strings.xml
@@ -139,6 +139,8 @@
<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="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-ko/strings.xml b/quickstep/res/values-ko/strings.xml
index e6a80c3..38d5bbb 100644
--- a/quickstep/res/values-ko/strings.xml
+++ b/quickstep/res/values-ko/strings.xml
@@ -139,6 +139,8 @@
<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="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 04e7f6e..faf5675 100644
--- a/quickstep/res/values-ky/strings.xml
+++ b/quickstep/res/values-ky/strings.xml
@@ -139,6 +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>
+ <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-lo/strings.xml b/quickstep/res/values-lo/strings.xml
index b3ca116..284cdc7 100644
--- a/quickstep/res/values-lo/strings.xml
+++ b/quickstep/res/values-lo/strings.xml
@@ -139,6 +139,8 @@
<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="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-lt/strings.xml b/quickstep/res/values-lt/strings.xml
index 4f3f36e..a95249b 100644
--- a/quickstep/res/values-lt/strings.xml
+++ b/quickstep/res/values-lt/strings.xml
@@ -139,6 +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>
+ <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-lv/strings.xml b/quickstep/res/values-lv/strings.xml
index fd75fc4..408f3c3 100644
--- a/quickstep/res/values-lv/strings.xml
+++ b/quickstep/res/values-lv/strings.xml
@@ -139,6 +139,8 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Vienmēr rādīt uzdevumu joslu"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Mainīt navigācijas režīmu"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Uzdevumu joslas atdalītājs"</string>
+ <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
+ <skip />
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Pārvietot uz augšējo/kreiso stūri"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Pārvietot uz apakšējo/labo stūri"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{papildu lietotne}zero{papildu lietotņu}one{papildu lietotne}other{papildu lietotnes}}"</string>
diff --git a/quickstep/res/values-mk/strings.xml b/quickstep/res/values-mk/strings.xml
index b50277b..588578e 100644
--- a/quickstep/res/values-mk/strings.xml
+++ b/quickstep/res/values-mk/strings.xml
@@ -139,6 +139,8 @@
<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="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-ml/strings.xml b/quickstep/res/values-ml/strings.xml
index ef72da1..c15a241 100644
--- a/quickstep/res/values-ml/strings.xml
+++ b/quickstep/res/values-ml/strings.xml
@@ -139,6 +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>
+ <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-mn/strings.xml b/quickstep/res/values-mn/strings.xml
index 2ca2e26..9223139 100644
--- a/quickstep/res/values-mn/strings.xml
+++ b/quickstep/res/values-mn/strings.xml
@@ -139,6 +139,8 @@
<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="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-mr/strings.xml b/quickstep/res/values-mr/strings.xml
index 159368d..3eef060 100644
--- a/quickstep/res/values-mr/strings.xml
+++ b/quickstep/res/values-mr/strings.xml
@@ -139,6 +139,8 @@
<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="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 9a27e51..a1f19a9 100644
--- a/quickstep/res/values-ms/strings.xml
+++ b/quickstep/res/values-ms/strings.xml
@@ -139,6 +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>
+ <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-my/strings.xml b/quickstep/res/values-my/strings.xml
index 7e298fb..6ea946a 100644
--- a/quickstep/res/values-my/strings.xml
+++ b/quickstep/res/values-my/strings.xml
@@ -139,6 +139,8 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Taskbar အမြဲပြရန်"</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="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-nb/strings.xml b/quickstep/res/values-nb/strings.xml
index 21b5fd4..33cde5a 100644
--- a/quickstep/res/values-nb/strings.xml
+++ b/quickstep/res/values-nb/strings.xml
@@ -139,6 +139,8 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Vis alltid oppgavelinjen"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Endre navigasjonsmodus"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Skille for oppgavelinjen"</string>
+ <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
+ <skip />
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Flytt til øverst/venstre"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Flytt til nederst/høyre"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{app til}other{apper til}}"</string>
diff --git a/quickstep/res/values-ne/strings.xml b/quickstep/res/values-ne/strings.xml
index 326222b..a2d4d32 100644
--- a/quickstep/res/values-ne/strings.xml
+++ b/quickstep/res/values-ne/strings.xml
@@ -139,6 +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>
+ <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-night/colors.xml b/quickstep/res/values-night/colors.xml
index 2052446..98e4871 100644
--- a/quickstep/res/values-night/colors.xml
+++ b/quickstep/res/values-night/colors.xml
@@ -25,7 +25,7 @@
<color name="all_set_page_background">@android:color/system_neutral1_900</color>
<!-- Turn on work apps button -->
- <color name="work_turn_on_stroke">?androidprv:attr/colorAccentSecondaryVariant</color>
+ <color name="work_turn_on_stroke">?attr/materialColorPrimary</color>
<color name="work_fab_bg_color">?attr/materialColorPrimaryFixedDim</color>
<color name="work_fab_icon_color">?attr/materialColorOnPrimaryFixed</color>
</resources>
\ No newline at end of file
diff --git a/quickstep/res/values-nl/strings.xml b/quickstep/res/values-nl/strings.xml
index 8a923b5..a4949ac 100644
--- a/quickstep/res/values-nl/strings.xml
+++ b/quickstep/res/values-nl/strings.xml
@@ -139,6 +139,8 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Taakbalk altijd tonen"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Navigatiemodus wijzigen"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Scheiding voor Taakbalk"</string>
+ <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
+ <skip />
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Naar boven/links verplaatsen"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Naar beneden/rechts verplaatsen"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{extra app}other{extra apps}}"</string>
diff --git a/quickstep/res/values-or/strings.xml b/quickstep/res/values-or/strings.xml
index 3150ded..7753153 100644
--- a/quickstep/res/values-or/strings.xml
+++ b/quickstep/res/values-or/strings.xml
@@ -139,6 +139,8 @@
<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="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-pa/strings.xml b/quickstep/res/values-pa/strings.xml
index 7da7555..011fa6e 100644
--- a/quickstep/res/values-pa/strings.xml
+++ b/quickstep/res/values-pa/strings.xml
@@ -139,6 +139,8 @@
<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="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-pl/strings.xml b/quickstep/res/values-pl/strings.xml
index 0cc49e2..df0f74c 100644
--- a/quickstep/res/values-pl/strings.xml
+++ b/quickstep/res/values-pl/strings.xml
@@ -139,6 +139,8 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Zawsze pokazuj pasek aplikacji"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Zmień tryb nawigacji"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Linia dzielenia paska aplikacji"</string>
+ <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
+ <skip />
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Przesuń w górny lewy róg"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Przesuń w dolny prawy róg"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{inna aplikacja}few{inne aplikacje}many{innych aplikacji}other{innej aplikacji}}"</string>
diff --git a/quickstep/res/values-pt-rPT/strings.xml b/quickstep/res/values-pt-rPT/strings.xml
index 33b87df..2167875 100644
--- a/quickstep/res/values-pt-rPT/strings.xml
+++ b/quickstep/res/values-pt-rPT/strings.xml
@@ -139,6 +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>
+ <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 0aa6295..9309810 100644
--- a/quickstep/res/values-pt/strings.xml
+++ b/quickstep/res/values-pt/strings.xml
@@ -139,6 +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>
+ <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-ro/strings.xml b/quickstep/res/values-ro/strings.xml
index 2f9d287..6bd3e0f 100644
--- a/quickstep/res/values-ro/strings.xml
+++ b/quickstep/res/values-ro/strings.xml
@@ -139,6 +139,8 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Afișează mereu bara"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Schimbă modul de navigare"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Separator pentru bara de activități"</string>
+ <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
+ <skip />
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Mută în stânga sus"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mută în dreapta jos"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{aplicație suplimentară}few{mai multe aplicații}other{mai multe aplicații}}"</string>
diff --git a/quickstep/res/values-ru/strings.xml b/quickstep/res/values-ru/strings.xml
index 2722ca9..76c4e1f 100644
--- a/quickstep/res/values-ru/strings.xml
+++ b/quickstep/res/values-ru/strings.xml
@@ -139,6 +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>
+ <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 19b61f7..0953b38 100644
--- a/quickstep/res/values-si/strings.xml
+++ b/quickstep/res/values-si/strings.xml
@@ -139,6 +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>
+ <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-sk/strings.xml b/quickstep/res/values-sk/strings.xml
index 0f0ec27..6241ad2 100644
--- a/quickstep/res/values-sk/strings.xml
+++ b/quickstep/res/values-sk/strings.xml
@@ -139,6 +139,8 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Zobrazovať panel aplikácií"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Zmeniť režim navigácie"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Rozdeľovač panela aplikácií"</string>
+ <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
+ <skip />
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Presunúť hore alebo doľava"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Presunúť dole alebo doprava"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{ďalšia aplikácia}few{ďalšie aplikácie}many{ďalšie aplikácie}other{ďalšie aplikácie}}"</string>
diff --git a/quickstep/res/values-sl/strings.xml b/quickstep/res/values-sl/strings.xml
index 972ced5..94de1e05 100644
--- a/quickstep/res/values-sl/strings.xml
+++ b/quickstep/res/values-sl/strings.xml
@@ -139,6 +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>
+ <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-sq/strings.xml b/quickstep/res/values-sq/strings.xml
index d2a7281..12635c6 100644
--- a/quickstep/res/values-sq/strings.xml
+++ b/quickstep/res/values-sq/strings.xml
@@ -139,6 +139,8 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Shfaq gjithmonë shiritin e detyrave"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Ndrysho modalitetin e navigimit"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Ndarësi i shiritit të detyrave"</string>
+ <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
+ <skip />
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Lëviz në krye/majtas"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Lëviz në fund/djathtas"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{aplikacion tjetër}other{aplikacione të tjera}}"</string>
diff --git a/quickstep/res/values-sr/strings.xml b/quickstep/res/values-sr/strings.xml
index 81204af..d6e5d03 100644
--- a/quickstep/res/values-sr/strings.xml
+++ b/quickstep/res/values-sr/strings.xml
@@ -139,6 +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>
+ <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 5d0c7a3..bba98c6 100644
--- a/quickstep/res/values-sv/strings.xml
+++ b/quickstep/res/values-sv/strings.xml
@@ -139,6 +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>
+ <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-sw/strings.xml b/quickstep/res/values-sw/strings.xml
index e133ba3..5b74600 100644
--- a/quickstep/res/values-sw/strings.xml
+++ b/quickstep/res/values-sw/strings.xml
@@ -139,6 +139,8 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Onyesha Zana kila wakati"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Badilisha hali ya usogezaji"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Kitenganishi cha Upauzana"</string>
+ <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
+ <skip />
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Sogeza juu/kushoto"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Sogeza chini/kulia"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{programu nyingine}other{programu zingine}}"</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-ta/strings.xml b/quickstep/res/values-ta/strings.xml
index 95d0fa9..39dc6bd 100644
--- a/quickstep/res/values-ta/strings.xml
+++ b/quickstep/res/values-ta/strings.xml
@@ -139,6 +139,8 @@
<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="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-te/strings.xml b/quickstep/res/values-te/strings.xml
index 116d388..91ef846 100644
--- a/quickstep/res/values-te/strings.xml
+++ b/quickstep/res/values-te/strings.xml
@@ -139,6 +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>
+ <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-th/strings.xml b/quickstep/res/values-th/strings.xml
index ecdb3b5..c6fddfb 100644
--- a/quickstep/res/values-th/strings.xml
+++ b/quickstep/res/values-th/strings.xml
@@ -139,6 +139,8 @@
<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="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-tl/strings.xml b/quickstep/res/values-tl/strings.xml
index da646a4..91f9675 100644
--- a/quickstep/res/values-tl/strings.xml
+++ b/quickstep/res/values-tl/strings.xml
@@ -139,6 +139,8 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Ipakita lagi ang Taskbar"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Magpalit ng navigation mode"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Divider ng Taskbar"</string>
+ <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
+ <skip />
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Ilipat sa itaas/kaliwa"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Ilipat sa ibaba/kanan"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{pang app}one{pang app}other{pang app}}"</string>
diff --git a/quickstep/res/values-tr/strings.xml b/quickstep/res/values-tr/strings.xml
index 7be5464..f5f98bb 100644
--- a/quickstep/res/values-tr/strings.xml
+++ b/quickstep/res/values-tr/strings.xml
@@ -139,6 +139,8 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Görev çubuğunu daima göster"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Gezinme modunu değiştir"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Görev Çubuğu Ayırıcısı"</string>
+ <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
+ <skip />
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Sol üste taşı"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Sağ alta taşı"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{uygulama daha}other{uygulama daha}}"</string>
diff --git a/quickstep/res/values-uk/strings.xml b/quickstep/res/values-uk/strings.xml
index 216ae0b..93d974f 100644
--- a/quickstep/res/values-uk/strings.xml
+++ b/quickstep/res/values-uk/strings.xml
@@ -139,6 +139,8 @@
<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="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-ur/strings.xml b/quickstep/res/values-ur/strings.xml
index 2307cb1..76a2c81 100644
--- a/quickstep/res/values-ur/strings.xml
+++ b/quickstep/res/values-ur/strings.xml
@@ -139,6 +139,8 @@
<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="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-uz/strings.xml b/quickstep/res/values-uz/strings.xml
index 9f98b55..0d6c09d 100644
--- a/quickstep/res/values-uz/strings.xml
+++ b/quickstep/res/values-uz/strings.xml
@@ -139,6 +139,8 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Vazifalar paneli doim chiqarilsin"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Navigatsiya rejimini oʻzgartirish"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Vazifalar panelini ajratkich"</string>
+ <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
+ <skip />
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Yuqoriga yoki chapga oʻtkazish"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Pastga yoki oʻngga oʻtkazish"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{boshqa ilova}other{boshqa ilovalar}}"</string>
diff --git a/quickstep/res/values-vi/strings.xml b/quickstep/res/values-vi/strings.xml
index 50acbbc..cf38392 100644
--- a/quickstep/res/values-vi/strings.xml
+++ b/quickstep/res/values-vi/strings.xml
@@ -139,6 +139,8 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Luôn hiện Thanh tác vụ"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Thay đổi chế độ điều hướng"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Đường phân chia Taskbar"</string>
+ <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
+ <skip />
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Chuyển lên trên cùng/sang bên trái"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Chuyển xuống dưới cùng/sang bên phải"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{ứng dụng khác}other{ứng dụng khác}}"</string>
diff --git a/quickstep/res/values-zh-rCN/strings.xml b/quickstep/res/values-zh-rCN/strings.xml
index 1b6c4e4..9017482 100644
--- a/quickstep/res/values-zh-rCN/strings.xml
+++ b/quickstep/res/values-zh-rCN/strings.xml
@@ -139,6 +139,8 @@
<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="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-zh-rHK/strings.xml b/quickstep/res/values-zh-rHK/strings.xml
index 38608e5..672a61c 100644
--- a/quickstep/res/values-zh-rHK/strings.xml
+++ b/quickstep/res/values-zh-rHK/strings.xml
@@ -139,6 +139,8 @@
<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="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-zh-rTW/strings.xml b/quickstep/res/values-zh-rTW/strings.xml
index 3e46779..257c996 100644
--- a/quickstep/res/values-zh-rTW/strings.xml
+++ b/quickstep/res/values-zh-rTW/strings.xml
@@ -139,6 +139,8 @@
<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="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-zu/strings.xml b/quickstep/res/values-zu/strings.xml
index 120b387..9d7ecd7 100644
--- a/quickstep/res/values-zu/strings.xml
+++ b/quickstep/res/values-zu/strings.xml
@@ -139,6 +139,8 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Bonisa i-Taskbar njalo."</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Shintsha imodi yokufuna"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Isihlukanisi se-Taskbar"</string>
+ <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
+ <skip />
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Hamba phezulu/kwesokunxele"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Hamba phansi/kwesokudla"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{i-app eyengeziwe}one{ama-app engeziwe}other{ama-app engeziwe}}"</string>
diff --git a/quickstep/res/values/colors.xml b/quickstep/res/values/colors.xml
index 0bb971e..4c48bd3 100644
--- a/quickstep/res/values/colors.xml
+++ b/quickstep/res/values/colors.xml
@@ -94,7 +94,7 @@
<color name="lottie_yellow600">#f9ab00</color>
<!-- Turn on work apps button -->
- <color name="work_turn_on_stroke">?androidprv:attr/colorAccentPrimaryVariant</color>
+ <color name="work_turn_on_stroke">?attr/materialColorPrimary</color>
<color name="work_fab_bg_color">?attr/materialColorPrimaryFixedDim</color>
<color name="work_fab_icon_color">?attr/materialColorOnPrimaryFixed</color>
</resources>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index e99e9b5..8957e0d 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -480,11 +480,15 @@
<dimen name="bubble_expanded_view_drop_target_margin">16dp</dimen>
<!-- Bubble bar flyout view -->
- <dimen name="bubblebar_flyout_padding_horizontal">14dp</dimen>
- <dimen name="bubblebar_flyout_padding_vertical">10dp</dimen>
+ <dimen name="bubblebar_flyout_padding">16dp</dimen>
<dimen name="bubblebar_flyout_elevation">4dp</dimen>
- <dimen name="bubblebar_flyout_avatar_message_space">6dp</dimen>
- <dimen name="bubblebar_flyout_max_width">96dp</dimen>
+ <dimen name="bubblebar_flyout_avatar_message_space">14dp</dimen>
+ <dimen name="bubblebar_flyout_min_width">238dp</dimen>
+ <dimen name="bubblebar_flyout_max_width">276dp</dimen>
+ <dimen name="bubblebar_flyout_triangle_width">12dp</dimen>
+ <dimen name="bubblebar_flyout_triangle_height">10dp</dimen>
+ <dimen name="bubblebar_flyout_triangle_overlap_amount">1dp</dimen>
+ <dimen name="bubblebar_flyout_triangle_radius">2dp</dimen>
<!-- Launcher splash screen -->
<!-- Note: keep this value in sync with the WindowManager/Shell dimens.xml -->
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index f72f3c5..008766b 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -320,6 +320,8 @@
<string name="change_navigation_mode">Change navigation mode</string>
<!-- Accessibility title for the Taskbar vertical divider icon. [CHAR_LIMIT=NONE] -->
<string name="taskbar_divider_a11y_title">Taskbar Divider</string>
+ <!-- Accessibility title for the Taskbar Overflow icon. [CHAR_LIMIT=NONE] -->
+ <string name="taskbar_overflow_a11y_title">Taskbar Overflow</string>
<!-- Label for moving drop target to the top or left side of the screen, depending on orientation (from the Taskbar only). -->
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/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/statehandlers/DesktopVisibilityController.java b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
index f29980b..3dcb2ac 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
@@ -16,9 +16,9 @@
package com.android.launcher3.statehandlers;
import static android.view.View.VISIBLE;
+import static android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.WALLPAPER_ACTIVITY;
import android.content.Context;
import android.os.Debug;
@@ -167,7 +167,8 @@
notifyDesktopVisibilityListeners(areDesktopTasksVisibleNow);
}
- if (!WALLPAPER_ACTIVITY.isEnabled(mContext) && wasVisible != isVisible) {
+ if (!ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()
+ && wasVisible != isVisible) {
// TODO: b/333533253 - Remove after flag rollout
if (mVisibleDesktopTasksCount > 0) {
setLauncherViewsVisibility(View.INVISIBLE);
@@ -225,7 +226,7 @@
notifyDesktopVisibilityListeners(areDesktopTasksVisibleNow);
}
- if (WALLPAPER_ACTIVITY.isEnabled(mContext)) {
+ if (ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()) {
return;
}
// TODO: b/333533253 - Clean up after flag rollout
@@ -252,7 +253,7 @@
for (DesktopVisibilityListener listener : mDesktopVisibilityListeners) {
listener.onDesktopVisibilityChanged(areDesktopTasksVisible);
}
- DisplayController.handleInfoChangeForDesktopMode(mContext);
+ DisplayController.INSTANCE.get(mContext).notifyConfigChange();
}
private void notifyTaskbarDesktopModeListeners(boolean doesAnyTaskRequireTaskbarRounding) {
@@ -341,7 +342,7 @@
if (mContext == null) {
return;
}
- if (WALLPAPER_ACTIVITY.isEnabled(mContext)) {
+ if (ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()) {
return;
}
if (DEBUG) {
@@ -376,7 +377,7 @@
if (mContext == null) {
return;
}
- if (WALLPAPER_ACTIVITY.isEnabled(mContext)) {
+ if (ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()) {
return;
}
if (DEBUG) {
@@ -396,7 +397,7 @@
if (mContext == null) {
return;
}
- if (WALLPAPER_ACTIVITY.isEnabled(mContext)) {
+ if (ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()) {
return;
}
if (DEBUG) {
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/KeyboardQuickSwitchView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
index b4102a9..fc8204a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
@@ -58,8 +58,12 @@
import java.util.Locale;
/**
- * View that allows quick switching between recent tasks through keyboard alt-tab and alt-shift-tab
- * commands.
+ * View that allows quick switching between recent tasks.
+ *
+ * Can be access via:
+ * - keyboard alt-tab
+ * - alt-shift-tab
+ * - taskbar overflow button
*/
public class KeyboardQuickSwitchView extends ConstraintLayout {
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 4d144ca..10ff9ac 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -15,15 +15,15 @@
*/
package com.android.launcher3.taskbar;
+import static android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY;
+
import static com.android.launcher3.QuickstepTransitionManager.TRANSIENT_TASKBAR_TRANSITION_DURATION;
import static com.android.launcher3.statemanager.BaseState.FLAG_NON_INTERACTIVE;
import static com.android.launcher3.taskbar.TaskbarEduTooltipControllerKt.TOOLTIP_STEP_FEATURES;
import static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_VISIBLE;
-import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.WALLPAPER_ACTIVITY;
import android.animation.Animator;
import android.animation.AnimatorSet;
-import android.graphics.Rect;
import android.window.RemoteTransition;
import androidx.annotation.NonNull;
@@ -50,6 +50,8 @@
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;
import java.util.Arrays;
@@ -126,7 +128,7 @@
@Override
protected void onDestroy() {
- onLauncherVisibilityChanged(false);
+ onLauncherVisibilityChanged(false /* isVisible */, true /* fromInitOrDestroy */);
super.onDestroy();
mTaskbarLauncherStateController.onDestroy();
@@ -188,35 +190,20 @@
}
/**
- * Returns the bounds of launcher's hotseat.
- */
- public void getHotseatBounds(Rect hotseatBoundsOut) {
- DeviceProfile launcherDP = mLauncher.getDeviceProfile();
- if (launcherDP.isQsbInline) {
- // Not currently supported.
- hotseatBoundsOut.setEmpty();
- return;
- }
- int left = (launcherDP.widthPx - launcherDP.getHotseatWidthPx()
- - mLauncher.getHotseat().getUnusedHorizontalSpace()) / 2;
- int right = left + launcherDP.getHotseatWidthPx();
- int bottom = launcherDP.getHotseatLayoutPadding(mLauncher).bottom;
- int top = bottom - launcherDP.hotseatCellHeightPx;
- hotseatBoundsOut.set(left, top, right, bottom);
- }
-
- /**
* Should be called from onResume() and onPause(), and animates the Taskbar accordingly.
*/
@Override
public void onLauncherVisibilityChanged(boolean isVisible) {
+ if (DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(mLauncher)) {
+ DisplayController.INSTANCE.get(mLauncher).notifyConfigChange();
+ }
onLauncherVisibilityChanged(isVisible, false /* fromInit */);
}
- private void onLauncherVisibilityChanged(boolean isVisible, boolean fromInit) {
+ private void onLauncherVisibilityChanged(boolean isVisible, boolean fromInitOrDestroy) {
onLauncherVisibilityChanged(
isVisible,
- fromInit,
+ fromInitOrDestroy,
/* startAnimation= */ true,
DisplayController.isTransientTaskbar(mLauncher)
? TRANSIENT_TASKBAR_TRANSITION_DURATION
@@ -227,7 +214,7 @@
@Nullable
private Animator onLauncherVisibilityChanged(
- boolean isVisible, boolean fromInit, boolean startAnimation, int duration) {
+ boolean isVisible, boolean fromInitOrDestroy, boolean startAnimation, int duration) {
// Launcher is resumed during the swipe-to-overview gesture under shell-transitions, so
// avoid updating taskbar state in that situation (when it's non-interactive -- or
// "background") to avoid premature animations.
@@ -238,14 +225,14 @@
return null;
}
- if (!WALLPAPER_ACTIVITY.isEnabled(mLauncher)
+ if (!ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()
&& mControllers.taskbarDesktopModeController.getAreDesktopTasksVisible()) {
// TODO: b/333533253 - Remove after flag rollout
isVisible = false;
}
mTaskbarLauncherStateController.updateStateForFlag(FLAG_VISIBLE, isVisible);
- if (fromInit || mControllers == null) {
+ if (fromInitOrDestroy) {
duration = 0;
}
return mTaskbarLauncherStateController.applyState(duration, startAnimation);
@@ -481,4 +468,25 @@
protected String getTaskbarUIControllerName() {
return "LauncherTaskbarUIController";
}
+
+ @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.
+ if (mLauncher.getWorkspace().isOverlayShown()) {
+ mLauncher.getWorkspace().onOverlayScrollChanged(0);
+ }
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index e9bd30a..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(
@@ -400,12 +406,6 @@
}
};
mSeparateWindowParent.recreateControllers();
- if (com.android.wm.shell.Flags.enableBubbleBarInPersistentTaskBar()
- && mControllers.bubbleControllers.isPresent()) {
- BubbleBarLocation bubblesLocation = mControllers.bubbleControllers.get()
- .bubbleBarViewController.getBubbleBarLocation();
- onBubbleBarLocationUpdated(bubblesLocation);
- }
}
private void initButtons(ViewGroup navContainer, ViewGroup endContainer,
@@ -1180,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) {
@@ -1223,6 +1239,20 @@
return (int) navBarTargetStartX - mNavButtonContainer.getLeft();
}
+ /** Adjusts the navigation buttons layout position according to the bubble bar location. */
+ public void onTaskbarLayoutChanged() {
+ if (mControllers.taskbarViewController.getIconLayoutBounds().isEmpty()) return;
+ if (enableBubbleBarInPersistentTaskBar()
+ && mControllers.bubbleControllers.isPresent()) {
+ 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);
+ }
+ }
+
private class RotationButtonListener implements RotationButton.RotationButtonUpdatesCallback {
@Override
public void onVisibilityChanged(boolean isVisible) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/NewWindowTaskbarShortcut.kt b/quickstep/src/com/android/launcher3/taskbar/NewWindowTaskbarShortcut.kt
new file mode 100644
index 0000000..dc66e0b
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/NewWindowTaskbarShortcut.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.content.Context
+import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK
+import android.view.View
+import com.android.launcher3.AbstractFloatingView
+import com.android.launcher3.R
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.popup.SystemShortcut
+import com.android.launcher3.views.ActivityContext
+
+/**
+ * A single menu item shortcut to execute creating a new instance of an app. Default interaction for
+ * [onClick] is to launch the app in full screen or as a floating window in Desktop Mode.
+ */
+class NewWindowTaskbarShortcut<T>(target: T, itemInfo: ItemInfo?, originalView: View?) :
+ SystemShortcut<T>(
+ R.drawable.desktop_mode_ic_taskbar_menu_new_window,
+ R.string.new_window_option_taskbar,
+ target,
+ itemInfo,
+ originalView
+ ) where T : Context?, T : ActivityContext? {
+
+ override fun onClick(v: View?) {
+ val intent = mItemInfo.intent ?: return
+ intent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK)
+ mTarget?.startActivitySafely(v, intent, mItemInfo)
+ AbstractFloatingView.closeAllOpenViews(mTarget)
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
index fabf3a5..7273fac 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -208,11 +208,8 @@
* Creates and returns a {@link RevealOutlineAnimation} Animator that updates the stashed handle
* shape and size. When stashed, the shape is a thin rounded pill. When unstashed, the shape
* morphs into the size of where the taskbar icons will be.
- *
- * @param taskbarToHotseatOffsets A Rect of offsets used to transform the bounds of the
- * stashed handle to wrap around the hotseat items.
*/
- public Animator createRevealAnimToIsStashed(boolean isStashed, Rect taskbarToHotseatOffsets) {
+ public Animator createRevealAnimToIsStashed(boolean isStashed) {
Rect visualBounds = mControllers.taskbarViewController.getIconLayoutVisualBounds();
float startRadius = mStashedHandleRadius;
@@ -223,13 +220,6 @@
visualBounds.bottom += heightDiff;
startRadius = visualBounds.height() / 2f;
-
- // We use these offsets to create a larger stashed handle to wrap around the items
- // of the hotseat. This is only used for certain animations.
- visualBounds.top += taskbarToHotseatOffsets.top;
- visualBounds.bottom += taskbarToHotseatOffsets.bottom;
- visualBounds.left += taskbarToHotseatOffsets.left;
- visualBounds.right += taskbarToHotseatOffsets.right;
}
final RevealOutlineAnimation handleRevealProvider = new RoundedRectRevealOutlineProvider(
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 901b646..a59445b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -29,6 +29,7 @@
import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
import static com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_OVERLAY_PROXY;
import static com.android.launcher3.Flags.enableCursorHoverStates;
+import static com.android.launcher3.Flags.taskbarOverflow;
import static com.android.launcher3.Utilities.calculateTextHeight;
import static com.android.launcher3.Utilities.isRunningInTestHarness;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
@@ -107,6 +108,7 @@
import com.android.launcher3.taskbar.allapps.TaskbarAllAppsController;
import com.android.launcher3.taskbar.bubbles.BubbleBarController;
import com.android.launcher3.taskbar.bubbles.BubbleBarPinController;
+import com.android.launcher3.taskbar.bubbles.BubbleBarSwipeController;
import com.android.launcher3.taskbar.bubbles.BubbleBarView;
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController;
import com.android.launcher3.taskbar.bubbles.BubbleControllers;
@@ -130,11 +132,11 @@
import com.android.launcher3.touch.ItemClickHandler.ItemClickProxy;
import com.android.launcher3.util.ActivityOptionsWrapper;
import com.android.launcher3.util.ApiWrapper;
+import com.android.launcher3.util.ApplicationInfoWrapper;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.NavigationMode;
-import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
@@ -278,9 +280,11 @@
BubbleBarController.onTaskbarRecreated();
if (BubbleBarController.isBubbleBarEnabled() && bubbleBarView != null) {
Optional<BubbleStashedHandleViewController> bubbleHandleController = Optional.empty();
+ Optional<BubbleBarSwipeController> bubbleBarSwipeController = Optional.empty();
if (isTransientTaskbar) {
bubbleHandleController = Optional.of(
new BubbleStashedHandleViewController(this, bubbleHandleView));
+ bubbleBarSwipeController = Optional.of(new BubbleBarSwipeController(this));
}
TaskbarHotseatDimensionsProvider dimensionsProvider =
new DeviceProfileDimensionsProviderAdapter(this);
@@ -298,6 +302,7 @@
() -> DisplayController.INSTANCE.get(this).getInfo().currentSize),
new BubblePinController(this, mDragLayer,
() -> DisplayController.INSTANCE.get(this).getInfo().currentSize),
+ bubbleBarSwipeController,
new BubbleCreator(this)
));
}
@@ -361,38 +366,6 @@
}
/**
- * Calculate the offsets needed to transform the transient taskbar bounds to the hotseat bounds.
- * @return The offsets will be stored in a Rect
- */
- public Rect calculateTaskbarToHotseatOffsets(Rect hotseatBounds) {
- Rect taskbar = getTransientTaskbarBounds();
- Rect offsets = new Rect();
-
- offsets.left = hotseatBounds.left - taskbar.left;
- offsets.right = hotseatBounds.right - taskbar.right;
-
- int heightDiff = hotseatBounds.height() - taskbar.height();
- offsets.top = (taskbar.height() - heightDiff) / 2;
-
- int gleanedTaskbarPadding = (mDeviceProfile.taskbarHeight
- - getTransientTaskbarBounds().height()) / 2;
- offsets.left -= gleanedTaskbarPadding;
- offsets.top -= gleanedTaskbarPadding;
- offsets.right += gleanedTaskbarPadding;
-
- // Bottom is relative to the bottom of layout, so we can calculate it with padding included.
- offsets.bottom = (hotseatBounds.height() - taskbar.height()) / 2;
-
- // Update bounds in taskbar background
- if (hotseatBounds.isEmpty()) {
- mDragLayer.getTaskbarToHotseatOffsetRect().setEmpty();
- } else {
- mDragLayer.getTaskbarToHotseatOffsetRect().set(offsets);
- }
- return offsets;
- }
-
- /**
* Copy the original DeviceProfile, match the number of hotseat icons and qsb width and update
* the icon size
*/
@@ -1221,6 +1194,11 @@
RecentsView recents = taskbarUIController.getRecentsView();
boolean shouldCloseAllOpenViews = true;
Object tag = view.getTag();
+
+ if (taskbarOverflow()) {
+ mControllers.keyboardQuickSwitchController.closeQuickSwitchView(false);
+ }
+
if (tag instanceof GroupTask groupTask) {
handleGroupTaskLaunch(
groupTask,
@@ -1259,7 +1237,8 @@
Intent intent = new Intent(info.getIntent())
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
- if (mIsSafeModeEnabled && !PackageManagerHelper.isSystemApp(this, intent)) {
+ if (mIsSafeModeEnabled
+ && !new ApplicationInfoWrapper(this, intent).isSystem()) {
Toast.makeText(this, R.string.safemode_shortcut_error,
Toast.LENGTH_SHORT).show();
} else if (info.isPromise()) {
@@ -1559,6 +1538,8 @@
* @param delayTaskbarBackground whether we will delay the taskbar background animation
*/
public void onSwipeToUnstashTaskbar(boolean delayTaskbarBackground) {
+ mControllers.uiController.onSwipeToUnstashTaskbar();
+
boolean wasStashed = mControllers.taskbarStashController.isStashed();
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(/* stash= */ false,
SHOULD_BUBBLES_FOLLOW_DEFAULT_VALUE, delayTaskbarBackground);
@@ -1709,18 +1690,6 @@
}
/**
- * Returns the bounds of launcher's hotseat (if exists).
- */
- public void getHotseatBounds(Rect hotseatBoundsOut) {
- TaskbarUIController uiController = mControllers.uiController;
- if (uiController instanceof LauncherTaskbarUIController launcherController) {
- launcherController.getHotseatBounds(hotseatBoundsOut);
- } else {
- hotseatBoundsOut.setEmpty();
- }
- }
-
- /**
* Called when we determine the touchable region.
*
* @param exclude {@code true} then the magnification region computation will omit the window.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
index d6ce3a4..c0e921e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
@@ -21,7 +21,6 @@
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Path
-import android.graphics.Rect
import android.graphics.RectF
import com.android.app.animation.Interpolators
import com.android.internal.policy.ScreenDecorationsUtils
@@ -60,9 +59,6 @@
var translationYForSwipe = 0f
var translationYForStash = 0f
- // When not empty, we can use this to transform transient taskbar background to hotseat bounds.
- val taskbarToHotseatOffsetRect = Rect()
-
private val transientBackgroundBounds = context.transientTaskbarBounds
private val shadowAlpha: Float
@@ -230,12 +226,6 @@
val radius = newBackgroundHeight / 2f
val bottomMarginProgress = bottomMargin * ((1f - progress) / 2f)
- // Used to transform the background so that it wraps around the items on the hotseat.
- val hotseatOffsetLeft = taskbarToHotseatOffsetRect.left * progress
- val hotseatOffsetTop = taskbarToHotseatOffsetRect.top * progress
- val hotseatOffsetRight = taskbarToHotseatOffsetRect.right * progress
- val hotseatOffsetBottom = taskbarToHotseatOffsetRect.bottom * progress
-
// Aligns the bottom with the bottom of the stashed handle.
val bottom =
canvas.height - bottomMargin +
@@ -260,10 +250,10 @@
strokePaint.alpha = (paint.alpha * strokeAlpha) / 255
lastDrawnTransientRect.set(
- transientBackgroundBounds.left + halfWidthDelta + hotseatOffsetLeft,
- bottom - newBackgroundHeight + hotseatOffsetTop,
- transientBackgroundBounds.right - halfWidthDelta + hotseatOffsetRight,
- bottom + hotseatOffsetBottom,
+ transientBackgroundBounds.left + halfWidthDelta,
+ bottom - newBackgroundHeight,
+ transientBackgroundBounds.right - halfWidthDelta,
+ bottom
)
val horizontalInset = fullWidth * widthInsetPercentage
lastDrawnTransientRect.inset(horizontalInset, 0f)
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/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
index a090956..e16c76d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
@@ -22,7 +22,6 @@
import android.content.Context;
import android.graphics.Canvas;
-import android.graphics.Rect;
import android.graphics.RectF;
import android.media.permission.SafeCloseable;
import android.util.AttributeSet;
@@ -100,7 +99,7 @@
public TaskbarDragLayer(@NonNull Context context, @Nullable AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
super(context, attrs, 1 /* alphaChannelCount */);
- mBackgroundRenderer = new TaskbarBackgroundRenderer(mActivity);
+ mBackgroundRenderer = new TaskbarBackgroundRenderer(mContainer);
mTaskbarBackgroundAlpha = new MultiPropertyFactory<>(this, BG_ALPHA, INDEX_COUNT,
(a, b) -> a * b, 1f);
@@ -109,7 +108,7 @@
public void init(TaskbarDragLayerController.TaskbarDragLayerCallbacks callbacks) {
mControllerCallbacks = callbacks;
- mBackgroundRenderer.updateStashedHandleWidth(mActivity, getResources());
+ mBackgroundRenderer.updateStashedHandleWidth(mContainer, getResources());
recreateControllers();
}
@@ -260,11 +259,6 @@
return mBackgroundRenderer.getLastDrawnTransientRect();
}
- /** Returns the rect used to transform transient taskbar to the hotseat */
- public Rect getTaskbarToHotseatOffsetRect() {
- return mBackgroundRenderer.getTaskbarToHotseatOffsetRect();
- }
-
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
TestLogging.recordMotionEvent(TestProtocol.SEQUENCE_MAIN, "Touch event", ev);
@@ -275,7 +269,7 @@
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getAction() == ACTION_UP && event.getKeyCode() == KEYCODE_BACK) {
- AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
+ AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mContainer);
if (topView != null && topView.canHandleBack()) {
topView.onBackInvoked();
// Handled by the floating view.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index 221504d..685c109 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -147,7 +147,11 @@
defaultTouchableRegion.addBoundsToRegion(bubbleBarViewController.bubbleBarBounds)
}
}
- if (taskbarStashController.isInApp || taskbarStashController.isInOverview) {
+ if (
+ taskbarStashController.isInApp ||
+ taskbarStashController.isInOverview ||
+ DisplayController.showLockedTaskbarOnHome(context)
+ ) {
// only add the taskbar touch region if not on home
val bottom = windowLayoutParams.height
val top = bottom - taskbarTouchableHeight
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 6bf1ee6..43b1ae0 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;
@@ -53,6 +59,7 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
import com.android.quickstep.RecentsAnimationCallbacks;
import com.android.quickstep.RecentsAnimationController;
@@ -61,6 +68,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;
@@ -150,6 +158,7 @@
private AnimatedFloat mTaskbarAlpha;
private AnimatedFloat mTaskbarCornerRoundness;
private MultiProperty mTaskbarAlphaForHome;
+ private @Nullable Animator mHotseatTranslationXAnimation;
private QuickstepLauncher mLauncher;
private boolean mIsDestroyed = false;
@@ -159,7 +168,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;
@@ -167,6 +181,8 @@
private boolean mShouldDelayLauncherStateAnim;
+ private @Nullable BubbleBarLocation mBubbleBarLocation;
+
// We skip any view synchronizations during init/destroy.
private boolean mCanSyncViews;
@@ -184,6 +200,8 @@
mIsQsbInline = dp.isQsbInline;
TaskbarLauncherStateController.this.updateIconAlphaForHome(
mTaskbarAlphaForHome.getValue(), ALPHA_CHANNEL_TASKBAR_ALIGNMENT);
+ TaskbarLauncherStateController.this.onBubbleBarLocationChanged(
+ mBubbleBarLocation, /* animate = */ false);
}
};
@@ -299,6 +317,7 @@
stashController.updateStateForFlag(FLAG_IN_APP, false);
updateStateForFlag(FLAG_TRANSITION_TO_VISIBLE, true);
+ mLauncherState = toState;
animatorSet.play(stashController.createApplyStateAnimator(duration));
animatorSet.play(applyState(duration, false));
@@ -522,7 +541,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);
@@ -548,7 +567,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));
}
@@ -618,6 +638,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();
@@ -662,6 +691,9 @@
* This refers to the intended state - a transition to this state might be in progress.
*/
public boolean isTaskbarAlignedWithHotseat() {
+ if (DisplayController.showLockedTaskbarOnHome(mLauncher) && isInLauncher()) {
+ return false;
+ }
return mLauncherState.isTaskbarAlignedWithHotseat(mLauncher);
}
@@ -673,8 +705,7 @@
boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher);
boolean willStashVisually = isInStashedState
&& mControllers.taskbarStashController.supportsVisualStashing();
- boolean isTaskbarAlignedWithHotseat =
- mLauncherState.isTaskbarAlignedWithHotseat(mLauncher);
+ boolean isTaskbarAlignedWithHotseat = isTaskbarAlignedWithHotseat();
return isTaskbarAlignedWithHotseat && !willStashVisually;
} else {
return false;
@@ -829,6 +860,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/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 1b4db7a..78e7b47 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -60,6 +60,7 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.contextualeducation.ContextualEduStatsManager;
import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks;
@@ -247,6 +248,7 @@
context,
navCallbacks,
SystemUiProxy.INSTANCE.get(mContext),
+ ContextualEduStatsManager.INSTANCE.get(mContext),
new Handler(),
AssistUtils.newInstance(mContext));
mComponentCallbacks = new ComponentCallbacks() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
index 5024cd8..bdefea6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
@@ -250,6 +250,7 @@
Map<PackageUserKey, Integer> packageUserKeytoUidMap) {
Preconditions.assertUIThread();
mControllers.taskbarAllAppsController.setApps(apps, flags, packageUserKeytoUidMap);
+ mControllers.taskbarPopupController.setApps(apps);
}
protected void dumpLogs(String prefix, PrintWriter pw) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
index 872a4d0..15c35b6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -45,12 +45,14 @@
import androidx.annotation.StringRes;
import com.android.launcher3.R;
+import com.android.launcher3.contextualeducation.ContextualEduStatsManager;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.util.AssistUtils;
+import com.android.systemui.contextualeducation.GestureType;
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import java.io.PrintWriter;
@@ -109,6 +111,7 @@
private final Context mContext;
private final TaskbarNavButtonCallbacks mCallbacks;
private final SystemUiProxy mSystemUiProxy;
+ private final ContextualEduStatsManager mContextualEduStatsManager;
private final Handler mHandler;
private final AssistUtils mAssistUtils;
@Nullable private StatsLogManager mStatsLogManager;
@@ -119,11 +122,13 @@
Context context,
TaskbarNavButtonCallbacks callbacks,
SystemUiProxy systemUiProxy,
+ ContextualEduStatsManager contextualEduStatsManager,
Handler handler,
AssistUtils assistUtils) {
mContext = context;
mCallbacks = callbacks;
mSystemUiProxy = systemUiProxy;
+ mContextualEduStatsManager = contextualEduStatsManager;
mHandler = handler;
mAssistUtils = assistUtils;
}
@@ -137,14 +142,20 @@
switch (buttonType) {
case BUTTON_BACK:
logEvent(LAUNCHER_TASKBAR_BACK_BUTTON_TAP);
+ mContextualEduStatsManager.updateEduStats(/* isTrackpadGesture= */ false,
+ GestureType.BACK);
executeBack();
break;
case BUTTON_HOME:
logEvent(LAUNCHER_TASKBAR_HOME_BUTTON_TAP);
+ mContextualEduStatsManager.updateEduStats(/* isTrackpadGesture= */ false,
+ GestureType.HOME);
navigateHome();
break;
case BUTTON_RECENTS:
logEvent(LAUNCHER_TASKBAR_OVERVIEW_BUTTON_TAP);
+ mContextualEduStatsManager.updateEduStats(/* isTrackpadGesture= */ false,
+ GestureType.OVERVIEW);
navigateToOverview();
break;
case BUTTON_IME_SWITCH:
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
index 2cee77d..70d4bb1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.taskbar;
+import static com.android.launcher3.model.data.AppInfo.COMPONENT_KEY_COMPARATOR;
import static com.android.launcher3.util.SplitConfigurationOptions.getLogEventForPosition;
import android.content.Intent;
@@ -29,11 +30,13 @@
import com.android.internal.logging.InstanceId;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.Flags;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.dot.FolderDotInfo;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -51,9 +54,11 @@
import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.LogUtils;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
@@ -79,6 +84,7 @@
// Initialized in init.
private TaskbarControllers mControllers;
private boolean mAllowInitialSplitSelection;
+ private AppInfo[] mAppInfosList;
public TaskbarPopupController(TaskbarActivityContext context) {
mContext = context;
@@ -195,6 +201,10 @@
if (com.android.wm.shell.Flags.enableBubbleAnything()) {
shortcuts.add(BUBBLE);
}
+ if (Flags.enableMultiInstanceMenuTaskbar()
+ && DesktopModeStatus.canEnterDesktopMode(mContext)) {
+ shortcuts.addAll(getMultiInstanceMenuOptions().toList());
+ }
return shortcuts.stream();
}
@@ -258,7 +268,55 @@
originalView, position, mAllowInitialSplitSelection);
}
- /**
+ /**
+ * Set the list of AppInfos to be able to pull from later
+ */
+ public void setApps(AppInfo[] apps) {
+ mAppInfosList = apps;
+ }
+
+ /**
+ * Finds and returns an AppInfo object from a list, using its ComponentKey for identification.
+ * Based off of {@link com.android.launcher3.allapps.AllAppsStore#getApp(ComponentKey)}
+ * since we cannot access AllAppsStore from here.
+ */
+ public AppInfo getApp(ComponentKey key) {
+ if (key == null) {
+ return null;
+ }
+ AppInfo tempInfo = new AppInfo();
+ tempInfo.componentName = key.componentName;
+ tempInfo.user = key.user;
+ int index = Arrays.binarySearch(mAppInfosList, tempInfo, COMPONENT_KEY_COMPARATOR);
+ return index < 0 ? null : mAppInfosList[index];
+ }
+
+ /**
+ * Returns a stream of Multi Instance menu options if an app supports it.
+ */
+ Stream<SystemShortcut.Factory<BaseTaskbarContext>> getMultiInstanceMenuOptions() {
+ SystemShortcut.Factory<BaseTaskbarContext> factory = createNewWindowShortcutFactory();
+ return factory != null ? Stream.of(factory) : Stream.empty();
+
+ }
+
+ /**
+ * Creates a factory function representing a "New Window" menu item only if the calling app
+ * supports multi-instance.
+ * @return A factory function to be used in populating the long-press menu.
+ */
+ SystemShortcut.Factory<BaseTaskbarContext> createNewWindowShortcutFactory() {
+ return (context, itemInfo, originalView) -> {
+ ComponentKey key = itemInfo.getComponentKey();
+ AppInfo app = getApp(key);
+ if (app != null && app.supportsMultiInstance()) {
+ return new NewWindowTaskbarShortcut<>(context, itemInfo, originalView);
+ }
+ return null;
+ };
+ }
+
+ /**
* A single menu item ("Split left," "Split right," or "Split top") that executes a split
* from the taskbar, as if the user performed a drag and drop split.
* Includes an onClick method that initiates the actual split.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
index 72bdafe..57d4dbb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
@@ -16,6 +16,7 @@
package com.android.launcher3.taskbar
import android.content.Context
+import android.window.flags.DesktopModeFlags
import androidx.annotation.VisibleForTesting
import com.android.launcher3.Flags.enableRecentsInTaskbar
import com.android.launcher3.model.data.ItemInfo
@@ -26,7 +27,6 @@
import com.android.quickstep.RecentsModel
import com.android.quickstep.util.DesktopTask
import com.android.quickstep.util.GroupTask
-import com.android.wm.shell.shared.desktopmode.DesktopModeFlags
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import java.io.PrintWriter
@@ -40,7 +40,7 @@
var canShowRunningApps =
DesktopModeStatus.canEnterDesktopMode(context) &&
- DesktopModeFlags.TASKBAR_RUNNING_APPS.isEnabled(context)
+ DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS.isTrue
@VisibleForTesting
set(isEnabledFromTest) {
field = isEnabledFromTest
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 9b7d3bf..8991965 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -42,7 +42,6 @@
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.app.RemoteAction;
-import android.graphics.Rect;
import android.graphics.drawable.Icon;
import android.os.SystemClock;
import android.util.Log;
@@ -62,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;
@@ -82,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)} */
@@ -131,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.
@@ -164,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.
@@ -186,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.
@@ -209,7 +214,6 @@
* by not scaling the height of the taskbar background.
*/
private static final int TRANSITION_UNSTASH_SUW_MANUAL = 3;
- private static final Rect EMPTY_RECT = new Rect();
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
@@ -254,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
@@ -446,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);
@@ -771,7 +780,7 @@
}
fullLengthAnimatorSet.play(mControllers.stashedHandleViewController
- .createRevealAnimToIsStashed(isStashed, EMPTY_RECT));
+ .createRevealAnimToIsStashed(isStashed));
// Return the stashed handle to its default scale in case it was changed as part of the
// feedforward hint. Note that the reveal animation above also visually scales it.
fullLengthAnimatorSet.play(mTaskbarStashedHandleHintScale.animateToValue(1f));
@@ -801,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) {
@@ -821,19 +830,6 @@
}
}
-
- Rect taskbarToHotseatOffsets = new Rect();
- if (enableScalingRevealHomeAnimation() && animationType == TRANSITION_HOME_TO_APP) {
- Rect hotseatRect = new Rect();
- mActivity.getHotseatBounds(hotseatRect);
-
- // Calculate and store offsets so that we can sync with the taskbar stashed handle
- taskbarToHotseatOffsets.set(
- mActivity.calculateTaskbarToHotseatOffsets(hotseatRect));
- as.addListener(AnimatorListeners.forEndCallback(
- () -> mActivity.calculateTaskbarToHotseatOffsets(EMPTY_RECT)));
- }
-
play(as, mTaskbarStashedHandleAlpha.animateToValue(stashedHandleAlphaTarget),
backgroundAndHandleAlphaStartDelay,
backgroundAndHandleAlphaDuration, LINEAR);
@@ -882,12 +878,10 @@
}
mControllers.taskbarViewController.addRevealAnimToIsStashed(skippable, isStashed, duration,
- EMPHASIZED, animationType == TRANSITION_UNSTASH_SUW_MANUAL,
- animationType == TRANSITION_HOME_TO_APP);
+ EMPHASIZED, animationType == TRANSITION_UNSTASH_SUW_MANUAL);
play(skippable, mControllers.stashedHandleViewController
- .createRevealAnimToIsStashed(isStashed, taskbarToHotseatOffsets), 0, duration,
- EMPHASIZED);
+ .createRevealAnimToIsStashed(isStashed), 0, duration, EMPHASIZED);
// Return the stashed handle to its default scale in case it was changed as part of the
// feedforward hint. Note that the reveal animation above also visually scales it.
@@ -968,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 =
@@ -980,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);
+ }
});
}
@@ -1052,6 +1053,9 @@
*/
@Nullable
public Animator createApplyStateAnimator(long duration) {
+ if (mActivity.isPhoneMode()) {
+ return null;
+ }
return mStatePropertyHolder.createSetStateAnimator(mState, duration);
}
@@ -1084,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;
@@ -1097,10 +1102,6 @@
/** Called when some system ui state has changed. (See SYSUI_STATE_... in QuickstepContract) */
public void updateStateForSysuiFlags(long systemUiStateFlags, boolean skipAnim) {
- if (mActivity.isPhoneMode()) {
- return;
- }
-
long animDuration = TASKBAR_STASH_DURATION;
long startDelay = 0;
@@ -1144,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;
@@ -1164,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.
*
@@ -1289,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) {
@@ -1317,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 9163246..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,7 +435,21 @@
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() {
}
+
+ /**
+ * Called when we want to unstash taskbar when user performs swipes up gesture.
+ */
+ public void onSwipeToUnstashTaskbar() {
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 0389a11..8763509 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -46,6 +46,7 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
import com.android.launcher3.Insettable;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -63,6 +64,7 @@
import com.android.launcher3.util.LauncherBindableItemsContainer;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.IconButtonView;
import com.android.quickstep.util.DesktopTask;
import com.android.quickstep.util.GroupTask;
import com.android.systemui.shared.recents.model.Task;
@@ -100,9 +102,12 @@
// Only non-null when device supports having an All Apps button.
@Nullable private final TaskbarAllAppsButtonContainer mAllAppsButtonContainer;
- // Only non-null when device supports having an All Apps button.
+ // Only non-null when device supports having a Divider button.
@Nullable private TaskbarDividerContainer mTaskbarDividerContainer;
+ // Only non-null when device supports having a Taskbar Overflow button.
+ @Nullable private IconButtonView mTaskbarOverflowView;
+
/**
* Whether the divider is between Hotseat icons and Recents,
* instead of between All Apps button and Hotseat.
@@ -115,6 +120,8 @@
private boolean mShouldTryStartAlign;
+ private final int mMaxNumIcons;
+
public TaskbarView(@NonNull Context context) {
this(context, null);
}
@@ -171,8 +178,27 @@
mTaskbarDividerContainer = new TaskbarDividerContainer(context);
}
+ if (Flags.taskbarOverflow()) {
+ mTaskbarOverflowView = (IconButtonView) LayoutInflater.from(context)
+ .inflate(R.layout.taskbar_overflow_button, this, false);
+ mTaskbarOverflowView.setIconDrawable(
+ resources.getDrawable(R.drawable.taskbar_overflow_icon));
+ mTaskbarOverflowView.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
+ }
// 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
@@ -263,6 +289,12 @@
if (mTaskbarDividerContainer != null && callbacks.supportsDividerLongPress()) {
mTaskbarDividerContainer.setUpCallbacks(callbacks);
}
+ if (mTaskbarOverflowView != null) {
+ mTaskbarOverflowView.setOnClickListener(
+ mControllerCallbacks.getOverflowOnClickListener());
+ mTaskbarOverflowView.setOnLongClickListener(
+ mControllerCallbacks.getOverflowOnLongClickListener());
+ }
}
private void removeAndRecycle(View view) {
@@ -290,6 +322,9 @@
removeView(mTaskbarDividerContainer);
}
}
+ if (mTaskbarOverflowView != null) {
+ removeView(mTaskbarOverflowView);
+ }
removeView(mQsb);
// Add Hotseat icons.
@@ -377,6 +412,9 @@
if (mTaskbarDividerContainer != null && !recentTasks.isEmpty()) {
addView(mTaskbarDividerContainer, nextViewIndex++);
mAddedDividerForRecents = true;
+ if (mTaskbarOverflowView != null) {
+ addView(mTaskbarOverflowView, nextViewIndex++);
+ }
}
// Add Recent/Running icons.
@@ -449,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.
@@ -456,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.
@@ -699,6 +779,14 @@
}
/**
+ * Returns the taskbar overflow view in the taskbar.
+ */
+ @Nullable
+ public IconButtonView getTaskbarOverflowView() {
+ return mTaskbarOverflowView;
+ }
+
+ /**
* Returns whether the divider is between Hotseat icons and Recents,
* instead of between All Apps button and Hotseat.
*/
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
index 5c8d439..176be1c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
@@ -134,4 +134,27 @@
return Flags.enableBubbleBarInPersistentTaskBar()
&& mControllers.bubbleControllers.isPresent();
}
+
+ /** Returns on click listener for the taskbar overflow view. */
+ public View.OnClickListener getOverflowOnClickListener() {
+ return new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mControllers.keyboardQuickSwitchController.openQuickSwitchView(
+ mControllers.taskbarViewController.getTaskIdsForPinnedApps());
+ }
+ };
+ }
+
+ /** Returns on long click listener for the taskbar overflow view. */
+ public View.OnLongClickListener getOverflowOnLongClickListener() {
+ return new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ 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 b663ccb..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;
@@ -36,7 +37,6 @@
import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_ALIGNMENT_ANIM;
import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_PINNING_ANIM;
import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_REVEAL_ANIM;
-import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
import android.animation.Animator;
import android.animation.AnimatorSet;
@@ -82,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;
@@ -140,10 +142,6 @@
private final AnimatedFloat mTaskbarIconTranslationYForPinning = new AnimatedFloat(
this::updateTranslationY);
- private final View.OnLayoutChangeListener mTaskbarViewLayoutChangeListener =
- (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom)
- -> updateTaskbarIconTranslationXForPinning();
-
private AnimatedFloat mTaskbarNavButtonTranslationY;
private AnimatedFloat mTaskbarNavButtonTranslationYForInAppDisplay;
@@ -158,6 +156,12 @@
// Initialized in init.
private TaskbarControllers mControllers;
+ private final View.OnLayoutChangeListener mTaskbarViewLayoutChangeListener =
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+ updateTaskbarIconTranslationXForPinning();
+ mControllers.navbarButtonsViewController.onTaskbarLayoutChanged();
+ };
+
// Animation to align icons with Launcher, created lazily. This allows the controller to be
// active only during the animation and does not need to worry about layout changes.
private AnimatorPlaybackController mIconAlignControllerLazy = null;
@@ -325,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();
}
/**
@@ -628,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,
@@ -669,8 +692,7 @@
* @param interpolator The interpolator to use for all animations.
*/
public void addRevealAnimToIsStashed(AnimatorSet as, boolean isStashed, long duration,
- Interpolator interpolator, boolean dispatchOnAnimationStart,
- boolean isHomeToAppAnimation) {
+ Interpolator interpolator, boolean dispatchOnAnimationStart) {
AnimatorSet reveal = new AnimatorSet();
Rect stashedBounds = new Rect();
@@ -719,21 +741,8 @@
reveal.play(ObjectAnimator.ofFloat(mtd.getTranslationX(INDEX_TASKBAR_REVEAL_ANIM),
MULTI_PROPERTY_VALUE, transX)
.setDuration(duration));
-
- if (enableScalingRevealHomeAnimation()) {
- // Delay y-translation by 1 frame to keep icons within the bounds of the bg.
- int delay = isHomeToAppAnimation ? getSingleFrameMs(mActivity) : 0;
- ObjectAnimator yAnimator =
- ObjectAnimator.ofFloat(mtd.getTranslationY(INDEX_TASKBAR_REVEAL_ANIM),
- MULTI_PROPERTY_VALUE, transY)
- .setDuration(Math.max(0, duration - delay));
- yAnimator.setStartDelay(delay);
- reveal.play(yAnimator);
- } else {
- reveal.play(
- ObjectAnimator.ofFloat(mtd.getTranslationY(INDEX_TASKBAR_REVEAL_ANIM),
- MULTI_PROPERTY_VALUE, transY));
- }
+ reveal.play(ObjectAnimator.ofFloat(mtd.getTranslationY(INDEX_TASKBAR_REVEAL_ANIM),
+ MULTI_PROPERTY_VALUE, transY));
as.addListener(forEndCallback(() ->
mtd.setTranslation(INDEX_TASKBAR_REVEAL_ANIM, 0, 0)));
} else {
@@ -825,10 +834,19 @@
: 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();
boolean isTaskbarDividerView = child == mTaskbarView.getTaskbarDividerViewContainer();
+ boolean isTaskbarOverflowView = child == mTaskbarView.getTaskbarOverflowView();
boolean isRecentTask = child.getTag() instanceof GroupTask;
// TODO(b/343522351): show recents on the home screen.
final boolean isRecentsInHotseat = false;
@@ -839,7 +857,8 @@
setter.setViewAlpha(child, 0, Interpolators.clampToProgress(LINEAR, 0.8f, 1f));
} else if ((isAllAppsButton && !FeatureFlags.enableAllAppsButtonInHotseat())
|| (isTaskbarDividerView && enableTaskbarPinning())
- || (isRecentTask && !isRecentsInHotseat)) {
+ || (isRecentTask && !isRecentsInHotseat)
+ || isTaskbarOverflowView) {
if (!isToHome
&& mIsHotseatIconOnTopWhenAligned
&& mIsStashed) {
@@ -858,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)
@@ -876,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);
@@ -924,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
new file mode 100644
index 0000000..bc562a6
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarSwipeController.kt
@@ -0,0 +1,246 @@
+/*
+ * 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
+
+import android.animation.ValueAnimator
+import android.content.Context
+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
+
+/** Handle swipe events on the bubble bar and handle */
+class BubbleBarSwipeController {
+
+ private val context: Context
+
+ private var bubbleStashedHandleViewController: BubbleStashedHandleViewController? = null
+ private lateinit var bubbleBarViewController: BubbleBarViewController
+ private lateinit var bubbleStashController: BubbleStashController
+
+ private var springAnimation: ValueAnimator? = null
+ private val animatedSwipeTranslation = AnimatedFloat(this::onSwipeUpdate)
+
+ private val unstashThreshold: Int
+ private val expandThreshold: Int
+ private val maxOverscroll: Int
+ private val stashThreshold: Int
+
+ private var swipeState: SwipeState = SwipeState()
+
+ constructor(tac: TaskbarActivityContext) : this(tac, DefaultDimensionProvider(tac))
+
+ @VisibleForTesting
+ constructor(context: Context, dimensionProvider: DimensionProvider) {
+ this.context = context
+ unstashThreshold = dimensionProvider.unstashThreshold
+ expandThreshold = dimensionProvider.expandThreshold
+ maxOverscroll = dimensionProvider.maxOverscroll
+ stashThreshold = dimensionProvider.stashThreshold
+ }
+
+ fun init(bubbleControllers: BubbleControllers) {
+ bubbleStashedHandleViewController =
+ bubbleControllers.bubbleStashedHandleViewController.orElse(null)
+ bubbleBarViewController = bubbleControllers.bubbleBarViewController
+ bubbleStashController = bubbleControllers.bubbleStashController
+ }
+
+ /** Start tracking a new swipe gesture */
+ fun start() {
+ if (springAnimation != null) reset()
+ 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) {
+ if (!canHandleSwipe(dy)) {
+ return
+ }
+ animatedSwipeTranslation.updateValue(dy)
+
+ val prevState = swipeState
+ // We can pass unstash threshold once per gesture, keep it true if it happened once
+ 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 ||
+ passedStashThreshold != prevState.passedStashThreshold
+ ) {
+ swipeState =
+ swipeState.copy(
+ passedUnstashThreshold = passedUnstashThreshold,
+ passedExpandThreshold = passedExpandThreshold,
+ passedStashThreshold = passedStashThreshold,
+ )
+ }
+
+ if (
+ swipeState.startState == STASHED &&
+ swipeState.passedUnstashThreshold &&
+ !prevState.passedUnstashThreshold
+ ) {
+ bubbleStashController.showBubbleBar(expandBubbles = false)
+ }
+ }
+
+ /** Finish tracking swipe gesture. Animate views back to resting state */
+ fun finish() {
+ when {
+ swipeState.passedExpandThreshold &&
+ swipeState.startState in setOf(STASHED, COLLAPSED) -> {
+ bubbleStashController.showBubbleBar(expandBubbles = true)
+ }
+ swipeState.passedStashThreshold && swipeState.startState == COLLAPSED -> {
+ bubbleStashController.stashBubbleBar()
+ }
+ }
+ if (animatedSwipeTranslation.value == 0f) {
+ reset()
+ } else {
+ springToRest()
+ }
+ }
+
+ /** Returns `true` if we are tracking a swipe gesture */
+ fun isSwipeGesture(): Boolean {
+ 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 {
+ return dy < -unstashThreshold
+ }
+
+ private fun isExpand(dy: Float): Boolean {
+ return dy < -expandThreshold
+ }
+
+ private fun isStash(dy: Float): Boolean {
+ return dy > stashThreshold
+ }
+
+ private fun reset() {
+ springAnimation?.let {
+ if (it.isRunning) {
+ it.removeAllListeners()
+ it.cancel()
+ animatedSwipeTranslation.updateValue(0f)
+ }
+ }
+ springAnimation = null
+ swipeState = SwipeState()
+ }
+
+ private fun onSwipeUpdate(value: Float) {
+ val dampedSwipe = -OverScroll.dampedScroll(-value, maxOverscroll).toFloat()
+ bubbleStashedHandleViewController?.setTranslationYForSwipe(dampedSwipe)
+ bubbleBarViewController.setTranslationYForSwipe(dampedSwipe)
+ }
+
+ private fun springToRest() {
+ springAnimation =
+ SpringAnimationBuilder(context)
+ .setStartValue(animatedSwipeTranslation.value)
+ .setEndValue(0f)
+ .setDampingRatio(SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY)
+ .setStiffness(SpringForce.STIFFNESS_LOW)
+ .build(animatedSwipeTranslation, AnimatedFloat.VALUE)
+ .also { it.doOnEnd { reset() } }
+ springAnimation?.start()
+ }
+
+ internal data class SwipeState(
+ 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) :
+ DimensionProvider {
+ override val unstashThreshold: Int
+ override val expandThreshold: Int
+ override val maxOverscroll: Int
+ override val stashThreshold: Int
+
+ init {
+ val resources = taskbarActivityContext.resources
+ unstashThreshold =
+ TaskbarThresholdUtils.getFromNavThreshold(
+ resources,
+ taskbarActivityContext.deviceProfile,
+ )
+ // TODO(325673340): review threshold with ux
+ expandThreshold =
+ TaskbarThresholdUtils.getAppWindowThreshold(
+ resources,
+ 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 a66df4c..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;
@@ -40,6 +41,7 @@
public final BubbleDismissController bubbleDismissController;
public final BubbleBarPinController bubbleBarPinController;
public final BubblePinController bubblePinController;
+ public final Optional<BubbleBarSwipeController> bubbleBarSwipeController;
public final BubbleCreator bubbleCreator;
private final RunnableList mPostInitRunnables = new RunnableList();
@@ -58,6 +60,7 @@
BubbleDismissController bubbleDismissController,
BubbleBarPinController bubbleBarPinController,
BubblePinController bubblePinController,
+ Optional<BubbleBarSwipeController> bubbleBarSwipeController,
BubbleCreator bubbleCreator) {
this.bubbleBarController = bubbleBarController;
this.bubbleBarViewController = bubbleBarViewController;
@@ -67,6 +70,7 @@
this.bubbleDismissController = bubbleDismissController;
this.bubbleBarPinController = bubbleBarPinController;
this.bubblePinController = bubblePinController;
+ this.bubbleBarSwipeController = bubbleBarSwipeController;
this.bubbleCreator = bubbleCreator;
}
@@ -76,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,
@@ -111,6 +115,7 @@
bubbleDismissController.init(/* bubbleControllers = */ this);
bubbleBarPinController.init(this, bubbleBarLocationListeners);
bubblePinController.init(this);
+ bubbleBarSwipeController.ifPresent(c -> c.init(this));
mPostInitRunnables.executeAllAndDestroy();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleCreator.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleCreator.java
index 12b1487..340a120 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleCreator.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleCreator.java
@@ -44,8 +44,6 @@
import android.view.LayoutInflater;
import android.view.ViewGroup;
-import androidx.appcompat.content.res.AppCompatResources;
-
import com.android.internal.graphics.ColorUtils;
import com.android.launcher3.R;
import com.android.launcher3.icons.BitmapInfo;
@@ -196,8 +194,7 @@
}
private Bitmap createOverflowBitmap() {
- Drawable iconDrawable = AppCompatResources.getDrawable(mContext,
- R.drawable.bubble_ic_overflow_button);
+ Drawable iconDrawable = mContext.getDrawable(R.drawable.bubble_ic_overflow_button);
final TypedArray ta = mContext.obtainStyledAttributes(
new int[]{
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 152dcf7..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]. */
@@ -28,12 +29,12 @@
) {
private var flyout: BubbleBarFlyoutView? = null
- val horizontalMargin =
+ private val horizontalMargin =
container.context.resources.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin)
fun setUpFlyout(message: BubbleBarFlyoutMessage) {
flyout?.let(container::removeView)
- val flyout = BubbleBarFlyoutView(container.context)
+ 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 d3dc3f8..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,15 +20,29 @@
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) : ConstraintLayout(context) {
+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) }
@@ -39,9 +53,36 @@
private val message: TextView by
lazy(LazyThreadSafetyMode.NONE) { findViewById(R.id.bubble_flyout_text) }
- private val flyoutHorizontalPadding by
+ private val flyoutPadding by
lazy(LazyThreadSafetyMode.NONE) {
- context.resources.getDimensionPixelSize(R.dimen.bubblebar_flyout_padding_horizontal)
+ context.resources.getDimensionPixelSize(R.dimen.bubblebar_flyout_padding)
+ }
+
+ private val triangleHeight by
+ lazy(LazyThreadSafetyMode.NONE) {
+ context.resources.getDimensionPixelSize(R.dimen.bubblebar_flyout_triangle_height)
+ }
+
+ private val triangleOverlap by
+ lazy(LazyThreadSafetyMode.NONE) {
+ context.resources.getDimensionPixelSize(
+ R.dimen.bubblebar_flyout_triangle_overlap_amount
+ )
+ }
+
+ private val triangleWidth by
+ lazy(LazyThreadSafetyMode.NONE) {
+ context.resources.getDimensionPixelSize(R.dimen.bubblebar_flyout_triangle_width)
+ }
+
+ private val triangleRadius by
+ lazy(LazyThreadSafetyMode.NONE) {
+ context.resources.getDimensionPixelSize(R.dimen.bubblebar_flyout_triangle_radius)
+ }
+
+ private val minFlyoutWidth by
+ lazy(LazyThreadSafetyMode.NONE) {
+ context.resources.getDimensionPixelSize(R.dimen.bubblebar_flyout_min_width)
}
private val maxFlyoutWidth by
@@ -49,8 +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
@@ -66,20 +142,65 @@
ta.recycle()
setWillNotDraw(false)
- clipChildren = false
+ clipChildren = true
clipToPadding = false
- val horizontalPadding =
- context.resources.getDimensionPixelSize(R.dimen.bubblebar_flyout_padding_horizontal)
- val verticalPadding =
- context.resources.getDimensionPixelSize(R.dimen.bubblebar_flyout_padding_vertical)
- setPadding(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding)
- translationZ =
- context.resources.getDimensionPixelSize(R.dimen.bubblebar_flyout_elevation).toFloat()
+ 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 = flyoutElevation
+
+ RoundedArrowDrawable.addDownPointingRoundedTriangleToPath(
+ triangleWidth.toFloat(),
+ triangleHeight.toFloat(),
+ 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
@@ -88,32 +209,151 @@
avatar.visibility = GONE
}
- val maxTextViewWidth = maxFlyoutWidth - flyoutHorizontalPadding * 2
+ val minTextViewWidth: Int
+ val maxTextViewWidth: Int
+ if (avatar.visibility == VISIBLE) {
+ minTextViewWidth = minFlyoutWidth - avatar.width - flyoutPadding * 2
+ maxTextViewWidth = maxFlyoutWidth - avatar.width - flyoutPadding * 2
+ } else {
+ // when there's no avatar, the width of the text view is constant, so we're setting the
+ // min and max to the same value
+ minTextViewWidth = minFlyoutWidth - flyoutPadding * 2
+ maxTextViewWidth = minTextViewWidth
+ }
+
if (flyoutMessage.senderName.isEmpty()) {
sender.visibility = GONE
} else {
+ sender.minWidth = minTextViewWidth
sender.maxWidth = maxTextViewWidth
sender.text = flyoutMessage.senderName
sender.visibility = VISIBLE
}
+ message.minWidth = minTextViewWidth
message.maxWidth = maxTextViewWidth
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) {
+ // 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,
+ )
+
+ 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(
- 0f,
- 0f,
- width.toFloat(),
- height.toFloat(),
- cornerRadius,
- cornerRadius,
+ backgroundRect,
+ currentCornerRadius,
+ currentCornerRadius,
backgroundPaint,
)
+ if (expansionProgress >= minExpansionProgressForTriangle) {
+ drawTriangle(canvas)
+ }
+ canvas.restore()
+ invalidateOutline()
super.onDraw(canvas)
}
+ private fun drawTriangle(canvas: Canvas) {
+ canvas.save()
+ 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/TransientBubbleStashController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
index 4f0337d..74e3c00 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
@@ -32,8 +32,8 @@
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.Companion.BAR_STASH_DURATION
@@ -305,7 +305,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 +318,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/taskbar/overlay/TaskbarOverlayDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java
index 773b0b9..669850c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java
@@ -73,7 +73,7 @@
@Override
public void recreateControllers() {
List<TouchController> controllers = new ArrayList<>();
- controllers.add(mActivity.getDragController());
+ controllers.add(mContainer.getDragController());
controllers.addAll(mTouchControllers);
mControllers = controllers.toArray(new TouchController[0]);
}
@@ -87,7 +87,7 @@
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getAction() == ACTION_UP && event.getKeyCode() == KEYCODE_BACK) {
- AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
+ AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mContainer);
if (topView != null && topView.canHandleBack()) {
topView.onBackInvoked();
return true;
@@ -96,7 +96,7 @@
&& event.getKeyCode() == KeyEvent.KEYCODE_ESCAPE && event.hasNoModifiers()) {
// Ignore escape if pressed in conjunction with any modifier keys. Close each
// floating view one at a time for each key press.
- AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
+ AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mContainer);
if (topView != null) {
topView.close(/* animate= */ true);
return true;
@@ -107,7 +107,7 @@
@Override
public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
- if (mActivity.isAnySystemDragInProgress()) {
+ if (mContainer.isAnySystemDragInProgress()) {
inoutInfo.touchableRegion.setEmpty();
inoutInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
}
@@ -123,7 +123,7 @@
@Override
public void onViewRemoved(View child) {
super.onViewRemoved(child);
- mActivity.getOverlayController().maybeCloseWindow();
+ mContainer.getOverlayController().maybeCloseWindow();
}
/** Adds a {@link TouchController} to this drag layer. */
@@ -147,14 +147,14 @@
* 2) Sets tappableInsets bottom inset to 0.
*/
private WindowInsets updateInsetsDueToStashing(WindowInsets oldInsets) {
- if (!DisplayController.isTransientTaskbar(mActivity)) {
+ if (!DisplayController.isTransientTaskbar(mContainer)) {
return oldInsets;
}
WindowInsets.Builder updatedInsetsBuilder = new WindowInsets.Builder(oldInsets);
Insets oldNavInsets = oldInsets.getInsets(WindowInsets.Type.navigationBars());
Insets newNavInsets = Insets.of(oldNavInsets.left, oldNavInsets.top, oldNavInsets.right,
- mActivity.getStashedTaskbarHeight());
+ mContainer.getStashedTaskbarHeight());
updatedInsetsBuilder.setInsets(WindowInsets.Type.navigationBars(), newNavInsets);
Insets oldTappableInsets = oldInsets.getInsets(WindowInsets.Type.tappableElement());
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/QuickstepInteractionHandler.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
index 93cbdc7..26a1322 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
@@ -29,7 +29,6 @@
import android.widget.RemoteViews;
import android.window.SplashScreen;
-import com.android.launcher3.Utilities;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.ActivityOptionsWrapper;
@@ -66,7 +65,7 @@
Pair<Intent, ActivityOptions> options = remoteResponse.getLaunchOptions(view);
ActivityOptionsWrapper activityOptions = mLauncher.getAppTransitionManager()
.getActivityLaunchOptions(hostView, (ItemInfo) hostView.getTag());
- if (Utilities.ATLEAST_S && !pendingIntent.isActivity()) {
+ if (!pendingIntent.isActivity()) {
// In the event this pending intent eventually launches an activity, i.e. a trampoline,
// use the Quickstep transition animation.
try {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 1f5cd3a..bc0ace2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -19,6 +19,7 @@
import static android.os.Trace.TRACE_TAG_APP;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
+import static android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY;
import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.internal.jank.Cuj.CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE;
@@ -36,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;
@@ -59,13 +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.desktopmode.DesktopModeFlags.WALLPAPER_ACTIVITY;
-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,10 +199,9 @@
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 kotlin.Unit;
-
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -214,6 +214,8 @@
import java.util.function.Predicate;
import java.util.stream.Stream;
+import kotlin.Unit;
+
public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
SystemShortcut.BubbleActivityStarter {
private static final boolean TRACE_LAYOUTS =
@@ -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
@@ -271,7 +274,7 @@
RecentsView<?,?> overviewPanel = getOverviewPanel();
SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.get(this);
mSplitSelectStateController =
- new SplitSelectStateController(this, mHandler, getStateManager(),
+ new SplitSelectStateController(this, getStateManager(),
getDepthController(), getStatsLogManager(),
systemUiProxy, RecentsModel.INSTANCE.get(this),
() -> onStateBack());
@@ -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;
@@ -681,10 +677,6 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
- // Back dispatcher is registered in {@link BaseActivity#onCreate}. For predictive back to
- // work, we must opt-in BEFORE registering back dispatcher. So we need to call
- // setEnableOnBackInvokedCallback() before super.onCreate()
- getApplicationInfo().setEnableOnBackInvokedCallback(true);
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
mPendingSplitSelectInfo = ObjectWrapper.unwrap(
@@ -1003,7 +995,7 @@
@Override
public void setResumed() {
DesktopVisibilityController desktopVisibilityController = getDesktopVisibilityController();
- if (!WALLPAPER_ACTIVITY.isEnabled(this)
+ if (!ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()
&& desktopVisibilityController != null
&& desktopVisibilityController.areDesktopTasksVisible()
&& !desktopVisibilityController.isRecentsGestureInProgress()) {
@@ -1105,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;
}
@@ -1345,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);
}
@@ -1410,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/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index 11e0ed5..1d9e492 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -45,6 +45,7 @@
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.contextualeducation.ContextualEduStatsManager;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.util.DisplayController;
@@ -53,6 +54,7 @@
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.OverviewToHomeAnim;
import com.android.quickstep.views.RecentsView;
+import com.android.systemui.contextualeducation.GestureType;
import java.util.function.BiConsumer;
@@ -219,6 +221,8 @@
}
if (mStartState != mEndState) {
logHomeGesture();
+ ContextualEduStatsManager.INSTANCE.get(mLauncher).updateEduStats(
+ mSwipeDetector.isTrackpadGesture(), GestureType.HOME);
}
AbstractFloatingView topOpenView = AbstractFloatingView.getTopOpenView(mLauncher);
if (topOpenView != null) {
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 240d6ad..dc7ed24 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -45,6 +45,7 @@
import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
+import static com.android.quickstep.BaseContainerInterface.AnimationFactory;
import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
@@ -55,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;
@@ -92,6 +89,7 @@
import android.view.animation.Interpolator;
import android.widget.Toast;
import android.window.PictureInPictureSurfaceTransaction;
+import android.window.flags.DesktopModeFlags;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -102,6 +100,7 @@
import com.android.internal.util.LatencyTracker;
import com.android.launcher3.AbstractFloatingView;
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.AnimationSuccessListener;
@@ -111,6 +110,7 @@
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
import com.android.launcher3.statemanager.BaseState;
+import com.android.launcher3.statemanager.StatefulContainer;
import com.android.launcher3.taskbar.TaskbarThresholdUtils;
import com.android.launcher3.taskbar.TaskbarUIController;
import com.android.launcher3.uioverrides.QuickstepLauncher;
@@ -119,11 +119,12 @@
import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.util.VibratorWrapper;
import com.android.launcher3.util.WindowBounds;
-import com.android.quickstep.BaseActivityInterface.AnimationFactory;
import com.android.quickstep.GestureState.GestureEndTarget;
import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
+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;
@@ -152,12 +153,9 @@
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.wm.shell.shared.TransactionPool;
-import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.shared.startingsurface.SplashScreenExitAnimationUtils;
-import kotlin.Unit;
-
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -167,15 +165,16 @@
import java.util.OptionalInt;
import java.util.function.Consumer;
+import kotlin.Unit;
+
/**
* Handles the navigation gestures when Launcher is the default home activity.
*/
public abstract class AbsSwipeUpHandler<
- RECENTS_CONTAINER extends Context & RecentsViewContainer,
- RECENTS_VIEW extends RecentsView<RECENTS_CONTAINER, STATE>,
- STATE extends BaseState<STATE>>
- extends SwipeUpAnimationLogic
- implements OnApplyWindowInsetsListener, RecentsAnimationCallbacks.RecentsAnimationListener {
+ RECENTS_CONTAINER extends Context & RecentsViewContainer & StatefulContainer<STATE>,
+ RECENTS_VIEW extends RecentsView<RECENTS_CONTAINER, STATE>, STATE extends BaseState<STATE>>
+ extends SwipeUpAnimationLogic implements OnApplyWindowInsetsListener,
+ RecentsAnimationCallbacks.RecentsAnimationListener {
private static final String TAG = "AbsSwipeUpHandler";
private static final ArrayList<String> STATE_NAMES = new ArrayList<>();
@@ -201,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);
@@ -292,7 +291,7 @@
private static final int LOG_NO_OP_PAGE_INDEX = -1;
protected final TaskAnimationManager mTaskAnimationManager;
-
+ protected final RecentsWindowManager mRecentsWindowManager;
// Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
private RunningWindowAnim[] mRunningWindowAnim;
// Possible second animation running at the same time as mRunningWindowAnim
@@ -353,9 +352,12 @@
public AbsSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
TaskAnimationManager taskAnimationManager, GestureState gestureState,
long touchTimeMs, boolean continuingLastGesture,
- InputConsumerController inputConsumer) {
+ InputConsumerController inputConsumer, RecentsWindowManager recentsWindowManager) {
super(context, deviceState, gestureState);
mContainerInterface = gestureState.getContainerInterface();
+ if (recentsWindowManager != null && Flags.enableFallbackOverviewInWindow()) {
+ recentsWindowManager.registerInitListener(this::onActivityInit);
+ }
mActivityInitListener =
mContainerInterface.createActivityInitListener(this::onActivityInit);
mInputConsumerProxy =
@@ -369,6 +371,7 @@
endLauncherTransitionController();
}, new InputProxyHandlerFactory(mContainerInterface, mGestureState));
mTaskAnimationManager = taskAnimationManager;
+ mRecentsWindowManager = recentsWindowManager;
mTouchTimeMs = touchTimeMs;
mContinuingLastGesture = continuingLastGesture;
@@ -819,7 +822,7 @@
return;
}
initTransitionEndpoints(mContainer.getDeviceProfile());
- mAnimationFactory.createActivityInterface(mTransitionDragLength);
+ mAnimationFactory.createContainerInterface(mTransitionDragLength);
}
/**
@@ -863,10 +866,13 @@
}
}
+ public Intent getHomeIntent() {
+ return mGestureState.getHomeIntent();
+ }
+
public Intent getLaunchIntent() {
return mGestureState.getOverviewIntent();
}
-
/**
* Called when the value of {@link #mCurrentShift} changes
*/
@@ -975,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
@@ -1145,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) {
@@ -1181,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. */
@@ -1200,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;
}
@@ -1218,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.
@@ -1256,8 +1252,8 @@
? mRecentsView.getCurrentPageTaskView() : null;
if (DesktopModeStatus.canEnterDesktopMode(mContext)
- && !(DesktopModeFlags.WALLPAPER_ACTIVITY.isEnabled(mContext)
- && DesktopModeFlags.QUICK_SWITCH.isEnabled(mContext))) {
+ && !(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()
+ && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH.isTrue())) {
if ((nextPageTaskView instanceof DesktopTaskView
|| currentPageTaskView instanceof DesktopTaskView)
&& endTarget == NEW_TASK) {
@@ -1423,8 +1419,8 @@
};
if (DesktopModeStatus.canEnterDesktopMode(mContext)
- && !(DesktopModeFlags.WALLPAPER_ACTIVITY.isEnabled(mContext)
- && DesktopModeFlags.QUICK_SWITCH.isEnabled(mContext))) {
+ && !(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()
+ && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH.isTrue())) {
if (mRecentsView != null && (mRecentsView.getCurrentPageTaskView() != null
&& !(mRecentsView.getCurrentPageTaskView() instanceof DesktopTaskView))) {
ActiveGestureLog.INSTANCE.trackEvent(ActiveGestureErrorDetector.GestureEvent
@@ -1981,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();
@@ -2249,8 +2243,8 @@
});
if (DesktopModeStatus.canEnterDesktopMode(mContext)
- && !(DesktopModeFlags.WALLPAPER_ACTIVITY.isEnabled(mContext)
- && DesktopModeFlags.QUICK_SWITCH.isEnabled(mContext))) {
+ && !(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()
+ && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH.isTrue())) {
if (mRecentsView.getNextPageTaskView() instanceof DesktopTaskView
|| mRecentsView.getCurrentPageTaskView() instanceof DesktopTaskView) {
mRecentsViewScrollLinked = false;
@@ -2284,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(
@@ -2304,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) {
@@ -2382,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;
}
@@ -2414,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;
}
@@ -2424,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;
}
@@ -2485,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/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 8703843..1e755eb 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -36,7 +36,6 @@
import android.view.MotionEvent;
import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
@@ -62,17 +61,14 @@
public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_TYPE>,
ACTIVITY_TYPE extends StatefulActivity<STATE_TYPE> & RecentsViewContainer> extends
BaseContainerInterface<STATE_TYPE, ACTIVITY_TYPE> {
- private final STATE_TYPE mBackgroundState;
private STATE_TYPE mTargetState;
- @Nullable private Runnable mOnInitBackgroundStateUICallback = null;
-
protected BaseActivityInterface(boolean rotationSupportedByActivity,
STATE_TYPE overviewState, STATE_TYPE backgroundState) {
+ super(backgroundState);
this.rotationSupportedByActivity = rotationSupportedByActivity;
mTargetState = overviewState;
- mBackgroundState = backgroundState;
}
/**
@@ -124,13 +120,6 @@
return activity != null && activity.isStarted();
}
- @UiThread
- @Nullable
- public abstract <T extends RecentsView> T getVisibleRecentsView();
-
- @UiThread
- public abstract boolean switchToRecentsIfVisible(Animator.AnimatorListener animatorListener);
-
public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) {
TaskbarUIController controller = getTaskbarController();
boolean isEventOverBubbleBarStashHandle =
@@ -163,49 +152,6 @@
recentsView.switchToScreenshot(thumbnailDatas, runnable);
}
-
- protected void runOnInitBackgroundStateUI(Runnable callback) {
- ACTIVITY_TYPE activity = getCreatedContainer();
- if (activity != null && activity.getStateManager().getState() == mBackgroundState) {
- callback.run();
- onInitBackgroundStateUI();
- return;
- }
- mOnInitBackgroundStateUICallback = callback;
- }
-
- private void onInitBackgroundStateUI() {
- if (mOnInitBackgroundStateUICallback != null) {
- mOnInitBackgroundStateUICallback.run();
- mOnInitBackgroundStateUICallback = null;
- }
- }
-
- public interface AnimationFactory {
-
- void createActivityInterface(long transitionLength);
-
- /**
- * @param attached Whether to show RecentsView alongside the app window. If false, recents
- * will be hidden by some property we can animate, e.g. alpha.
- * @param animate Whether to animate recents to/from its new attached state.
- * @param updateRunningTaskAlpha Whether to update the running task's attached alpha
- */
- default void setRecentsAttachedToAppWindow(
- boolean attached, boolean animate, boolean updateRunningTaskAlpha) { }
-
- default boolean isRecentsAttachedToAppWindow() {
- return false;
- }
-
- default boolean hasRecentsEverAttachedToAppWindow() {
- return false;
- }
-
- /** Called when the gesture ends and we know what state it is going towards */
- default void setEndTarget(GestureState.GestureEndTarget endTarget) { }
- }
-
class DefaultAnimationFactory implements AnimationFactory {
protected final ACTIVITY_TYPE mActivity;
@@ -234,7 +180,7 @@
}
@Override
- public void createActivityInterface(long transitionLength) {
+ public void createContainerInterface(long transitionLength) {
PendingAnimation pa = new PendingAnimation(transitionLength * 2);
createBackgroundToOverviewAnim(mActivity, pa);
AnimatorPlaybackController controller = pa.createPlaybackController();
diff --git a/quickstep/src/com/android/quickstep/BaseContainerInterface.java b/quickstep/src/com/android/quickstep/BaseContainerInterface.java
index bf3a662..14c2cc4 100644
--- a/quickstep/src/com/android/quickstep/BaseContainerInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseContainerInterface.java
@@ -34,16 +34,19 @@
import android.view.View;
import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Flags;
import com.android.launcher3.R;
import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.statemanager.BaseState;
+import com.android.launcher3.statemanager.StatefulContainer;
import com.android.launcher3.taskbar.TaskbarUIController;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.WindowBounds;
import com.android.launcher3.views.ScrimView;
+import com.android.quickstep.fallback.window.RecentsWindowManager;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.AnimatorControllerWithResistance;
@@ -57,13 +60,28 @@
import java.util.function.Predicate;
public abstract class BaseContainerInterface<STATE_TYPE extends BaseState<STATE_TYPE>,
- CONTAINER_TYPE extends RecentsViewContainer> {
+ CONTAINER_TYPE extends RecentsViewContainer & StatefulContainer<STATE_TYPE>> {
public boolean rotationSupportedByActivity = false;
+ protected final STATE_TYPE mBackgroundState;
+
+ protected BaseContainerInterface(STATE_TYPE backgroundState) {
+ mBackgroundState = backgroundState;
+ }
+
+ @UiThread
+ @Nullable
+ public abstract <T extends RecentsView<?,?>> T getVisibleRecentsView();
+
+ @UiThread
+ public abstract boolean switchToRecentsIfVisible(Animator.AnimatorListener animatorListener);
@Nullable
public abstract CONTAINER_TYPE getCreatedContainer();
+ @Nullable
+ protected Runnable mOnInitBackgroundStateUICallback = null;
+
public abstract boolean isInLiveTileMode();
public abstract void onAssistantVisibilityChanged(float assistantVisibility);
@@ -88,7 +106,32 @@
@Nullable
public abstract TaskbarUIController getTaskbarController();
- public abstract BaseActivityInterface.AnimationFactory prepareRecentsUI(
+ public interface AnimationFactory {
+
+ void createContainerInterface(long transitionLength);
+
+ /**
+ * @param attached Whether to show RecentsView alongside the app window. If false, recents
+ * will be hidden by some property we can animate, e.g. alpha.
+ * @param animate Whether to animate recents to/from its new attached state.
+ * @param updateRunningTaskAlpha Whether to update the running task's attached alpha
+ */
+ default void setRecentsAttachedToAppWindow(
+ boolean attached, boolean animate, boolean updateRunningTaskAlpha) { }
+
+ default boolean isRecentsAttachedToAppWindow() {
+ return false;
+ }
+
+ default boolean hasRecentsEverAttachedToAppWindow() {
+ return false;
+ }
+
+ /** Called when the gesture ends and we know what state it is going towards */
+ default void setEndTarget(GestureState.GestureEndTarget endTarget) { }
+ }
+
+ public abstract BaseContainerInterface.AnimationFactory prepareRecentsUI(
RecentsAnimationDeviceState deviceState, boolean activityVisible,
Consumer<AnimatorControllerWithResistance> callback);
@@ -126,6 +169,17 @@
return false;
}
+ public void runOnInitBackgroundStateUI(Runnable callback) {
+ StatefulContainer container = getCreatedContainer();
+ if (container != null
+ && container.getStateManager().getState() == mBackgroundState) {
+ callback.run();
+ onInitBackgroundStateUI();
+ return;
+ }
+ mOnInitBackgroundStateUICallback = callback;
+ }
+
@Nullable
public DesktopVisibilityController getDesktopVisibilityController() {
CONTAINER_TYPE container = getCreatedContainer();
@@ -403,4 +457,11 @@
outRect,
orientationHandler);
}
+
+ protected void onInitBackgroundStateUI() {
+ if (mOnInitBackgroundStateUICallback != null) {
+ mOnInitBackgroundStateUICallback.run();
+ mOnInitBackgroundStateUICallback = null;
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/BaseWindowInterface.java b/quickstep/src/com/android/quickstep/BaseWindowInterface.java
new file mode 100644
index 0000000..9d6e61d
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/BaseWindowInterface.java
@@ -0,0 +1,259 @@
+/*
+ * 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 static com.android.app.animation.Interpolators.ACCELERATE_2;
+import static com.android.app.animation.Interpolators.INSTANT;
+import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
+import static com.android.quickstep.AbsSwipeUpHandler.RECENTS_ATTACH_DURATION;
+import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM;
+import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_TRANSLATE_X_ANIM;
+import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
+import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
+import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
+import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.statehandlers.DepthController;
+import com.android.launcher3.taskbar.TaskbarUIController;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.NavigationMode;
+import com.android.quickstep.fallback.RecentsState;
+import com.android.quickstep.fallback.window.RecentsWindowManager;
+import com.android.quickstep.util.AnimatorControllerWithResistance;
+import com.android.quickstep.views.RecentsView;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+
+import java.util.HashMap;
+import java.util.Optional;
+import java.util.function.Consumer;
+
+/**
+ * Temporary utility class in place for differences needed between
+ * Recents in Window in Launcher vs Fallback
+ */
+public abstract class BaseWindowInterface extends
+ BaseContainerInterface<RecentsState, RecentsWindowManager> {
+
+ final String TAG = "BaseWindowInterface";
+ private RecentsState mTargetState;
+
+
+ protected BaseWindowInterface(RecentsState overviewState, RecentsState backgroundState) {
+ super(backgroundState);
+ mTargetState = overviewState;
+ }
+
+ @Nullable
+ public abstract RecentsWindowManager getCreatedContainer();
+
+ @Nullable
+ public DepthController getDepthController() {
+ return null;
+ }
+
+ public final boolean isResumed() {
+ return isStarted();
+ }
+
+ public final boolean isStarted() {
+ RecentsWindowManager windowManager = getCreatedContainer();
+ return windowManager != null && windowManager.isStarted();
+ }
+
+ public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) {
+ TaskbarUIController controller = getTaskbarController();
+ boolean isEventOverBubbleBarStashHandle =
+ controller != null && controller.isEventOverBubbleBarViews(ev);
+ return deviceState.isInDeferredGestureRegion(ev) || deviceState.isImeRenderingNavButtons()
+ || isTrackpadMultiFingerSwipe(ev) || isEventOverBubbleBarStashHandle;
+ }
+
+ /**
+ * Closes any overlays.
+ */
+ public void closeOverlay() {
+ Optional.ofNullable(getTaskbarController()).ifPresent(
+ TaskbarUIController::hideOverlayWindow);
+ }
+
+ public void switchRunningTaskViewToScreenshot(HashMap<Integer, ThumbnailData> thumbnailDatas,
+ Runnable runnable) {
+ RecentsWindowManager windowManager = getCreatedContainer();
+ if (windowManager == null) {
+ return;
+ }
+ RecentsView recentsView = windowManager.getOverviewPanel();
+ if (recentsView == null) {
+ if (runnable != null) {
+ runnable.run();
+ }
+ return;
+ }
+ recentsView.switchToScreenshot(thumbnailDatas, runnable);
+ }
+
+ /**
+ * todo: Create an abstract animation factory to handle both activity and window implementations
+ * todo: move new factory into BaseContainerInterface and cleanup.
+ */
+
+ class DefaultAnimationFactory implements AnimationFactory {
+
+ protected final RecentsWindowManager mRecentsWindowManager;
+ private final RecentsState mStartState;
+ private final Consumer<AnimatorControllerWithResistance> mCallback;
+
+ private boolean mIsAttachedToWindow;
+ private boolean mHasEverAttachedToWindow;
+
+ DefaultAnimationFactory(Consumer<AnimatorControllerWithResistance> callback) {
+ mCallback = callback;
+
+ mRecentsWindowManager = getCreatedContainer();
+ mStartState = mRecentsWindowManager.getStateManager().getState();
+ }
+
+ protected RecentsWindowManager initBackgroundStateUI() {
+ RecentsState resetState = mStartState;
+ if (mStartState.shouldDisableRestore()) {
+ resetState = mRecentsWindowManager.getStateManager().getRestState();
+ }
+ mRecentsWindowManager.getStateManager().setRestState(resetState);
+ mRecentsWindowManager.getStateManager().goToState(mBackgroundState, false);
+ onInitBackgroundStateUI();
+ return mRecentsWindowManager;
+ }
+
+ @Override
+ public void createContainerInterface(long transitionLength) {
+ PendingAnimation pa = new PendingAnimation(transitionLength * 2);
+ createBackgroundToOverviewAnim(mRecentsWindowManager, pa);
+ AnimatorPlaybackController controller = pa.createPlaybackController();
+ mRecentsWindowManager.getStateManager().setCurrentUserControlledAnimation(controller);
+
+ // Since we are changing the start position of the UI, reapply the state, at the end
+ controller.setEndAction(() -> {
+ mRecentsWindowManager.getStateManager().goToState(
+ controller.getInterpolatedProgress() > 0.5 ? mTargetState
+ : mBackgroundState,
+ /* animated= */ false);
+ });
+
+ RecentsView recentsView = mRecentsWindowManager.getOverviewPanel();
+ AnimatorControllerWithResistance controllerWithResistance =
+ AnimatorControllerWithResistance.createForRecents(controller,
+ mRecentsWindowManager, recentsView.getPagedViewOrientedState(),
+ mRecentsWindowManager.getDeviceProfile(), recentsView,
+ RECENTS_SCALE_PROPERTY, recentsView, TASK_SECONDARY_TRANSLATION);
+ mCallback.accept(controllerWithResistance);
+
+ // Creating the activity controller animation sometimes reapplies the launcher state
+ // (because we set the animation as the current state animation), so we reapply the
+ // attached state here as well to ensure recents is shown/hidden appropriately.
+ if (DisplayController.getNavigationMode(mRecentsWindowManager)
+ == NavigationMode.NO_BUTTON) {
+ setRecentsAttachedToAppWindow(mIsAttachedToWindow, false, false);
+ }
+ }
+
+ @Override
+ public void setRecentsAttachedToAppWindow(boolean attached, boolean animate,
+ boolean updateRunningTaskAlpha) {
+
+ if (mIsAttachedToWindow == attached && animate) {
+ return;
+ }
+ mRecentsWindowManager.getStateManager()
+ .cancelStateElementAnimation(INDEX_RECENTS_FADE_ANIM);
+ mRecentsWindowManager.getStateManager()
+ .cancelStateElementAnimation(INDEX_RECENTS_TRANSLATE_X_ANIM);
+
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ mIsAttachedToWindow = attached;
+ if (attached) {
+ mHasEverAttachedToWindow = true;
+ }
+ }});
+
+ long animationDuration = animate ? RECENTS_ATTACH_DURATION : 0;
+ Animator fadeAnim = mRecentsWindowManager.getStateManager()
+ .createStateElementAnimation(INDEX_RECENTS_FADE_ANIM, attached ? 1 : 0);
+ fadeAnim.setInterpolator(attached ? INSTANT : ACCELERATE_2);
+ fadeAnim.setDuration(animationDuration);
+ animatorSet.play(fadeAnim);
+
+ float fromTranslation = ADJACENT_PAGE_HORIZONTAL_OFFSET.get(
+ mRecentsWindowManager.getOverviewPanel());
+ float toTranslation = attached ? 0 : 1;
+
+ Animator translationAnimator =
+ mRecentsWindowManager.getStateManager().createStateElementAnimation(
+ INDEX_RECENTS_TRANSLATE_X_ANIM, fromTranslation, toTranslation);
+ translationAnimator.setDuration(animationDuration);
+ animatorSet.play(translationAnimator);
+ animatorSet.start();
+ }
+
+ @Override
+ public boolean isRecentsAttachedToAppWindow() {
+ return mIsAttachedToWindow;
+ }
+
+ @Override
+ public boolean hasRecentsEverAttachedToAppWindow() {
+ return mHasEverAttachedToWindow;
+ }
+
+ @Override
+ public void setEndTarget(GestureState.GestureEndTarget endTarget) {
+ mTargetState = stateFromGestureEndTarget(endTarget);
+ }
+
+ protected void createBackgroundToOverviewAnim(RecentsWindowManager container,
+ PendingAnimation pa) {
+ // Scale down recents from being full screen to being in overview.
+ RecentsView recentsView = container.getOverviewPanel();
+ pa.addFloat(recentsView, RECENTS_SCALE_PROPERTY,
+ recentsView.getMaxScaleForFullScreen(), 1, LINEAR);
+ pa.addFloat(recentsView, FULLSCREEN_PROGRESS, 1, 0, LINEAR);
+
+ pa.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ TaskbarUIController taskbarUIController = getTaskbarController();
+ if (taskbarUIController != null) {
+ taskbarUIController.setSystemGestureInProgress(true);
+ }
+ }
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index 9b66154..7abcfb8 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -64,6 +64,7 @@
import com.android.launcher3.util.DisplayController;
import com.android.quickstep.fallback.FallbackRecentsView;
import com.android.quickstep.fallback.RecentsState;
+import com.android.quickstep.fallback.window.RecentsWindowManager;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties;
@@ -82,7 +83,7 @@
* Handles the navigation gestures when a 3rd party launcher is the default home activity.
*/
public class FallbackSwipeHandler extends
- AbsSwipeUpHandler<RecentsActivity, FallbackRecentsView, RecentsState> {
+ AbsSwipeUpHandler<RecentsActivity, FallbackRecentsView<RecentsActivity>, RecentsState> {
private static final String TAG = "FallbackSwipeHandler";
@@ -105,7 +106,7 @@
TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
boolean continuingLastGesture, InputConsumerController inputConsumer) {
super(context, deviceState, taskAnimationManager, gestureState, touchTimeMs,
- continuingLastGesture, inputConsumer);
+ continuingLastGesture, inputConsumer, null);
mRunningOverHome = mGestureState.getRunningTask() != null
&& mGestureState.getRunningTask().isHomeTask();
diff --git a/quickstep/src/com/android/quickstep/FallbackWindowInterface.java b/quickstep/src/com/android/quickstep/FallbackWindowInterface.java
new file mode 100644
index 0000000..ea478dd
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/FallbackWindowInterface.java
@@ -0,0 +1,259 @@
+/*
+ * 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 static com.android.launcher3.util.NavigationMode.NO_BUTTON;
+import static com.android.quickstep.fallback.RecentsState.BACKGROUND_APP;
+import static com.android.quickstep.fallback.RecentsState.DEFAULT;
+import static com.android.quickstep.fallback.RecentsState.HOME;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.content.Context;
+import android.graphics.Rect;
+import android.view.MotionEvent;
+import android.view.RemoteAnimationTarget;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.taskbar.FallbackTaskbarUIController;
+import com.android.launcher3.util.DisplayController;
+import com.android.quickstep.GestureState.GestureEndTarget;
+import com.android.quickstep.fallback.RecentsState;
+import com.android.quickstep.fallback.window.RecentsWindowManager;
+import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
+import com.android.quickstep.util.ActivityInitListener;
+import com.android.quickstep.util.AnimatorControllerWithResistance;
+import com.android.quickstep.views.RecentsView;
+
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+/**
+ * {@link BaseWindowInterface} for recents when the default launcher is different than the
+ * currently running one and apps should interact with the {@link RecentsWindowManager} as opposed
+ * to the in-launcher one.
+ */
+public final class FallbackWindowInterface extends BaseWindowInterface{
+
+ private static FallbackWindowInterface INSTANCE;
+
+ private final RecentsWindowManager mRecentsWindowManager;
+
+ /**
+ * This is only null before init() or after destroy()
+ */
+ @Nullable
+ public static FallbackWindowInterface getInstance(){
+ return INSTANCE;
+ }
+
+ public static void init(RecentsWindowManager recentsWindowManager) {
+ if (INSTANCE == null) {
+ INSTANCE = new FallbackWindowInterface(recentsWindowManager);
+ }
+ }
+
+ private FallbackWindowInterface(RecentsWindowManager recentsWindowManager) {
+ super(DEFAULT, BACKGROUND_APP);
+ mRecentsWindowManager = recentsWindowManager;
+ }
+
+ public void destroy() {
+ INSTANCE = null;
+ }
+
+ /** 2 */
+ @Override
+ public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect,
+ RecentsPagedOrientationHandler orientationHandler) {
+ calculateTaskSize(context, dp, outRect, orientationHandler);
+ if (dp.isVerticalBarLayout() && DisplayController.getNavigationMode(context) != NO_BUTTON) {
+ return dp.isSeascape() ? outRect.left : (dp.widthPx - outRect.right);
+ } else {
+ return dp.heightPx - outRect.bottom;
+ }
+ }
+
+ /** 5 */
+ @Override
+ public void onAssistantVisibilityChanged(float visibility) {
+ // This class becomes active when the screen is locked.
+ // Rather than having it handle assistant visibility changes, the assistant visibility is
+ // set to zero prior to this class becoming active.
+ }
+
+ /** 6 */
+ @Override
+ public BaseWindowInterface.AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState
+ deviceState, boolean activityVisible,
+ Consumer<AnimatorControllerWithResistance> callback) {
+ notifyRecentsOfOrientation(deviceState.getRotationTouchHelper());
+ BaseWindowInterface.DefaultAnimationFactory factory =
+ new BaseWindowInterface.DefaultAnimationFactory(callback);
+ factory.initBackgroundStateUI();
+ return factory;
+ }
+
+ @Override
+ public ActivityInitListener createActivityInitListener(
+ Predicate<Boolean> onInitListener) {
+ //todo figure out how to properly replace this
+ return new ActivityInitListener<>((activity, alreadyOnHome) ->
+ onInitListener.test(alreadyOnHome), RecentsActivity.ACTIVITY_TRACKER);
+ }
+
+ @Nullable
+ @Override
+ public RecentsWindowManager getCreatedContainer() {
+ return mRecentsWindowManager;
+ }
+
+ @Override
+ public FallbackTaskbarUIController getTaskbarController() {
+ RecentsWindowManager manager = getCreatedContainer();
+ if (manager == null) {
+ return null;
+ }
+ return null;
+ // todo b/365775636: pass a taskbar implementation
+ // return manager.getTaskbarUIController();
+ }
+
+ @Override
+ public Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTarget target) {
+ // TODO: Remove this once b/77875376 is fixed
+ return target.screenSpaceBounds;
+ }
+
+ @Nullable
+ @Override
+ public <T extends RecentsView<?, ?>> T getVisibleRecentsView() {
+ RecentsWindowManager manager = getCreatedContainer();
+ if(manager.isStarted() || isInLiveTileMode()){
+ return getCreatedContainer().getOverviewPanel();
+ }
+ return null;
+ }
+
+ @Override
+ public boolean switchToRecentsIfVisible(Animator.AnimatorListener animatorListener) {
+ return false;
+ }
+
+ @Override
+ protected int getOverviewScrimColorForState(RecentsWindowManager container,
+ RecentsState state) {
+ return state.getScrimColor(container.asContext());
+ }
+
+ @Override
+ public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) {
+ // In non-gesture mode, user might be clicking on the home button which would directly
+ // start the home activity instead of going through recents. In that case, defer starting
+ // recents until we are sure it is a gesture.
+ return false;
+// return !deviceState.isFullyGesturalNavMode();
+// || super.deferStartingActivity(deviceState, ev);
+ }
+
+ @Override
+ public void onExitOverview(RotationTouchHelper deviceState, Runnable exitRunnable) {
+ final StateManager<RecentsState, RecentsWindowManager> stateManager =
+ getCreatedContainer().getStateManager();
+ if (stateManager.getState() == HOME) {
+ exitRunnable.run();
+ notifyRecentsOfOrientation(deviceState);
+ return;
+ }
+
+ stateManager.addStateListener(
+ new StateManager.StateListener<RecentsState>() {
+ @Override
+ public void onStateTransitionComplete(RecentsState toState) {
+ // Are we going from Recents to Workspace?
+ if (toState == HOME) {
+ exitRunnable.run();
+ notifyRecentsOfOrientation(deviceState);
+ stateManager.removeStateListener(this);
+ }
+ }
+ });
+ }
+
+ @Override
+ public boolean isInLiveTileMode() {
+ RecentsWindowManager windowManager = getCreatedContainer();
+ return windowManager != null && windowManager.getStateManager().getState() == DEFAULT &&
+ windowManager.isStarted();
+ }
+
+ @Override
+ public void onLaunchTaskFailed() {
+ // TODO: probably go back to overview instead.
+ RecentsWindowManager manager = getCreatedContainer();
+ if (manager == null) {
+ return;
+ }
+ manager.<RecentsView>getOverviewPanel().startHome();
+ }
+
+ @Override
+ public RecentsState stateFromGestureEndTarget(GestureEndTarget endTarget) {
+ switch (endTarget) {
+ case RECENTS:
+ return DEFAULT;
+ case NEW_TASK:
+ case LAST_TASK:
+ return BACKGROUND_APP;
+ case HOME:
+ case ALL_APPS:
+ default:
+ return HOME;
+ }
+ }
+
+ private void notifyRecentsOfOrientation(RotationTouchHelper rotationTouchHelper) {
+ // reset layout on swipe to home
+ RecentsView recentsView = getCreatedContainer().getOverviewPanel();
+ recentsView.setLayoutRotation(rotationTouchHelper.getCurrentActiveRotation(),
+ rotationTouchHelper.getDisplayRotation());
+ }
+
+ @Override
+ public @Nullable Animator getParallelAnimationToLauncher(GestureEndTarget endTarget,
+ long duration, RecentsAnimationCallbacks callbacks) {
+ FallbackTaskbarUIController uiController = getTaskbarController();
+ Animator superAnimator = super.getParallelAnimationToLauncher(
+ endTarget, duration, callbacks);
+ if (uiController == null) {
+ return superAnimator;
+ }
+ RecentsState toState = stateFromGestureEndTarget(endTarget);
+ Animator taskbarAnimator = uiController.createAnimToRecentsState(toState, duration);
+ if (taskbarAnimator == null) {
+ return superAnimator;
+ }
+ if (superAnimator == null) {
+ return taskbarAnimator;
+ }
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.playTogether(superAnimator, taskbarAnimator);
+ return animatorSet;
+ }
+}
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 9cc463a..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;
@@ -38,9 +37,11 @@
import androidx.annotation.Nullable;
import com.android.launcher3.statemanager.BaseState;
+import com.android.launcher3.statemanager.StatefulContainer;
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;
@@ -190,7 +191,7 @@
public GestureState(OverviewComponentObserver componentObserver, int gestureId) {
mHomeIntent = componentObserver.getHomeIntent();
mOverviewIntent = componentObserver.getOverviewIntent();
- mContainerInterface = componentObserver.getActivityInterface();
+ mContainerInterface = componentObserver.getContainerInterface();
mStateCallback = new MultiStateCallback(
STATE_NAMES.toArray(new String[0]), GestureState::getTrackedEventForState);
mGestureId = gestureId;
@@ -269,8 +270,8 @@
/**
* @return the interface to the activity handing the UI updates for this gesture.
*/
- public <S extends BaseState<S>, T extends RecentsViewContainer>
- BaseContainerInterface<S, T> getContainerInterface() {
+ public <S extends BaseState<S>, T extends RecentsViewContainer & StatefulContainer<S>>
+ BaseContainerInterface getContainerInterface() {
return mContainerInterface;
}
@@ -410,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/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index d2dcd7b..dacafd4 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -46,6 +46,7 @@
import com.android.launcher3.views.FloatingIconView;
import com.android.launcher3.views.FloatingView;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
+import com.android.quickstep.fallback.window.RecentsWindowManager;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.ScalingWorkspaceRevealAnim;
import com.android.quickstep.util.StaggeredWorkspaceAnim;
@@ -68,7 +69,7 @@
TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
boolean continuingLastGesture, InputConsumerController inputConsumer) {
super(context, deviceState, taskAnimationManager, gestureState, touchTimeMs,
- continuingLastGesture, inputConsumer);
+ continuingLastGesture, inputConsumer, null);
}
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 f92c557..a33e5c0 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
@@ -45,8 +45,8 @@
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.RecentsViewContainer
import com.android.quickstep.views.TaskView
import com.android.systemui.shared.recents.model.ThumbnailData
import com.android.systemui.shared.system.InteractionJankMonitorWrapper
@@ -80,19 +80,11 @@
*/
private var keyboardTaskFocusIndex = -1
- /**
- * Whether we should incoming toggle commands while a previous toggle command is still ongoing.
- * This serves as a rate-limiter to prevent overlapping animations that can clobber each other
- * and prevent clean-up callbacks from running. This thus prevents a recurring set of bugs with
- * janky recents animations and unresponsive home and overview buttons.
- */
- private var waitForToggleCommandComplete = false
-
- private val activityInterface: BaseActivityInterface<*, *>
- get() = overviewComponentObserver.activityInterface
+ private val containerInterface: BaseContainerInterface<*, *>
+ get() = overviewComponentObserver.containerInterface
private val visibleRecentsView: RecentsView<*, *>?
- get() = activityInterface.getVisibleRecentsView<RecentsView<*, *>>()
+ get() = containerInterface.getVisibleRecentsView<RecentsView<*, *>>()
/**
* Adds a command to be executed next, after all pending tasks are completed. Max commands that
@@ -174,12 +166,6 @@
*/
@VisibleForTesting
fun executeCommand(command: CommandInfo, onCallbackResult: () -> Unit): Boolean {
- // This shouldn't happen if we execute 1 command per time.
- if (waitForToggleCommandComplete && command.type == TOGGLE) {
- Log.d(TAG, "executeCommand: $command - waiting for toggle command complete")
- return true
- }
-
val recentsView = visibleRecentsView
Log.d(TAG, "executeCommand: $command - visibleRecentsView: $recentsView")
return if (recentsView != null) {
@@ -251,7 +237,6 @@
): Boolean {
var callbackList: RunnableList? = null
if (taskView != null) {
- waitForToggleCommandComplete = true
taskView.isEndQuickSwitchCuj = true
callbackList = taskView.launchWithAnimation()
}
@@ -260,13 +245,11 @@
callbackList.add {
Log.d(TAG, "launching task callback: $command")
onCallbackResult()
- waitForToggleCommandComplete = false
}
Log.d(TAG, "launching task - waiting for callback: $command")
return false
} else {
recents.startHome()
- waitForToggleCommandComplete = false
return true
}
}
@@ -275,10 +258,10 @@
command: CommandInfo,
onCallbackResult: () -> Unit,
): Boolean {
- val recentsViewContainer = activityInterface.getCreatedContainer() as? RecentsViewContainer
+ val recentsViewContainer = containerInterface.getCreatedContainer()
val recentsView: RecentsView<*, *>? = recentsViewContainer?.getOverviewPanel()
val deviceProfile = recentsViewContainer?.getDeviceProfile()
- val uiController = activityInterface.getTaskbarController()
+ val uiController = containerInterface.getTaskbarController()
val allowQuickSwitch =
uiController != null &&
deviceProfile != null &&
@@ -298,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
@@ -333,13 +316,13 @@
onCallbackResult()
}
}
- if (activityInterface.switchToRecentsIfVisible(animatorListener)) {
+ if (containerInterface.switchToRecentsIfVisible(animatorListener)) {
Log.d(TAG, "switching to Overview state - waiting: $command")
// If successfully switched, wait until animation finishes
return false
}
- val activity = activityInterface.getCreatedContainer()
+ val activity = containerInterface.getCreatedContainer()
if (activity != null) {
InteractionJankMonitorWrapper.begin(activity.rootView, Cuj.CUJ_LAUNCHER_QUICK_SWITCH)
}
@@ -369,7 +352,7 @@
Log.d(TAG, "recents animation started: $command")
updateRecentsViewFocus(command)
logShowOverviewFrom(command.type)
- activityInterface.runOnInitBackgroundStateUI {
+ containerInterface.runOnInitBackgroundStateUI {
Log.d(TAG, "recents animation started - onInitBackgroundStateUI: $command")
interactionHandler.onGestureEnded(0f, PointF())
}
@@ -383,7 +366,7 @@
interactionHandler.onGestureCancelled()
command.removeListener(this)
- activityInterface.getCreatedContainer() ?: return
+ containerInterface.getCreatedContainer() ?: return
recentsView?.onRecentsAnimationComplete()
}
}
@@ -490,7 +473,7 @@
}
private fun logShowOverviewFrom(commandType: CommandType) {
- val container = activityInterface.getCreatedContainer() as? RecentsViewContainer ?: return
+ val container = containerInterface.getCreatedContainer() ?: return
val event =
when (commandType) {
SHOW -> LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_SHORTCUT
@@ -517,7 +500,6 @@
pw.println(" pendingCommandType=${commandQueue.first().type}")
}
pw.println(" keyboardTaskFocusIndex=$keyboardTaskFocusIndex")
- pw.println(" waitForToggleCommandComplete=$waitForToggleCommandComplete")
}
@VisibleForTesting
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index ca19480..66112c1 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -39,9 +39,10 @@
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
+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;
@@ -73,7 +74,7 @@
private Consumer<Boolean> mOverviewChangeListener = b -> { };
private String mUpdateRegisteredPackage;
- private BaseActivityInterface mActivityInterface;
+ private BaseContainerInterface mContainerInterface;
private Intent mOverviewIntent;
private boolean mIsHomeAndOverviewSame;
private boolean mIsDefaultHome;
@@ -150,8 +151,8 @@
// Set assistant visibility to 0 from launcher's perspective, ensures any elements that
// launcher made invisible become visible again before the new activity control helper
// becomes active.
- if (mActivityInterface != null) {
- mActivityInterface.onAssistantVisibilityChanged(0.f);
+ if (mContainerInterface != null) {
+ mContainerInterface.onAssistantVisibilityChanged(0.f);
}
if (SEPARATE_RECENTS_ACTIVITY.get()) {
@@ -168,7 +169,7 @@
if (!mIsHomeDisabled && (defaultHome == null || mIsDefaultHome)) {
// User default home is same as out home app. Use Overview integrated in Launcher.
- mActivityInterface = LauncherActivityInterface.INSTANCE;
+ mContainerInterface = LauncherActivityInterface.INSTANCE;
mIsHomeAndOverviewSame = true;
mOverviewIntent = mMyHomeIntent;
mCurrentHomeIntent.setComponent(mMyHomeIntent.getComponent());
@@ -178,7 +179,11 @@
} else {
// The default home app is a different launcher. Use the fallback Overview instead.
- mActivityInterface = FallbackActivityInterface.INSTANCE;
+ if (Flags.enableFallbackOverviewInWindow()) {
+ mContainerInterface = FallbackWindowInterface.getInstance();
+ } else {
+ mContainerInterface = FallbackActivityInterface.INSTANCE;
+ }
mIsHomeAndOverviewSame = false;
mOverviewIntent = mFallbackIntent;
mCurrentHomeIntent.setComponent(defaultHome);
@@ -266,21 +271,12 @@
}
/**
- * Get the current activity control helper for managing interactions to the overview activity.
+ * Get the current control helper for managing interactions to the overview container.
*
- * @return the current activity control helper
+ * @return the current control helper
*/
- public BaseActivityInterface getActivityInterface() {
- return mActivityInterface;
- }
-
- /**
- * Get the current container control helper for managing interactions to the overview activity.
- *
- * @return the current container control helper
- */
- public BaseContainerInterface<?, ?> getContainerInterface() {
- return mActivityInterface;
+ public BaseContainerInterface<?,?> getContainerInterface() {
+ return mContainerInterface;
}
public void dump(PrintWriter pw) {
@@ -309,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/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 49b6f57..c3b9736 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -7,6 +7,7 @@
import android.content.Context;
import android.content.res.Resources;
import android.os.Bundle;
+import android.view.WindowInsets;
import androidx.annotation.Nullable;
@@ -203,11 +204,12 @@
}
@Override
- protected Activity getCurrentActivity() {
+ protected WindowInsets getWindowInsets() {
RecentsAnimationDeviceState rads = new RecentsAnimationDeviceState(mContext);
OverviewComponentObserver observer = new OverviewComponentObserver(mContext, rads);
try {
- return observer.getActivityInterface().getCreatedContainer();
+ return observer.getContainerInterface()
+ .getCreatedContainer().getRootView().getRootWindowInsets();
} finally {
observer.onDestroy();
rads.destroy();
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 9c60693..9ac4141 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -122,9 +122,6 @@
// Strong refs to runners which are cleared when the activity is destroyed
private RemoteAnimationFactory mActivityLaunchAnimationRunner;
- // For handling degenerate cases where starting an activity doesn't actually trigger the remote
- // animation callback
- private final Handler mHandler = new Handler();
private final Runnable mAnimationStartTimeoutRunnable = this::onAnimationStartTimeout;
private SplitSelectStateController mSplitSelectStateController;
@Nullable
@@ -137,7 +134,7 @@
SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.get(this);
// SplitSelectStateController needs to be created before setContentView()
mSplitSelectStateController =
- new SplitSelectStateController(this, mHandler, getStateManager(),
+ new SplitSelectStateController(this, getStateManager(),
null /* depthController */, getStatsLogManager(),
systemUiProxy, RecentsModel.INSTANCE.get(this),
null /*activityBackCallback*/);
@@ -198,7 +195,7 @@
}
@Override
- protected void onHandleConfigurationChanged() {
+ public void onHandleConfigurationChanged() {
initDeviceProfile();
AbstractFloatingView.closeOpenViews(this, true,
@@ -352,7 +349,6 @@
@Override
protected void onStop() {
super.onStop();
-
// Workaround for b/78520668, explicitly trim memory once UI is hidden
onTrimMemory(TRIM_MEMORY_UI_HIDDEN);
mFallbackRecentsView.updateLocusId();
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 b4bd3e3..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;
@@ -51,9 +50,11 @@
import android.window.RemoteTransition;
import android.window.TaskSnapshot;
import android.window.TransitionFilter;
+import android.window.flags.DesktopModeFlags;
import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
import com.android.internal.logging.InstanceId;
@@ -62,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;
@@ -85,14 +86,13 @@
import com.android.wm.shell.desktopmode.IDesktopTaskListener;
import com.android.wm.shell.draganddrop.IDragAndDrop;
import com.android.wm.shell.onehanded.IOneHanded;
-import com.android.wm.shell.recents.IRecentsAnimationController;
-import com.android.wm.shell.recents.IRecentsAnimationRunner;
import com.android.wm.shell.recents.IRecentTasks;
import com.android.wm.shell.recents.IRecentTasksListener;
+import com.android.wm.shell.recents.IRecentsAnimationController;
+import com.android.wm.shell.recents.IRecentsAnimationRunner;
import com.android.wm.shell.shared.GroupedRecentTaskInfo;
import com.android.wm.shell.shared.IShellTransitions;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
-import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.shared.split.SplitBounds;
@@ -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
@@ -1395,7 +1402,7 @@
private boolean shouldEnableRunningTasksForDesktopMode() {
return DesktopModeStatus.canEnterDesktopMode(mContext)
- && DesktopModeFlags.TASKBAR_RUNNING_APPS.isEnabled(mContext);
+ && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS.isTrue();
}
private boolean handleMessageAsync(Message msg) {
@@ -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 289a2c1..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;
@@ -43,11 +42,13 @@
import androidx.annotation.UiThread;
import com.android.internal.util.ArrayUtils;
+import com.android.launcher3.Flags;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.taskbar.TaskbarUIController;
import com.android.launcher3.util.DisplayController;
-import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.fallback.window.RecentsWindowManager;
+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;
@@ -64,6 +65,7 @@
SystemProperties.getBoolean("persist.wm.debug.shell_transit_rotate", false);
private final Context mCtx;
+ private RecentsWindowManager mRecentsWindowsManager;
private RecentsAnimationController mController;
private RecentsAnimationCallbacks mCallbacks;
private RecentsAnimationTargets mTargets;
@@ -98,10 +100,10 @@
}
};
- TaskAnimationManager(Context ctx) {
+ TaskAnimationManager(Context ctx, RecentsWindowManager manager) {
mCtx = ctx;
+ mRecentsWindowsManager = manager;
}
-
SystemUiProxy getSystemUiProxy() {
return SystemUiProxy.INSTANCE.get(mCtx);
}
@@ -134,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";
@@ -166,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) {
@@ -212,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);
@@ -224,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);
@@ -273,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 */,
@@ -299,39 +292,46 @@
mCallbacks.addListener(listener);
final ActivityOptions options = ActivityOptions.makeBasic();
- options.setPendingIntentBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
- options.setTransientLaunch();
- options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_RECENTS_ANIMATION, eventTime);
- // Notify taskbar that we should skip reacting to launcher visibility change to
- // avoid a jumping taskbar.
- TaskbarUIController taskbarUIController = containerInterface.getTaskbarController();
- if (enableScalingRevealHomeAnimation() && taskbarUIController != null) {
- taskbarUIController.setSkipLauncherVisibilityChange(true);
+ // TODO:(b/365777482) if flag is enabled, but on launcher it will crash.
+ if(containerInterface.getCreatedContainer() instanceof RecentsWindowManager
+ && Flags.enableFallbackOverviewInWindow()){
+ mRecentsAnimationStartPending =
+ getSystemUiProxy().startRecentsActivity(intent, options, mCallbacks);
+ mRecentsWindowsManager.startRecentsWindow();
+ } else {
+ options.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
+ options.setTransientLaunch();
+ options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_RECENTS_ANIMATION, eventTime);
- mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() {
- @Override
- public void onRecentsAnimationCanceled(
- @NonNull HashMap<Integer, ThumbnailData> thumbnailDatas) {
- taskbarUIController.setSkipLauncherVisibilityChange(false);
- }
+ // Notify taskbar that we should skip reacting to launcher visibility change to
+ // avoid a jumping taskbar.
+ TaskbarUIController taskbarUIController = containerInterface.getTaskbarController();
+ if (enableScalingRevealHomeAnimation() && taskbarUIController != null) {
+ taskbarUIController.setSkipLauncherVisibilityChange(true);
- @Override
- public void onRecentsAnimationFinished(
- @NonNull RecentsAnimationController controller) {
- taskbarUIController.setSkipLauncherVisibilityChange(false);
- }
- });
+ mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() {
+ @Override
+ public void onRecentsAnimationCanceled(
+ @NonNull HashMap<Integer, ThumbnailData> thumbnailDatas) {
+ taskbarUIController.setSkipLauncherVisibilityChange(false);
+ }
+
+ @Override
+ public void onRecentsAnimationFinished(
+ @NonNull RecentsAnimationController controller) {
+ taskbarUIController.setSkipLauncherVisibilityChange(false);
+ }
+ });
+ }
+
+ mRecentsAnimationStartPending = getSystemUiProxy()
+ .startRecentsActivity(intent, options, mCallbacks);
}
-
- mRecentsAnimationStartPending = getSystemUiProxy()
- .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;
@@ -341,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);
@@ -423,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 */);
@@ -461,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;
@@ -487,6 +485,10 @@
mTargets = null;
mLastGestureState = null;
mLastAppearedTaskTargets = null;
+
+ if(Flags.enableFallbackOverviewInWindow()) {
+ mRecentsWindowsManager.cleanup();
+ }
}
@Nullable
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/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index f4ff4b2..8e45767 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -173,9 +173,8 @@
protected T getActionsView() {
if (mActionsView == null) {
- mActionsView = BaseActivity.fromContext(
- mTaskContainer.getTaskView().getContext()).findViewById(
- R.id.overview_actions_view);
+ mActionsView = (T) RecentsViewContainer.containerFromContext(
+ mTaskContainer.getTaskView().getContext()).getActionsView();
}
return mActionsView;
}
diff --git a/quickstep/src/com/android/quickstep/TaskUtils.java b/quickstep/src/com/android/quickstep/TaskUtils.java
index 63e536a..49f7daf 100644
--- a/quickstep/src/com/android/quickstep/TaskUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskUtils.java
@@ -31,8 +31,8 @@
import androidx.annotation.Nullable;
import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.util.ApplicationInfoWrapper;
import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.TraceHelper;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -70,8 +70,8 @@
return "";
}
UserHandle user = UserHandle.of(userId);
- ApplicationInfo applicationInfo = PackageManagerHelper.INSTANCE.get(context)
- .getApplicationInfo(packageName, user, 0);
+ ApplicationInfo applicationInfo =
+ new ApplicationInfoWrapper(context, packageName, user).getInfo();
if (applicationInfo == null) {
Log.e(TAG, "Failed to get title for userId=" + userId + ", packageName=" + packageName);
return "";
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index d8063ba..07ee479 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -59,7 +59,6 @@
import com.android.app.animation.Interpolators;
import com.android.internal.jank.Cuj;
-import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.anim.AnimationSuccessListener;
@@ -68,6 +67,7 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.statemanager.StatefulContainer;
import com.android.launcher3.taskbar.TaskbarUIController;
import com.android.launcher3.util.DisplayController;
import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
@@ -80,6 +80,7 @@
import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.GroupedTaskView;
import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.RecentsViewContainer;
import com.android.quickstep.views.TaskView;
import com.android.systemui.animation.RemoteAnimationTargetCompat;
import com.android.systemui.shared.recents.model.Task;
@@ -155,8 +156,9 @@
return taskView;
}
- public static void createRecentsWindowAnimator(
- @NonNull RecentsView recentsView,
+ public static <T extends Context & RecentsViewContainer & StatefulContainer<?>>
+ void createRecentsWindowAnimator(
+ @NonNull RecentsView<T, ?> recentsView,
@NonNull TaskView v,
boolean skipViewChanges,
@NonNull RemoteAnimationTarget[] appTargets,
@@ -204,8 +206,9 @@
int taskIndex = recentsView.indexOfChild(v);
Context context = v.getContext();
- BaseActivity baseActivity = BaseActivity.fromContext(context);
- DeviceProfile dp = baseActivity.getDeviceProfile();
+
+ T container = RecentsViewContainer.containerFromContext(context);
+ DeviceProfile dp = container.getDeviceProfile();
boolean showAsGrid = dp.isTablet;
boolean parallaxCenterAndAdjacentTask =
!showAsGrid && taskIndex != recentsView.getCurrentPage();
@@ -417,7 +420,8 @@
if (depthController != null) {
out.setFloat(depthController.stateDepth, MULTI_PROPERTY_VALUE,
- BACKGROUND_APP.getDepth(baseActivity), TOUCH_RESPONSE);
+ BACKGROUND_APP.getDepth(container),
+ TOUCH_RESPONSE);
}
}
diff --git a/quickstep/src/com/android/quickstep/TopTaskTracker.java b/quickstep/src/com/android/quickstep/TopTaskTracker.java
index 3cf0542..23a1ec7 100644
--- a/quickstep/src/com/android/quickstep/TopTaskTracker.java
+++ b/quickstep/src/com/android/quickstep/TopTaskTracker.java
@@ -206,12 +206,17 @@
Collections.addAll(mOrderedTaskList, tasks);
}
- // Strip the pinned task
ArrayList<RunningTaskInfo> tasks = new ArrayList<>(mOrderedTaskList);
- tasks.removeIf(t -> t.taskId == mPinnedTaskId);
+ // Strip the pinned task and recents task
+ tasks.removeIf(t -> t.taskId == mPinnedTaskId || isRecentsTask(t));
return new CachedTaskInfo(tasks);
}
+ private static boolean isRecentsTask(RunningTaskInfo task) {
+ return task != null && task.configuration.windowConfiguration
+ .getActivityType() == ACTIVITY_TYPE_RECENTS;
+ }
+
/**
* Class to provide information about a task which can be safely cached and do not change
* during the lifecycle of the task.
@@ -267,8 +272,7 @@
}
public boolean isRecentsTask() {
- return mTopTask != null && mTopTask.configuration.windowConfiguration
- .getActivityType() == ACTIVITY_TYPE_RECENTS;
+ return TopTaskTracker.isRecentsTask(mTopTask);
}
/**
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 178636e..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;
@@ -76,6 +71,7 @@
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.MotionEvent;
+import android.view.View;
import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
@@ -83,7 +79,6 @@
import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting;
-import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.ConstantItem;
import com.android.launcher3.EncryptionType;
import com.android.launcher3.Flags;
@@ -107,6 +102,8 @@
import com.android.launcher3.util.ScreenOnTracker;
import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.OverviewCommandHelper.CommandType;
+import com.android.quickstep.fallback.window.RecentsWindowManager;
+import com.android.quickstep.fallback.window.RecentsWindowSwipeHandler;
import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
import com.android.quickstep.inputconsumers.AssistantInputConsumer;
import com.android.quickstep.inputconsumers.BubbleBarInputConsumer;
@@ -124,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;
@@ -324,10 +322,10 @@
@Override
public void enterStageSplitFromRunningApp(boolean leftOrTop) {
executeForTouchInteractionService(tis -> {
- StatefulActivity activity =
- tis.mOverviewComponentObserver.getActivityInterface().getCreatedContainer();
- if (activity != null) {
- activity.enterStageSplitFromRunningApp(leftOrTop);
+ RecentsViewContainer container = tis.mOverviewComponentObserver
+ .getContainerInterface().getCreatedContainer();
+ if (container != null) {
+ container.enterStageSplitFromRunningApp(leftOrTop);
}
});
}
@@ -606,6 +604,8 @@
this::createLauncherSwipeHandler;
private final AbsSwipeUpHandler.Factory mFallbackSwipeHandlerFactory =
this::createFallbackSwipeHandler;
+ private final AbsSwipeUpHandler.Factory mRecentsWindowSwipeHandlerFactory =
+ this::createRecentsWindowSwipeHandler;
private final ScreenOnTracker.ScreenOnListener mScreenOnListener = this::onScreenOnChanged;
@@ -638,6 +638,7 @@
private InputEventReceiver mInputEventReceiver;
private TaskbarManager mTaskbarManager;
+ private RecentsWindowManager mRecentsWindowManager;
private Function<GestureState, AnimatedFloat> mSwipeUpProxyProvider = i -> null;
private AllAppsActionManager mAllAppsActionManager;
private InputManager mInputManager;
@@ -650,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();
@@ -668,6 +671,9 @@
mDesktopVisibilityController = new DesktopVisibilityController(this);
mTaskbarManager = new TaskbarManager(
this, mAllAppsActionManager, mNavCallbacks, mDesktopVisibilityController);
+ if(Flags.enableFallbackOverviewInWindow()) {
+ mRecentsWindowManager = new RecentsWindowManager(this);
+ }
mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
// Call runOnUserUnlocked() before any other callbacks to ensure everything is initialized.
@@ -680,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;
@@ -717,8 +724,9 @@
@UiThread
public void onUserUnlocked() {
- Log.d(TAG, "onUserUnlocked: userId=" + getUserId());
- mTaskAnimationManager = new TaskAnimationManager(this);
+ Log.d(TAG, "onUserUnlocked: userId=" + getUserId()
+ + " instance=" + System.identityHashCode(this));
+ mTaskAnimationManager = new TaskAnimationManager(this, mRecentsWindowManager);
mOverviewComponentObserver = new OverviewComponentObserver(this, mDeviceState);
mOverviewCommandHelper = new OverviewCommandHelper(this,
mOverviewComponentObserver, mTaskAnimationManager);
@@ -761,11 +769,12 @@
private void onOverviewTargetChange(boolean isHomeAndOverviewSame) {
mAllAppsActionManager.setHomeAndOverviewSame(isHomeAndOverviewSame);
-
- StatefulActivity<?> newOverviewActivity =
- mOverviewComponentObserver.getActivityInterface().getCreatedContainer();
- if (newOverviewActivity != null) {
- mTaskbarManager.setActivity(newOverviewActivity);
+ RecentsViewContainer newOverviewContainer =
+ mOverviewComponentObserver.getContainerInterface().getCreatedContainer();
+ if (newOverviewContainer != null
+ && newOverviewContainer instanceof StatefulActivity activity) {
+ //TODO(b/368030750) refactor taskbarManager to accept RecentsViewContainer
+ mTaskbarManager.setActivity(activity);
}
mTISBinder.onOverviewTargetChange();
}
@@ -795,14 +804,15 @@
@UiThread
private void onAssistantVisibilityChanged() {
if (LockedUserState.get(this).isUserUnlocked()) {
- mOverviewComponentObserver.getActivityInterface().onAssistantVisibilityChanged(
+ mOverviewComponentObserver.getContainerInterface().onAssistantVisibilityChanged(
mDeviceState.getAssistantVisibility());
}
}
@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();
@@ -818,6 +828,10 @@
mTrackpadsConnected.clear();
mTaskbarManager.destroy();
+
+ if (mRecentsWindowManager != null) {
+ mRecentsWindowManager.destroy();
+ }
mDesktopVisibilityController.onDestroy();
sConnected = false;
@@ -827,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;
}
@@ -844,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;
@@ -855,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;
}
@@ -891,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;
}
@@ -911,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);
@@ -929,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);
@@ -957,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);
@@ -986,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()));
}
}
}
@@ -1075,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);
}
@@ -1110,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;
}
@@ -1150,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;
@@ -1167,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()) {
@@ -1183,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);
}
@@ -1198,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);
}
@@ -1211,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);
}
@@ -1224,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, ");
}
@@ -1239,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);
}
@@ -1316,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) {
@@ -1326,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);
}
@@ -1346,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.
@@ -1367,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);
}
@@ -1397,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) {
@@ -1410,28 +1383,30 @@
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);
}
}
public AbsSwipeUpHandler.Factory getSwipeUpHandlerFactory() {
- return !mOverviewComponentObserver.isHomeAndOverviewSame()
- ? mFallbackSwipeHandlerFactory : mLauncherSwipeHandlerFactory;
+ return mOverviewComponentObserver.isHomeAndOverviewSame()
+ ? mLauncherSwipeHandlerFactory : (Flags.enableFallbackOverviewInWindow()
+ ? mRecentsWindowSwipeHandlerFactory : mFallbackSwipeHandlerFactory);
}
private InputConsumer createOtherActivityInputConsumer(GestureState gestureState,
@@ -1450,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));
}
}
@@ -1475,35 +1448,35 @@
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));
}
- boolean hasWindowFocus = container.getRootView().hasWindowFocus();
+ View rootview = container.getRootView();
+ boolean hasWindowFocus = rootview != null && rootview.hasWindowFocus();
boolean isPreviousGestureAnimatingToLauncher =
previousGestureState.isRunningAnimationToLauncher()
|| 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);
@@ -1540,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;
@@ -1575,11 +1550,11 @@
return;
}
- final BaseActivityInterface activityInterface =
- mOverviewComponentObserver.getActivityInterface();
+ final BaseContainerInterface containerInterface =
+ mOverviewComponentObserver.getContainerInterface();
final Intent overviewIntent = new Intent(
mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState());
- if (activityInterface.getCreatedContainer() != null && fromInit) {
+ if (containerInterface.getCreatedContainer() != null && fromInit) {
// The activity has been created before the initialization of overview service. It is
// usually happens when booting or launcher is the top activity, so we should already
// have the latest state.
@@ -1589,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);
}
@@ -1599,18 +1573,18 @@
if (!LockedUserState.get(this).isUserUnlocked()) {
return;
}
- final BaseActivityInterface activityInterface =
- mOverviewComponentObserver.getActivityInterface();
- final BaseDraggingActivity activity = activityInterface.getCreatedContainer();
- if (activity == null || activity.isStarted()) {
+ final BaseContainerInterface containerInterface =
+ mOverviewComponentObserver.getContainerInterface();
+ final RecentsViewContainer container = containerInterface.getCreatedContainer();
+ if (container == null || container.isStarted()) {
// We only care about the existing background activity.
return;
}
- Configuration oldConfig = activity.getResources().getConfiguration();
+ Configuration oldConfig = container.asContext().getResources().getConfiguration();
boolean isFoldUnfold = isTablet(oldConfig) != isTablet(newConfig);
if (!isFoldUnfold && mOverviewComponentObserver.canHandleConfigChanges(
- activity.getComponentName(),
- activity.getResources().getConfiguration().diff(newConfig))) {
+ container.getComponentName(),
+ container.asContext().getResources().getConfiguration().diff(newConfig))) {
// Since navBar gestural height are different between portrait and landscape,
// can handle orientation changes and refresh navigation gestural region through
// onOneHandedModeChanged()
@@ -1649,11 +1623,11 @@
pw.println("\tmInputEventReceiver=" + mInputEventReceiver);
DisplayController.INSTANCE.get(this).dump(pw);
pw.println("TouchState:");
- BaseDraggingActivity createdOverviewActivity = mOverviewComponentObserver == null ? null
- : mOverviewComponentObserver.getActivityInterface().getCreatedContainer();
+ RecentsViewContainer createdOverviewContainer = mOverviewComponentObserver == null ? null
+ : mOverviewComponentObserver.getContainerInterface().getCreatedContainer();
boolean resumed = mOverviewComponentObserver != null
- && mOverviewComponentObserver.getActivityInterface().isResumed();
- pw.println("\tcreatedOverviewActivity=" + createdOverviewActivity);
+ && mOverviewComponentObserver.getContainerInterface().isResumed();
+ pw.println("\tcreatedOverviewActivity=" + createdOverviewContainer);
pw.println("\tresumed=" + resumed);
pw.println("\tmConsumer=" + mConsumer.getName());
ActiveGestureLog.INSTANCE.dump("", pw);
@@ -1661,8 +1635,8 @@
if (mTaskAnimationManager != null) {
mTaskAnimationManager.dump("", pw);
}
- if (createdOverviewActivity != null) {
- createdOverviewActivity.getDeviceProfile().dump(this, "", pw);
+ if (createdOverviewContainer != null) {
+ createdOverviewContainer.getDeviceProfile().dump(this, "", pw);
}
mTaskbarManager.dumpLogs("", pw);
mDesktopVisibilityController.dumpLogs("", pw);
@@ -1685,4 +1659,11 @@
gestureState, touchTimeMs, mTaskAnimationManager.isRecentsAnimationRunning(),
mInputConsumer);
}
+
+ private AbsSwipeUpHandler createRecentsWindowSwipeHandler(
+ GestureState gestureState, long touchTimeMs) {
+ return new RecentsWindowSwipeHandler(this, mDeviceState, mTaskAnimationManager,
+ gestureState, touchTimeMs, mTaskAnimationManager.isRecentsAnimationRunning(),
+ mInputConsumer, mRecentsWindowManager);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/dagger/QuickStepModule.java b/quickstep/src/com/android/quickstep/dagger/QuickStepModule.java
index db29636..08345b8 100644
--- a/quickstep/src/com/android/quickstep/dagger/QuickStepModule.java
+++ b/quickstep/src/com/android/quickstep/dagger/QuickStepModule.java
@@ -15,10 +15,8 @@
*/
package com.android.quickstep.dagger;
-import com.android.quickstep.logging.LoggingModule;
-
import dagger.Module;
-@Module(includes = {LoggingModule.class})
+@Module
public class QuickStepModule {
}
diff --git a/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java b/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
new file mode 100644
index 0000000..f2d5715
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
@@ -0,0 +1,33 @@
+/*
+ * 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.dagger;
+
+import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherBaseAppComponent;
+import com.android.quickstep.logging.SettingsChangeLogger;
+
+/**
+ * Launcher Quickstep base component for Dagger injection.
+ *
+ * This class is not actually annotated as a Dagger component, since it is not used directly as one.
+ * Doing so generates unnecessary code bloat.
+ *
+ * See {@link LauncherAppComponent} for the one actually used.
+ */
+public interface QuickstepBaseAppComponent extends LauncherBaseAppComponent {
+ SettingsChangeLogger getSettingsChangeLogger();
+}
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index 94764a5..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;
@@ -47,9 +49,9 @@
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig;
-import com.android.quickstep.RecentsActivity;
import com.android.quickstep.views.ClearAllButton;
import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.RecentsViewContainer;
/**
* State controller for fallback recents activity
@@ -57,12 +59,12 @@
public class FallbackRecentsStateController implements StateHandler<RecentsState> {
private final StateAnimationConfig mNoConfig = new StateAnimationConfig();
- private final RecentsActivity mActivity;
+ private final RecentsViewContainer mRecentsViewContainer;
private final FallbackRecentsView mRecentsView;
- public FallbackRecentsStateController(RecentsActivity activity) {
- mActivity = activity;
- mRecentsView = activity.getOverviewPanel();
+ public FallbackRecentsStateController(RecentsViewContainer container) {
+ mRecentsViewContainer = container;
+ mRecentsView = container.getOverviewPanel();
}
@Override
@@ -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();
}
});
@@ -96,10 +98,10 @@
setter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
clearAllButtonAlpha, LINEAR);
float overviewButtonAlpha = state.hasOverviewActions() ? 1 : 0;
- setter.setFloat(mActivity.getActionsView().getVisibilityAlpha(),
+ setter.setFloat(mRecentsViewContainer.getActionsView().getVisibilityAlpha(),
AnimatedFloat.VALUE, overviewButtonAlpha, LINEAR);
- float[] scaleAndOffset = state.getOverviewScaleAndOffset(mActivity);
+ float[] scaleAndOffset = state.getOverviewScaleAndOffset(mRecentsViewContainer);
setter.setFloat(mRecentsView, RECENTS_SCALE_PROPERTY, scaleAndOffset[0],
config.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR));
setter.setFloat(mRecentsView, ADJACENT_PAGE_HORIZONTAL_OFFSET, scaleAndOffset[1],
@@ -110,16 +112,24 @@
setter.setFloat(mRecentsView, TASK_MODALNESS, state.getOverviewModalness(),
config.getInterpolator(ANIM_OVERVIEW_MODAL, LINEAR));
setter.setFloat(mRecentsView, FULLSCREEN_PROGRESS, state.isFullScreen() ? 1 : 0, LINEAR);
- boolean showAsGrid = state.displayOverviewTasksAsGrid(mActivity.getDeviceProfile());
+ boolean showAsGrid =
+ state.displayOverviewTasksAsGrid(mRecentsViewContainer.getDeviceProfile());
setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS, showAsGrid ? 1f : 0f,
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(mActivity.getScrimView(), state.getScrimColor(mActivity),
+ setter.setViewBackgroundColor(mRecentsViewContainer.getScrimView(),
+ state.getScrimColor(mRecentsViewContainer.asContext()),
config.getInterpolator(ANIM_SCRIM_FADE, LINEAR));
if (isSplitSelectionState(state)) {
- int duration = state.getTransitionDuration(mActivity, true /* isToState */);
+ int duration =
+ state.getTransitionDuration(mRecentsViewContainer.asContext(), true);
// TODO (b/246851887): Pass in setter as a NO_ANIM PendingAnimation instead
PendingAnimation pa = new PendingAnimation(duration);
mRecentsView.createSplitSelectInitAnimation(pa, duration);
@@ -129,7 +139,7 @@
Pair<FloatProperty<RecentsView>, FloatProperty<RecentsView>> taskViewsFloat =
mRecentsView.getPagedOrientationHandler().getSplitSelectTaskOffset(
TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION,
- mActivity.getDeviceProfile());
+ mRecentsViewContainer.getDeviceProfile());
setter.setFloat(mRecentsView, taskViewsFloat.first, isSplitSelectionState(state)
? mRecentsView.getSplitSelectTranslation() : 0, LINEAR);
setter.setFloat(mRecentsView, taskViewsFloat.second, 0, LINEAR);
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index e67a9bc..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;
@@ -31,6 +30,7 @@
import androidx.annotation.Nullable;
import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Flags;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.config.FeatureFlags;
@@ -38,17 +38,20 @@
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.statemanager.StateManager.StateListener;
+import com.android.launcher3.statemanager.StatefulContainer;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
+import com.android.quickstep.BaseContainerInterface;
import com.android.quickstep.FallbackActivityInterface;
+import com.android.quickstep.FallbackWindowInterface;
import com.android.quickstep.GestureState;
-import com.android.quickstep.RecentsActivity;
import com.android.quickstep.RotationTouchHelper;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.SplitSelectStateController;
import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.RecentsViewContainer;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
@@ -56,7 +59,8 @@
import java.util.Arrays;
import java.util.List;
-public class FallbackRecentsView extends RecentsView<RecentsActivity, RecentsState>
+public class FallbackRecentsView<CONTAINER_TYPE extends Context & RecentsViewContainer
+ & StatefulContainer<RecentsState>> extends RecentsView<CONTAINER_TYPE, RecentsState>
implements StateListener<RecentsState> {
private static final int TASK_DISMISS_DURATION = 150;
@@ -69,10 +73,16 @@
}
public FallbackRecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr, FallbackActivityInterface.INSTANCE);
+ super(context, attrs, defStyleAttr, getContainerInterface());
mContainer.getStateManager().addStateListener(this);
}
+ private static BaseContainerInterface<RecentsState, ?> getContainerInterface() {
+ return Flags.enableFallbackOverviewInWindow()
+ ? FallbackWindowInterface.getInstance()
+ : FallbackActivityInterface.INSTANCE;
+ }
+
@Override
public void init(OverviewActionsView actionsView, SplitSelectStateController splitController,
@Nullable DesktopRecentsTransitionController desktopRecentsTransitionController) {
@@ -93,7 +103,7 @@
}
@Override
- public StateManager<RecentsState, RecentsActivity> getStateManager() {
+ public StateManager<RecentsState, ?> getStateManager() {
return mContainer.getStateManager();
}
@@ -260,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();
}
@@ -283,7 +293,8 @@
}
}
- if (isOverlayEnabled) {
+ // disabling this so app icons aren't drawn on top of recent tasks.
+ if (isOverlayEnabled && !Flags.enableFallbackOverviewInWindow()) {
runActionOnRemoteHandles(remoteTargetHandle ->
remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true));
}
@@ -312,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/RecentsDragLayer.java b/quickstep/src/com/android/quickstep/fallback/RecentsDragLayer.java
index 29c3dc8..a2884b6 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsDragLayer.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsDragLayer.java
@@ -20,13 +20,12 @@
import com.android.launcher3.util.TouchController;
import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.RecentsActivity;
+import com.android.quickstep.views.RecentsViewContainer;
/**
* Drag layer for fallback recents activity
*/
-public class RecentsDragLayer extends BaseDragLayer<RecentsActivity> {
-
+public class RecentsDragLayer<T extends Context & RecentsViewContainer> extends BaseDragLayer<T> {
public RecentsDragLayer(Context context, AttributeSet attrs) {
super(context, attrs, 1 /* alphaChannelCount */);
}
@@ -34,8 +33,8 @@
@Override
public void recreateControllers() {
mControllers = new TouchController[] {
- new RecentsTaskController(mActivity),
- new FallbackNavBarTouchController(mActivity),
+ new RecentsTaskController(mContainer),
+ new FallbackNavBarTouchController(mContainer),
};
}
}
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/fallback/RecentsTaskController.java b/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java
index 2cb398c..07da379 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java
@@ -15,18 +15,22 @@
*/
package com.android.quickstep.fallback;
+import android.content.Context;
+
+import com.android.launcher3.statemanager.StatefulContainer;
import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
-import com.android.quickstep.RecentsActivity;
+import com.android.quickstep.views.RecentsViewContainer;
-public class RecentsTaskController extends TaskViewTouchController<RecentsActivity> {
-
- public RecentsTaskController(RecentsActivity activity) {
- super(activity);
+public class RecentsTaskController<T extends Context & RecentsViewContainer &
+ StatefulContainer<RecentsState>> extends TaskViewTouchController<T> {
+ public RecentsTaskController(T container) {
+ super(container);
}
@Override
protected boolean isRecentsInteractive() {
- return mContainer.hasWindowFocus() || mContainer.getStateManager().getState().hasLiveTile();
+ return mContainer.getRootView().hasWindowFocus()
+ || mContainer.getStateManager().getState().hasLiveTile();
}
@Override
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowContext.kt b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowContext.kt
new file mode 100644
index 0000000..52a7682
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowContext.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.fallback.window
+
+import android.content.Context
+import android.graphics.PixelFormat
+import android.view.ContextThemeWrapper
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_CONSUME_IME_INSETS
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.util.Themes
+import com.android.launcher3.views.ActivityContext
+import com.android.launcher3.views.BaseDragLayer
+import com.android.quickstep.fallback.RecentsDragLayer
+
+/**
+ * Window context for the Overview overlays.
+ * <p>
+ * Overlays have their own window and need a window context.
+ */
+open class RecentsWindowContext(windowContext: Context) :
+ ContextThemeWrapper(windowContext, Themes.getActivityThemeRes(windowContext)), ActivityContext {
+
+ private var deviceProfile: DeviceProfile? = null
+ private var dragLayer: RecentsDragLayer<RecentsWindowManager> = RecentsDragLayer(this, null)
+ private val deviceProfileChangeListeners:
+ MutableList<DeviceProfile.OnDeviceProfileChangeListener> =
+ ArrayList()
+
+ private val windowTitle: String = "RecentsWindow"
+
+ protected var windowLayoutParams: WindowManager.LayoutParams? =
+ createDefaultWindowLayoutParams(
+ WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, windowTitle)
+
+ override fun getDragLayer(): BaseDragLayer<RecentsWindowManager> {
+ return dragLayer
+ }
+
+ override fun getDeviceProfile(): DeviceProfile {
+ if (deviceProfile == null) {
+ deviceProfile = InvariantDeviceProfile.INSTANCE[this].getDeviceProfile(this)
+ .copy(this)
+ }
+ return deviceProfile!!
+ }
+
+ override fun getOnDeviceProfileChangeListeners():
+ List<DeviceProfile.OnDeviceProfileChangeListener> {
+ return deviceProfileChangeListeners
+ }
+
+ /**
+ * Creates LayoutParams for adding a view directly to WindowManager as a new window.
+ *
+ * @param type The window type to pass to the created WindowManager.LayoutParams.
+ * @param title The window title to pass to the created WindowManager.LayoutParams.
+ */
+ fun createDefaultWindowLayoutParams(type: Int, title: String): WindowManager.LayoutParams {
+ var windowFlags =
+ (WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or
+ WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS or
+ WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED)
+
+ val windowLayoutParams =
+ WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ type,
+ windowFlags,
+ PixelFormat.TRANSLUCENT,
+ )
+
+ windowLayoutParams.title = title
+ windowLayoutParams.fitInsetsTypes = 0
+ windowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+ windowLayoutParams.isSystemApplicationOverlay = true
+ windowLayoutParams.privateFlags = PRIVATE_FLAG_CONSUME_IME_INSETS
+
+ return windowLayoutParams
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
new file mode 100644
index 0000000..8ce61f5
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
@@ -0,0 +1,370 @@
+/*
+ * 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.fallback.window
+
+import android.animation.AnimatorSet
+import android.app.ActivityOptions
+import android.content.ComponentName
+import android.content.Context
+import android.content.LocusId
+import android.os.Bundle
+import android.util.Log
+import android.view.KeyEvent
+import android.view.LayoutInflater
+import android.view.MotionEvent
+import android.view.RemoteAnimationAdapter
+import android.view.RemoteAnimationTarget
+import android.view.SurfaceControl
+import android.view.View
+import android.view.Window
+import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
+import android.window.RemoteTransition
+import com.android.launcher3.BaseActivity
+import com.android.launcher3.LauncherAnimationRunner
+import com.android.launcher3.LauncherAnimationRunner.RemoteAnimationFactory
+import com.android.launcher3.R
+import com.android.launcher3.statehandlers.DesktopVisibilityController
+import com.android.launcher3.statemanager.StateManager
+import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory
+import com.android.launcher3.statemanager.StatefulContainer
+import com.android.launcher3.util.DisplayController
+import com.android.launcher3.util.RunnableList
+import com.android.launcher3.util.SystemUiController
+import com.android.launcher3.views.BaseDragLayer
+import com.android.launcher3.views.ScrimView
+import com.android.quickstep.FallbackWindowInterface
+import com.android.quickstep.OverviewComponentObserver
+import com.android.quickstep.RecentsModel
+import com.android.quickstep.RemoteAnimationTargets
+import com.android.quickstep.SystemUiProxy
+import com.android.quickstep.fallback.FallbackRecentsStateController
+import com.android.quickstep.fallback.FallbackRecentsView
+import com.android.quickstep.fallback.RecentsDragLayer
+import com.android.quickstep.fallback.RecentsState
+import com.android.quickstep.fallback.RecentsState.BACKGROUND_APP
+import com.android.quickstep.fallback.RecentsState.BG_LAUNCHER
+import com.android.quickstep.fallback.RecentsState.DEFAULT
+import com.android.quickstep.fallback.RecentsState.HOME
+import com.android.quickstep.fallback.RecentsState.MODAL_TASK
+import com.android.quickstep.fallback.RecentsState.OVERVIEW_SPLIT_SELECT
+import com.android.quickstep.util.RecentsAtomicAnimationFactory
+import com.android.quickstep.util.SplitSelectStateController
+import com.android.quickstep.util.TISBindHelper
+import com.android.quickstep.views.OverviewActionsView
+import com.android.quickstep.views.RecentsView
+import com.android.quickstep.views.RecentsViewContainer
+import java.util.function.Predicate
+
+/**
+ * Class that will manage RecentsView lifecycle within a window and interface correctly
+ * where needed. This allows us to run RecentsView in a window where needed.
+ * todo: b/365776320, b/365777482
+ */
+class RecentsWindowManager(context: Context) :
+ RecentsWindowContext(context), RecentsViewContainer, StatefulContainer<RecentsState> {
+
+ companion object {
+ private const val HOME_APPEAR_DURATION: Long = 250
+ private const val TAG = "RecentsWindowManager"
+ private const val DEBUG = false
+ }
+
+ protected var recentsView: FallbackRecentsView<RecentsWindowManager>? = null
+ private val windowContext: Context = createWindowContext(TYPE_APPLICATION_OVERLAY, null)
+ private val windowManager: WindowManager = windowContext.getSystemService(WindowManager::class.java)!!
+ private var layoutInflater: LayoutInflater = LayoutInflater.from(this).cloneInContext(this)
+ private var stateManager: StateManager<RecentsState, RecentsWindowManager> =
+ StateManager<RecentsState, RecentsWindowManager>(this, RecentsState.BG_LAUNCHER)
+ private var mSystemUiController: SystemUiController? = null
+
+ private var dragLayer: RecentsDragLayer<RecentsWindowManager>? = null
+ private var windowView: View? = null
+ private var actionsView: OverviewActionsView<*>? = null
+ private var scrimView: ScrimView? = null
+
+ private var isShown = false
+
+ private var tisBindHelper: TISBindHelper = TISBindHelper(this) {}
+
+ // Callback array that corresponds to events defined in @ActivityEvent
+ private val mEventCallbacks =
+ arrayOf(RunnableList(), RunnableList(), RunnableList(), RunnableList())
+ private var onInitListener: Predicate<Boolean>? = null
+
+ init {
+ FallbackWindowInterface.init(this)
+ }
+
+ override fun destroy() {
+ super.destroy()
+ FallbackWindowInterface.getInstance()?.destroy()
+ }
+
+ override fun startHome() {
+ val recentsView: RecentsView<*, *> = getOverviewPanel()
+ recentsView.switchToScreenshot {
+ recentsView.finishRecentsAnimation(true) { startHomeInternal() }
+ }
+ }
+
+ private fun startHomeInternal() {
+ val runner = LauncherAnimationRunner(mainThreadHandler, mAnimationToHomeFactory, true)
+ val options =
+ ActivityOptions.makeRemoteAnimation(
+ RemoteAnimationAdapter(runner, HOME_APPEAR_DURATION, 0),
+ RemoteTransition(
+ runner.toRemoteTransition(),
+ iApplicationThread,
+ "StartHomeFromRecents",
+ ),
+ )
+ OverviewComponentObserver.startHomeIntentSafely(this, options.toBundle(), TAG)
+ }
+
+ private val mAnimationToHomeFactory =
+ RemoteAnimationFactory {
+ _: Int,
+ appTargets: Array<RemoteAnimationTarget>?,
+ wallpaperTargets: Array<RemoteAnimationTarget>?,
+ nonAppTargets: Array<RemoteAnimationTarget>?,
+ result: LauncherAnimationRunner.AnimationResult? ->
+ val controller =
+ getStateManager().createAnimationToNewWorkspace(BG_LAUNCHER, HOME_APPEAR_DURATION)
+ controller.dispatchOnStart()
+ val targets =
+ RemoteAnimationTargets(
+ appTargets,
+ wallpaperTargets,
+ nonAppTargets,
+ RemoteAnimationTarget.MODE_OPENING,
+ )
+ for (app in targets.apps) {
+ SurfaceControl.Transaction().setAlpha(app.leash, 1f).apply()
+ }
+ val anim = AnimatorSet()
+ anim.play(controller.animationPlayer)
+ anim.setDuration(HOME_APPEAR_DURATION)
+ result!!.setAnimation(
+ anim,
+ this@RecentsWindowManager,
+ {
+ getStateManager().goToState(HOME, false)
+ cleanup()
+ },
+ true, /* skipFirstFrame */
+ )
+ }
+
+ fun cleanup() {
+ if (isShown) {
+ windowManager.removeViewImmediate(windowView)
+ isShown = false
+ }
+ }
+
+ fun startRecentsWindow() {
+ if (isShown) return
+ if (windowView == null) {
+ windowView = layoutInflater.inflate(R.layout.fallback_recents_activity, null)
+ }
+ windowManager.addView(windowView, windowLayoutParams)
+ isShown = true
+
+ windowView?.systemUiVisibility =
+ (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
+ View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
+ View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)
+
+ recentsView = windowView?.findViewById(R.id.overview_panel)
+ actionsView = windowView?.findViewById(R.id.overview_actions_view)
+ scrimView = windowView?.findViewById(R.id.scrim_view)
+ val systemUiProxy = SystemUiProxy.INSTANCE[this]
+ val splitSelectStateController =
+ SplitSelectStateController(
+ this,
+ getStateManager(),
+ null, /* depthController */
+ statsLogManager,
+ systemUiProxy,
+ RecentsModel.INSTANCE[this],
+ null, /*activityBackCallback*/
+ )
+ recentsView?.init(actionsView, splitSelectStateController, null)
+ dragLayer = windowView?.findViewById(R.id.drag_layer)
+
+ actionsView?.updateDimension(getDeviceProfile(), recentsView?.lastComputedTaskSize)
+ actionsView?.updateVerticalMargin(DisplayController.getNavigationMode(this))
+
+ mSystemUiController = SystemUiController(windowView)
+ onInitListener?.test(true)
+ }
+
+ override fun canStartHomeSafely(): Boolean {
+ val overviewCommandHelper = tisBindHelper.overviewCommandHelper
+ return overviewCommandHelper == null || overviewCommandHelper.canStartHomeSafely()
+ }
+
+ override fun getDesktopVisibilityController(): DesktopVisibilityController? {
+ return tisBindHelper.desktopVisibilityController
+ }
+
+ fun registerInitListener(onInitListener: Predicate<Boolean>) {
+ this.onInitListener = onInitListener
+ }
+
+ override fun collectStateHandlers(out: MutableList<StateManager.StateHandler<RecentsState?>>?) {
+ out!!.add(FallbackRecentsStateController(this))
+ }
+
+ override fun getStateManager(): StateManager<RecentsState, RecentsWindowManager> {
+ return this.stateManager
+ }
+
+ override fun shouldAnimateStateChange(): Boolean {
+ return true
+ }
+
+ override fun isInState(state: RecentsState?): Boolean {
+ return stateManager.state == state
+ }
+
+ override fun onStateSetStart(state: RecentsState?) {
+ super.onStateSetStart(state)
+ logState(state, "state started:")
+ }
+
+ override fun onStateSetEnd(state: RecentsState?) {
+ super.onStateSetEnd(state)
+ logState(state, "state ended:")
+ }
+
+ private fun logState(state: RecentsState?, prefix: String) {
+ if (!DEBUG) {
+ return
+ }
+ if (state != null) {
+ when (state) {
+ DEFAULT -> Log.d(TAG, prefix + "default")
+ MODAL_TASK -> {
+ Log.d(TAG, prefix + "MODAL_TASK")
+ }
+ BACKGROUND_APP -> {
+ Log.d(TAG, prefix + "BACKGROUND_APP")
+ }
+ HOME -> {
+ Log.d(TAG, prefix + "HOME")
+ }
+ BG_LAUNCHER -> {
+ Log.d(TAG, prefix + "BG_LAUNCHER")
+ }
+ OVERVIEW_SPLIT_SELECT -> {
+ Log.d(TAG, prefix + "OVERVIEW_SPLIT_SELECT")
+ }
+ }
+ }
+ }
+
+ override fun getSystemUiController(): SystemUiController? {
+ if (mSystemUiController == null) {
+ mSystemUiController = SystemUiController(rootView)
+ }
+ return mSystemUiController
+ }
+
+ override fun getContext(): Context {
+ return this
+ }
+
+ override fun getScrimView(): ScrimView? {
+ return scrimView
+ }
+
+ override fun <T : View?> getOverviewPanel(): T {
+ return recentsView as T
+ }
+
+ override fun getRootView(): View? {
+ return windowView
+ }
+
+ override fun getDragLayer(): BaseDragLayer<RecentsWindowManager> {
+ return dragLayer!!
+ }
+
+ override fun dispatchGenericMotionEvent(ev: MotionEvent?): Boolean {
+ // TODO(b/368610710)
+ return false
+ }
+
+ override fun dispatchKeyEvent(ev: KeyEvent?): Boolean {
+ // TODO(b/368610710)
+ return false
+ }
+
+ override fun getActionsView(): OverviewActionsView<*>? {
+ return actionsView
+ }
+
+ override fun addForceInvisibleFlag(flag: Int) {}
+
+ override fun clearForceInvisibleFlag(flag: Int) {}
+
+ override fun setLocusContext(id: LocusId?, bundle: Bundle?) {
+ // no op
+ }
+
+ override fun isStarted(): Boolean {
+ return isShown
+ }
+
+ /** Adds a callback for the provided activity event */
+ override fun addEventCallback(@BaseActivity.ActivityEvent event: Int, callback: Runnable?) {
+ mEventCallbacks[event].add(callback)
+ }
+
+ /** Removes a previously added callback */
+ override fun removeEventCallback(@BaseActivity.ActivityEvent event: Int, callback: Runnable?) {
+ mEventCallbacks[event].remove(callback)
+ }
+
+ override fun runOnBindToTouchInteractionService(r: Runnable?) {
+ tisBindHelper.runOnBindToTouchInteractionService(r)
+ }
+
+ override fun addMultiWindowModeChangedListener(
+ listener: BaseActivity.MultiWindowModeChangedListener?
+ ) {
+ // TODO(b/368408838)
+ }
+
+ override fun removeMultiWindowModeChangedListener(
+ listener: BaseActivity.MultiWindowModeChangedListener?
+ ) {}
+
+ override fun returnToHomescreen() {
+ startHome()
+ }
+
+ override fun isRecentsViewVisible(): Boolean {
+ return getStateManager().state!!.isRecentsViewVisible
+ }
+
+ override fun createAtomicAnimationFactory(): AtomicAnimationFactory<RecentsState?>? {
+ return RecentsAtomicAnimationFactory<RecentsWindowManager, RecentsState>(this)
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowSwipeHandler.java b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowSwipeHandler.java
new file mode 100644
index 0000000..34b3d74
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowSwipeHandler.java
@@ -0,0 +1,452 @@
+/*
+ * 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.fallback.window;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.content.Intent.EXTRA_COMPONENT_NAME;
+import static android.content.Intent.EXTRA_USER;
+
+import static com.android.app.animation.Interpolators.ACCELERATE;
+import static com.android.launcher3.GestureNavContract.EXTRA_GESTURE_CONTRACT;
+import static com.android.launcher3.GestureNavContract.EXTRA_ICON_POSITION;
+import static com.android.launcher3.GestureNavContract.EXTRA_ICON_SURFACE;
+import static com.android.launcher3.GestureNavContract.EXTRA_ON_FINISH_CALLBACK;
+import static com.android.launcher3.GestureNavContract.EXTRA_REMOTE_CALLBACK;
+import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
+
+import android.animation.ObjectAnimator;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.RemoteAnimationTarget;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimatedFloat;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.anim.SpringAnimationBuilder;
+import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.util.DisplayController;
+import com.android.quickstep.AbsSwipeUpHandler;
+import com.android.quickstep.GestureState;
+import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.RemoteAnimationTargets;
+import com.android.quickstep.TaskAnimationManager;
+import com.android.quickstep.fallback.FallbackRecentsView;
+import com.android.quickstep.fallback.RecentsState;
+import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.RectFSpringAnim;
+import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties;
+import com.android.quickstep.util.TransformParams;
+import com.android.quickstep.util.TransformParams.BuilderProxy;
+import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.recents.model.Task.TaskKey;
+import com.android.systemui.shared.system.InputConsumerController;
+
+import java.lang.ref.WeakReference;
+import java.util.List;
+import java.util.UUID;
+import java.util.function.Consumer;
+
+/**
+ * Handles the navigation gestures when a 3rd party launcher is the default home activity.
+ *
+ * Bugs: b/365775417
+ */
+public class RecentsWindowSwipeHandler extends AbsSwipeUpHandler<RecentsWindowManager,
+ FallbackRecentsView<RecentsWindowManager>, RecentsState> {
+
+ private static final String TAG = "RecentsWindowSwipeHandler";
+
+ /**
+ * Message used for receiving gesture nav contract information. We use a static messenger to
+ * avoid leaking too make binders in case the receiving launcher does not handle the contract
+ * properly.
+ */
+ private static StaticMessageReceiver sMessageReceiver = null;
+
+ private FallbackHomeAnimationFactory mActiveAnimationFactory;
+ private final boolean mRunningOverHome;
+
+ private final Matrix mTmpMatrix = new Matrix();
+ private float mMaxLauncherScale = 1;
+
+ private boolean mAppCanEnterPip;
+
+ public RecentsWindowSwipeHandler(Context context, RecentsAnimationDeviceState deviceState,
+ TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
+ boolean continuingLastGesture, InputConsumerController inputConsumer,
+ RecentsWindowManager recentsWindowManager) {
+ super(context, deviceState, taskAnimationManager, gestureState, touchTimeMs,
+ continuingLastGesture, inputConsumer, recentsWindowManager);
+
+ mRunningOverHome = mGestureState.getRunningTask() != null
+ && mGestureState.getRunningTask().isHomeTask();
+ if (mRunningOverHome) {
+ runActionOnRemoteHandles(remoteTargetHandle ->
+ remoteTargetHandle.getTransformParams().setHomeBuilderProxy(
+ RecentsWindowSwipeHandler.
+ this::updateHomeActivityTransformDuringSwipeUp));
+ }
+ }
+
+ @Override
+ protected void initTransitionEndpoints(DeviceProfile dp) {
+ super.initTransitionEndpoints(dp);
+ if (mRunningOverHome) {
+ // Full screen scale should be independent of remote target handle
+ mMaxLauncherScale = 1 / mRemoteTargetHandles[0].getTaskViewSimulator()
+ .getFullScreenScale();
+ }
+ }
+
+ private void updateHomeActivityTransformDuringSwipeUp(SurfaceProperties builder,
+ RemoteAnimationTarget app, TransformParams params) {
+ setHomeScaleAndAlpha(builder, app, mCurrentShift.value,
+ Utilities.boundToRange(1 - mCurrentShift.value, 0, 1));
+ }
+
+ private void setHomeScaleAndAlpha(SurfaceProperties builder,
+ RemoteAnimationTarget app, float verticalShift, float alpha) {
+ float scale = Utilities.mapRange(verticalShift, 1, mMaxLauncherScale);
+ mTmpMatrix.setScale(scale, scale,
+ app.localBounds.exactCenterX(), app.localBounds.exactCenterY());
+ builder.setMatrix(mTmpMatrix).setAlpha(alpha);
+ builder.setShow();
+ }
+
+ @Override
+ protected HomeAnimationFactory createHomeAnimationFactory(
+ List<IBinder> launchCookies,
+ long duration,
+ boolean isTargetTranslucent,
+ boolean appCanEnterPip,
+ RemoteAnimationTarget runningTaskTarget,
+ @Nullable TaskView targetTaskView) {
+ mAppCanEnterPip = appCanEnterPip;
+ if (appCanEnterPip) {
+ return new FallbackPipToHomeAnimationFactory();
+ }
+ mActiveAnimationFactory = new FallbackHomeAnimationFactory(duration);
+ //todo: b/368410893 follow up on this as its intent focused and seems to cut immediately
+ Intent intent = new Intent(mGestureState.getHomeIntent());
+ if (mActiveAnimationFactory != null && runningTaskTarget != null) {
+ mActiveAnimationFactory.addGestureContract(intent, runningTaskTarget.taskInfo);
+ }
+ return mActiveAnimationFactory;
+ }
+
+ @Override
+ protected boolean handleTaskAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTargets,
+ @NonNull ActiveGestureLog.CompoundString failureReason) {
+ if (mActiveAnimationFactory != null
+ && mActiveAnimationFactory.handleHomeTaskAppeared(appearedTaskTargets)) {
+ mActiveAnimationFactory = null;
+ return false;
+ }
+
+ return super.handleTaskAppeared(appearedTaskTargets, failureReason);
+ }
+
+ @Override
+ protected void finishRecentsControllerToHome(Runnable callback) {
+ final Runnable recentsCallback;
+ if (mAppCanEnterPip) {
+ // Make sure Launcher is resumed after auto-enter-pip transition to actually trigger
+ // the PiP task appearing.
+ recentsCallback = () -> {
+ callback.run();
+ mRecentsWindowManager.startHome();
+ };
+ } else {
+ recentsCallback = callback;
+ }
+ mRecentsView.cleanupRemoteTargets();
+ mRecentsAnimationController.finish(
+ mAppCanEnterPip /* toRecents */, recentsCallback, true /* sendUserLeaveHint */);
+ }
+
+ @Override
+ protected void switchToScreenshot() {
+ if (mRunningOverHome) {
+ // When the current task is home, then we don't need to capture anything
+ mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
+ } else {
+ super.switchToScreenshot();
+ }
+ }
+
+ @Override
+ protected void notifyGestureAnimationStartToRecents() {
+ if (mRunningOverHome) {
+ if (DisplayController.getNavigationMode(mContext).hasGestures) {
+ mRecentsView.onGestureAnimationStartOnHome(
+ mGestureState.getRunningTask().getPlaceholderTasks(),
+ mDeviceState.getRotationTouchHelper());
+ }
+ } else {
+ super.notifyGestureAnimationStartToRecents();
+ }
+ }
+
+ private class FallbackPipToHomeAnimationFactory extends HomeAnimationFactory {
+ @NonNull
+ @Override
+ public AnimatorPlaybackController createActivityAnimationToHome() {
+ // copied from {@link LauncherSwipeHandlerV2.LauncherHomeAnimationFactory}
+ long accuracy = 2 * Math.max(mDp.widthPx, mDp.heightPx);
+ return mContainer.getStateManager().createAnimationToNewWorkspace(
+ RecentsState.HOME, accuracy, StateAnimationConfig.SKIP_ALL_ANIMATIONS);
+ }
+ }
+
+ private class FallbackHomeAnimationFactory extends HomeAnimationFactory
+ implements Consumer<Message> {
+ private final Rect mTempRect = new Rect();
+ private final TransformParams mHomeAlphaParams = new TransformParams();
+ private final AnimatedFloat mHomeAlpha;
+
+ private final AnimatedFloat mVerticalShiftForScale = new AnimatedFloat();
+ private final AnimatedFloat mRecentsAlpha = new AnimatedFloat();
+
+ private final RectF mTargetRect = new RectF();
+ private SurfaceControl mSurfaceControl;
+
+ private boolean mAnimationFinished;
+ private Message mOnFinishCallback;
+
+ private final long mDuration;
+
+ private RectFSpringAnim mSpringAnim;
+ FallbackHomeAnimationFactory(long duration) {
+ mDuration = duration;
+
+ if (mRunningOverHome) {
+ mHomeAlpha = new AnimatedFloat();
+ mHomeAlpha.value = Utilities.boundToRange(1 - mCurrentShift.value, 0, 1);
+ mVerticalShiftForScale.value = mCurrentShift.value;
+ runActionOnRemoteHandles(remoteTargetHandle ->
+ remoteTargetHandle.getTransformParams().setHomeBuilderProxy(
+ FallbackHomeAnimationFactory.this
+ ::updateHomeActivityTransformDuringHomeAnim));
+ } else {
+ mHomeAlpha = new AnimatedFloat(this::updateHomeAlpha);
+ mHomeAlpha.value = 0;
+ mHomeAlphaParams.setHomeBuilderProxy(
+ this::updateHomeActivityTransformDuringHomeAnim);
+ }
+
+ mRecentsAlpha.value = 1;
+ runActionOnRemoteHandles(remoteTargetHandle ->
+ remoteTargetHandle.getTransformParams().setBaseBuilderProxy(
+ FallbackHomeAnimationFactory.this
+ ::updateRecentsActivityTransformDuringHomeAnim));
+ }
+
+ @NonNull
+ @Override
+ public RectF getWindowTargetRect() {
+ if (mTargetRect.isEmpty()) {
+ mTargetRect.set(super.getWindowTargetRect());
+ }
+ return mTargetRect;
+ }
+
+ private void updateRecentsActivityTransformDuringHomeAnim(SurfaceProperties builder,
+ RemoteAnimationTarget app, TransformParams params) {
+ builder.setAlpha(mRecentsAlpha.value);
+ }
+
+ private void updateHomeActivityTransformDuringHomeAnim(SurfaceProperties builder,
+ RemoteAnimationTarget app, TransformParams params) {
+ setHomeScaleAndAlpha(builder, app, mVerticalShiftForScale.value, mHomeAlpha.value);
+ }
+
+ @NonNull
+ @Override
+ public AnimatorPlaybackController createActivityAnimationToHome() {
+ PendingAnimation pa = new PendingAnimation(mDuration);
+ pa.setFloat(mRecentsAlpha, AnimatedFloat.VALUE, 0, ACCELERATE);
+ return pa.createPlaybackController();
+ }
+
+ private void updateHomeAlpha() {
+ if (mHomeAlphaParams.getTargetSet() != null) {
+ mHomeAlphaParams.applySurfaceParams(
+ mHomeAlphaParams.createSurfaceParams(BuilderProxy.NO_OP));
+ }
+ }
+
+ public boolean handleHomeTaskAppeared(RemoteAnimationTarget[] appearedTaskTargets) {
+ RemoteAnimationTarget appearedTaskTarget = appearedTaskTargets[0];
+ if (appearedTaskTarget.windowConfiguration.getActivityType() == ACTIVITY_TYPE_HOME) {
+ RemoteAnimationTargets targets = new RemoteAnimationTargets(
+ new RemoteAnimationTarget[] {appearedTaskTarget},
+ new RemoteAnimationTarget[0], new RemoteAnimationTarget[0],
+ appearedTaskTarget.mode);
+ mHomeAlphaParams.setTargetSet(targets);
+ updateHomeAlpha();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void playAtomicAnimation(float velocity) {
+ ObjectAnimator alphaAnim = mHomeAlpha.animateToValue(mHomeAlpha.value, 1);
+ alphaAnim.setDuration(mDuration).setInterpolator(ACCELERATE);
+ alphaAnim.start();
+
+ if (mRunningOverHome) {
+ // Spring back launcher scale
+ new SpringAnimationBuilder(mContext)
+ .setStartValue(mVerticalShiftForScale.value)
+ .setEndValue(0)
+ .setStartVelocity(-velocity / mTransitionDragLength)
+ .setMinimumVisibleChange(1f / mDp.heightPx)
+ .setDampingRatio(0.6f)
+ .setStiffness(800)
+ .build(mVerticalShiftForScale, AnimatedFloat.VALUE)
+ .start();
+ }
+ }
+
+ @Override
+ public void setAnimation(RectFSpringAnim anim) {
+ mSpringAnim = anim;
+ mSpringAnim.addAnimatorListener(forEndCallback(this::onRectAnimationEnd));
+ }
+
+ private void onRectAnimationEnd() {
+ mAnimationFinished = true;
+ maybeSendEndMessage();
+ }
+
+ private void maybeSendEndMessage() {
+ if (mAnimationFinished && mOnFinishCallback != null) {
+ try {
+ mOnFinishCallback.replyTo.send(mOnFinishCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error sending icon position", e);
+ }
+ }
+ }
+
+ @Override
+ public void accept(Message msg) {
+ try {
+ Bundle data = msg.getData();
+ RectF position = data.getParcelable(EXTRA_ICON_POSITION);
+ if (!position.isEmpty()) {
+ mSurfaceControl = data.getParcelable(EXTRA_ICON_SURFACE);
+ mTargetRect.set(position);
+ if (mSpringAnim != null) {
+ mSpringAnim.onTargetPositionChanged();
+ }
+ }
+ mOnFinishCallback = data.getParcelable(EXTRA_ON_FINISH_CALLBACK);
+ maybeSendEndMessage();
+ } catch (Exception e) {
+ // Ignore
+ }
+ }
+
+ @Override
+ public void update(RectF currentRect, float progress, float radius, int overlayAlpha) {
+ if (mSurfaceControl != null) {
+ currentRect.roundOut(mTempRect);
+ Transaction t = new Transaction();
+ try {
+ t.setGeometry(mSurfaceControl, null, mTempRect, Surface.ROTATION_0);
+ t.apply();
+ } catch (RuntimeException e) {
+ // Ignore
+ }
+ }
+ }
+
+ private void addGestureContract(Intent intent, RunningTaskInfo runningTaskInfo) {
+ if (mRunningOverHome || runningTaskInfo == null) {
+ return;
+ }
+
+ TaskKey key = new TaskKey(runningTaskInfo);
+ if (key.getComponent() != null) {
+ if (sMessageReceiver == null) {
+ sMessageReceiver = new StaticMessageReceiver();
+ }
+
+ Bundle gestureNavContract = new Bundle();
+ gestureNavContract.putParcelable(EXTRA_COMPONENT_NAME, key.getComponent());
+ gestureNavContract.putParcelable(EXTRA_USER, UserHandle.of(key.userId));
+ gestureNavContract.putParcelable(
+ EXTRA_REMOTE_CALLBACK, sMessageReceiver.newCallback(this));
+ intent.putExtra(EXTRA_GESTURE_CONTRACT, gestureNavContract);
+ }
+ }
+ }
+
+ private static class StaticMessageReceiver implements Handler.Callback {
+
+ private final Messenger mMessenger =
+ new Messenger(new Handler(Looper.getMainLooper(), this));
+
+ private ParcelUuid mCurrentUID = new ParcelUuid(UUID.randomUUID());
+ private WeakReference<Consumer<Message>> mCurrentCallback = new WeakReference<>(null);
+
+ public Message newCallback(Consumer<Message> callback) {
+ mCurrentUID = new ParcelUuid(UUID.randomUUID());
+ mCurrentCallback = new WeakReference<>(callback);
+
+ Message msg = Message.obtain();
+ msg.replyTo = mMessenger;
+ msg.obj = mCurrentUID;
+ return msg;
+ }
+
+ @Override
+ public boolean handleMessage(@NonNull Message message) {
+ if (mCurrentUID.equals(message.obj)) {
+ Consumer<Message> consumer = mCurrentCallback.get();
+ if (consumer != null) {
+ consumer.accept(message);
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/BubbleBarInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/BubbleBarInputConsumer.java
index 92031c5..778c231 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/BubbleBarInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/BubbleBarInputConsumer.java
@@ -23,10 +23,12 @@
import android.view.MotionEvent;
import android.view.ViewConfiguration;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.taskbar.TaskbarActivityContext;
+import com.android.launcher3.taskbar.bubbles.BubbleBarSwipeController;
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController;
import com.android.launcher3.taskbar.bubbles.BubbleControllers;
-import com.android.launcher3.taskbar.bubbles.BubbleDragController;
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
@@ -40,10 +42,11 @@
private final BubbleStashController mBubbleStashController;
private final BubbleBarViewController mBubbleBarViewController;
- private final BubbleDragController mBubbleDragController;
+ @Nullable
+ private final BubbleBarSwipeController mBubbleBarSwipeController;
private final InputMonitorCompat mInputMonitorCompat;
- private boolean mSwipeUpOnBubbleHandle;
+ private boolean mPilfered;
private boolean mPassedTouchSlop;
private boolean mStashedOrCollapsedOnDown;
@@ -57,7 +60,8 @@
InputMonitorCompat inputMonitorCompat) {
mBubbleStashController = bubbleControllers.bubbleStashController;
mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
- mBubbleDragController = bubbleControllers.bubbleDragController;
+ mBubbleBarSwipeController = bubbleControllers.bubbleBarSwipeController.orElse(null);
+
mInputMonitorCompat = inputMonitorCompat;
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mTimeForTap = ViewConfiguration.getTapTimeout();
@@ -77,6 +81,9 @@
mDownPos.set(ev.getX(), ev.getY());
mLastPos.set(mDownPos);
mStashedOrCollapsedOnDown = mBubbleStashController.isStashed() || isCollapsed();
+ if (mBubbleBarSwipeController != null) {
+ mBubbleBarSwipeController.start();
+ }
break;
case MotionEvent.ACTION_MOVE:
int pointerIndex = ev.findPointerIndex(mActivePointerId);
@@ -90,11 +97,10 @@
if (!mPassedTouchSlop) {
mPassedTouchSlop = Math.abs(dY) > mTouchSlop || Math.abs(dX) > mTouchSlop;
}
- if (mStashedOrCollapsedOnDown && !mSwipeUpOnBubbleHandle && mPassedTouchSlop) {
- boolean verticalGesture = Math.abs(dY) > Math.abs(dX);
- if (verticalGesture && !mBubbleDragController.isDragging()) {
- mSwipeUpOnBubbleHandle = true;
- mBubbleStashController.showBubbleBar(/* expandBubbles= */ true);
+ if (mBubbleBarSwipeController != null) {
+ mBubbleBarSwipeController.swipeTo(dY);
+ if (!mPilfered && mBubbleBarSwipeController.isSwipeGesture()) {
+ mPilfered = true;
// Bubbles is handling the swipe so make sure no one else gets it.
TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers");
mInputMonitorCompat.pilferPointers();
@@ -102,8 +108,10 @@
}
break;
case MotionEvent.ACTION_UP:
+ boolean swipeUpOnBubbleHandle = mBubbleBarSwipeController != null
+ && mBubbleBarSwipeController.isSwipeGesture();
boolean isWithinTapTime = ev.getEventTime() - ev.getDownTime() <= mTimeForTap;
- if (isWithinTapTime && !mSwipeUpOnBubbleHandle && !mPassedTouchSlop
+ if (isWithinTapTime && !swipeUpOnBubbleHandle && !mPassedTouchSlop
&& mStashedOrCollapsedOnDown) {
// Taps on the handle / collapsed state should open the bar
mBubbleStashController.showBubbleBar(/* expandBubbles= */ true);
@@ -116,8 +124,11 @@
}
private void cleanupAfterMotionEvent() {
+ if (mBubbleBarSwipeController != null) {
+ mBubbleBarSwipeController.finish();
+ }
mPassedTouchSlop = false;
- mSwipeUpOnBubbleHandle = false;
+ mPilfered = false;
}
private boolean isCollapsed() {
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/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 69d3bc9..e19b338 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -41,6 +41,7 @@
import androidx.annotation.UiThread;
+import com.android.launcher3.Flags;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.testing.TestLogging;
@@ -424,7 +425,10 @@
mTaskAnimationManager.notifyRecentsAnimationState(mInteractionHandler);
notifyGestureStarted(true /*isLikelyToStartNewTask*/);
} else {
- Intent intent = new Intent(mInteractionHandler.getLaunchIntent());
+ // todo differentiate intent based on if we are on home or in app for overview in window
+ Intent intent = new Intent(Flags.enableFallbackOverviewInWindow()
+ ? mInteractionHandler.getHomeIntent()
+ : mInteractionHandler.getLaunchIntent());
intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, mGestureState.getGestureId());
mActiveCallbacks = mTaskAnimationManager.startRecentsAnimation(mGestureState, intent,
mInteractionHandler);
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index c61f71d..a236eca 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -28,6 +28,7 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.statemanager.BaseState;
+import com.android.launcher3.statemanager.StatefulContainer;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.views.BaseDragLayer;
@@ -41,7 +42,8 @@
/**
* Input consumer for handling touch on the recents/Launcher activity.
*/
-public class OverviewInputConsumer<S extends BaseState<S>, T extends RecentsViewContainer>
+public class OverviewInputConsumer<S extends BaseState<S>,
+ T extends RecentsViewContainer & StatefulContainer<S>>
implements InputConsumer {
private final T mContainer;
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/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
index 1bec970..f7f3157 100644
--- a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
@@ -62,6 +62,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatedFloat;
@@ -103,6 +104,9 @@
private final AnimatedFloat mSwipeProgress = new AnimatedFloat(this::onSwipeProgressUpdate);
+ private final InvariantDeviceProfile.OnIDPChangeListener mOnIDPChangeListener =
+ modelPropertiesChanged -> updateHint();
+
private TISBindHelper mTISBindHelper;
private BgDrawable mBackground;
@@ -115,6 +119,8 @@
private AnimatorPlaybackController mLauncherStartAnim = null;
+ private TextView mHintView;
+
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -167,12 +173,9 @@
}
});
- TextView hint = findViewById(R.id.hint);
- DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(this).getDeviceProfile(this);
- if (!dp.isGestureMode) {
- hint.setText(R.string.allset_button_hint);
- }
- hint.setAccessibilityDelegate(new SkipButtonAccessibilityDelegate());
+ mHintView = findViewById(R.id.hint);
+ mHintView.setAccessibilityDelegate(new SkipButtonAccessibilityDelegate());
+ updateHint();
mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
@@ -190,7 +193,21 @@
LOTTIE_TERTIARY_COLOR_TOKEN, R.color.all_set_bg_tertiary),
getTheme());
- startBackgroundAnimation(dp.isTablet);
+ startBackgroundAnimation(getDP().isTablet);
+ getIDP().addOnChangeListener(mOnIDPChangeListener);
+ }
+
+ private InvariantDeviceProfile getIDP() {
+ return LauncherAppState.getInstance(this).getInvariantDeviceProfile();
+ }
+
+ private DeviceProfile getDP() {
+ return getIDP().getDeviceProfile(this);
+ }
+
+ private void updateHint() {
+ mHintView.setText(
+ getDP().isGestureMode ? R.string.allset_hint : R.string.allset_button_hint);
}
private void runOnUiHelperThread(Runnable runnable) {
@@ -202,7 +219,7 @@
}
private void startBackgroundAnimation(boolean forTablet) {
- if (!Utilities.ATLEAST_S || mVibrator == null) {
+ if (mVibrator == null) {
return;
}
boolean supportsThud = mVibrator.areAllPrimitivesSupported(
@@ -311,6 +328,7 @@
@Override
protected void onDestroy() {
super.onDestroy();
+ getIDP().removeOnChangeListener(mOnIDPChangeListener);
mTISBindHelper.onDestroy();
clearBinderOverride();
if (mBackgroundAnimatorListener != null) {
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/logging/LoggingModule.java b/quickstep/src/com/android/quickstep/logging/LoggingModule.java
deleted file mode 100644
index 8fdf3c7..0000000
--- a/quickstep/src/com/android/quickstep/logging/LoggingModule.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quickstep.logging;
-
-import android.content.Context;
-
-import com.android.launcher3.dagger.ApplicationContext;
-import com.android.launcher3.dagger.LauncherAppSingleton;
-
-import dagger.Module;
-import dagger.Provides;
-
-@Module
-public class LoggingModule {
- @Provides
- @LauncherAppSingleton
- SettingsChangeLogger provideSettingsChangeLogger(@ApplicationContext Context context) {
- return SettingsChangeLogger.INSTANCE.get(context);
- }
-}
diff --git a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
index 717f6c8..995635f 100644
--- a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
+++ b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
@@ -43,16 +43,21 @@
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppSingleton;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
import com.android.launcher3.model.DeviceGridState;
+import com.android.launcher3.util.DaggerSingletonObject;
+import com.android.launcher3.util.DaggerSingletonTracker;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.Info;
-import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.ExecutorUtil;
import com.android.launcher3.util.NavigationMode;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.SettingsCache;
+import com.android.quickstep.dagger.QuickstepBaseAppComponent;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -60,9 +65,12 @@
import java.io.IOException;
import java.util.Optional;
+import javax.inject.Inject;
+
/**
* Utility class to log launcher settings changes
*/
+@LauncherAppSingleton
public class SettingsChangeLogger implements
DisplayController.DisplayInfoChangeListener, OnSharedPreferenceChangeListener,
SafeCloseable {
@@ -70,8 +78,8 @@
/**
* Singleton instance
*/
- public static MainThreadInitializedObject<SettingsChangeLogger> INSTANCE =
- new MainThreadInitializedObject<>(SettingsChangeLogger::new);
+ public static DaggerSingletonObject<SettingsChangeLogger> INSTANCE =
+ new DaggerSingletonObject<>(QuickstepBaseAppComponent::getSettingsChangeLogger);
private static final String TAG = "SettingsChangeLogger";
private static final String BOOLEAN_PREF = "SwitchPreference";
@@ -84,25 +92,31 @@
private StatsLogManager.LauncherEvent mNotificationDotsEvent;
private StatsLogManager.LauncherEvent mHomeScreenSuggestionEvent;
- private SettingsChangeLogger(Context context) {
- this(context, StatsLogManager.newInstance(context));
+ @Inject
+ SettingsChangeLogger(@ApplicationContext Context context, DaggerSingletonTracker tracker) {
+ this(context, StatsLogManager.newInstance(context), tracker);
}
@VisibleForTesting
- SettingsChangeLogger(Context context, StatsLogManager statsLogManager) {
+ SettingsChangeLogger(Context context, StatsLogManager statsLogManager,
+ DaggerSingletonTracker tracker) {
mContext = context;
mStatsLogManager = statsLogManager;
mLoggablePrefs = loadPrefKeys(context);
- DisplayController.INSTANCE.get(context).addChangeListener(this);
- mNavMode = DisplayController.getNavigationMode(context);
- getPrefs(context).registerOnSharedPreferenceChangeListener(this);
- getDevicePrefs(context).registerOnSharedPreferenceChangeListener(this);
+ ExecutorUtil.executeSyncOnMainOrFail(() -> {
+ DisplayController.INSTANCE.get(context).addChangeListener(this);
+ mNavMode = DisplayController.getNavigationMode(context);
- SettingsCache mSettingsCache = SettingsCache.INSTANCE.get(context);
- mSettingsCache.register(NOTIFICATION_BADGING_URI,
- this::onNotificationDotsChanged);
- onNotificationDotsChanged(mSettingsCache.getValue(NOTIFICATION_BADGING_URI));
+ getPrefs(context).registerOnSharedPreferenceChangeListener(this);
+ getDevicePrefs(context).registerOnSharedPreferenceChangeListener(this);
+
+ SettingsCache settingsCache = SettingsCache.INSTANCE.get(context);
+ settingsCache.register(NOTIFICATION_BADGING_URI,
+ this::onNotificationDotsChanged);
+ onNotificationDotsChanged(settingsCache.getValue(NOTIFICATION_BADGING_URI));
+ tracker.addCloseable(this);
+ });
}
private static ArrayMap<String, LoggablePref> loadPrefKeys(Context context) {
@@ -209,6 +223,8 @@
public void close() {
getPrefs(mContext).unregisterOnSharedPreferenceChangeListener(this);
getDevicePrefs(mContext).unregisterOnSharedPreferenceChangeListener(this);
+ SettingsCache settingsCache = SettingsCache.INSTANCE.get(mContext);
+ settingsCache.unregister(NOTIFICATION_BADGING_URI, this::onNotificationDotsChanged);
}
@VisibleForTesting
diff --git a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
index 658975c..88ef0a8 100644
--- a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
@@ -453,6 +453,10 @@
}
}
+ /**
+ * @param inSplitSelection Whether user currently has a task from this task group staged for
+ * split screen. Currently this state is not reachable in fake landscape.
+ */
override fun measureGroupedTaskViewThumbnailBounds(
primarySnapshot: View,
secondarySnapshot: View,
@@ -460,7 +464,8 @@
parentHeight: Int,
splitBoundsConfig: SplitBounds,
dp: DeviceProfile,
- isRtl: Boolean
+ isRtl: Boolean,
+ inSplitSelection: Boolean
) {
val primaryParams = primarySnapshot.layoutParams as FrameLayout.LayoutParams
val secondaryParams = secondarySnapshot.layoutParams as FrameLayout.LayoutParams
@@ -569,6 +574,10 @@
iconAppChipView.setRotation(degreesRotated)
}
+ /**
+ * @param inSplitSelection Whether user currently has a task from this task group staged for
+ * split screen. Currently this state is not reachable in fake landscape.
+ */
override fun setSplitIconParams(
primaryIconView: View,
secondaryIconView: View,
@@ -579,7 +588,8 @@
groupedTaskViewWidth: Int,
isRtl: Boolean,
deviceProfile: DeviceProfile,
- splitConfig: SplitBounds
+ splitConfig: SplitBounds,
+ inSplitSelection: Boolean
) {
val spaceAboveSnapshot = deviceProfile.overviewTaskThumbnailTopMarginPx
val totalThumbnailHeight = groupedTaskViewHeight - spaceAboveSnapshot
diff --git a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
index cc022b2..c0b697d 100644
--- a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
+++ b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
@@ -530,10 +530,16 @@
}
}
+ /**
+ * @param inSplitSelection Whether user currently has a task from this task group staged for
+ * split screen. If true, we have custom translations/scaling in place
+ * for the remaining snapshot, so we'll skip setting translation/scale
+ * here.
+ */
@Override
public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
int parentWidth, int parentHeight, SplitBounds splitBoundsConfig,
- DeviceProfile dp, boolean isRtl) {
+ DeviceProfile dp, boolean isRtl, boolean inSplitSelection) {
int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
FrameLayout.LayoutParams primaryParams =
@@ -541,11 +547,10 @@
FrameLayout.LayoutParams secondaryParams =
(FrameLayout.LayoutParams) secondarySnapshot.getLayoutParams();
- // Reset margin and translations that aren't used in this method, but are used in other
+ // Reset margins that aren't used in this method, but are used in other
// `RecentsPagedOrientationHandler` variants.
secondaryParams.topMargin = 0;
primaryParams.topMargin = spaceAboveSnapshot;
- primarySnapshot.setTranslationY(0);
int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
float dividerScale = splitBoundsConfig.appsStackedVertically
@@ -553,28 +558,35 @@
: splitBoundsConfig.dividerWidthPercent;
Pair<Point, Point> taskViewSizes =
getGroupedTaskViewSizes(dp, splitBoundsConfig, parentWidth, parentHeight);
- if (dp.isLeftRightSplit) {
- int scaledDividerBar = Math.round(parentWidth * dividerScale);
- if (isRtl) {
- int translationX = taskViewSizes.second.x + scaledDividerBar;
- primarySnapshot.setTranslationX(-translationX);
- secondarySnapshot.setTranslationX(0);
+ if (!inSplitSelection) {
+ // Reset translations that aren't used in this method, but are used in other
+ // `RecentsPagedOrientationHandler` variants.
+ primarySnapshot.setTranslationY(0);
+
+ if (dp.isLeftRightSplit) {
+ int scaledDividerBar = Math.round(parentWidth * dividerScale);
+ if (isRtl) {
+ int translationX = taskViewSizes.second.x + scaledDividerBar;
+ primarySnapshot.setTranslationX(-translationX);
+ secondarySnapshot.setTranslationX(0);
+ } else {
+ int translationX = taskViewSizes.first.x + scaledDividerBar;
+ secondarySnapshot.setTranslationX(translationX);
+ primarySnapshot.setTranslationX(0);
+ }
+ secondarySnapshot.setTranslationY(spaceAboveSnapshot);
} else {
- int translationX = taskViewSizes.first.x + scaledDividerBar;
- secondarySnapshot.setTranslationX(translationX);
+ float finalDividerHeight = Math.round(totalThumbnailHeight * dividerScale);
+ float translationY =
+ taskViewSizes.first.y + spaceAboveSnapshot + finalDividerHeight;
+ secondarySnapshot.setTranslationY(translationY);
+
+ // Reset unused translations.
+ secondarySnapshot.setTranslationX(0);
primarySnapshot.setTranslationX(0);
}
-
- secondarySnapshot.setTranslationY(spaceAboveSnapshot);
- } else {
- float finalDividerHeight = Math.round(totalThumbnailHeight * dividerScale);
- float translationY = taskViewSizes.first.y + spaceAboveSnapshot + finalDividerHeight;
- secondarySnapshot.setTranslationY(translationY);
-
- // Reset unused translations.
- secondarySnapshot.setTranslationX(0);
- primarySnapshot.setTranslationX(0);
}
+
primarySnapshot.measure(
View.MeasureSpec.makeMeasureSpec(taskViewSizes.first.x, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(taskViewSizes.first.y, View.MeasureSpec.EXACTLY));
@@ -582,10 +594,6 @@
View.MeasureSpec.makeMeasureSpec(taskViewSizes.second.x, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(taskViewSizes.second.y,
View.MeasureSpec.EXACTLY));
- primarySnapshot.setScaleX(1);
- secondarySnapshot.setScaleX(1);
- primarySnapshot.setScaleY(1);
- secondarySnapshot.setScaleY(1);
}
@Override
@@ -662,11 +670,16 @@
iconAppChipView.setRotation(getDegreesRotated());
}
+ /**
+ * @param inSplitSelection Whether user currently has a task from this task group staged for
+ * split screen. If true, we have custom translations in place for the
+ * remaining icon, so we'll skip setting translations here.
+ */
@Override
public void setSplitIconParams(View primaryIconView, View secondaryIconView,
int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl,
- DeviceProfile deviceProfile, SplitBounds splitConfig) {
+ DeviceProfile deviceProfile, SplitBounds splitConfig, boolean inSplitSelection) {
FrameLayout.LayoutParams primaryIconParams =
(FrameLayout.LayoutParams) primaryIconView.getLayoutParams();
FrameLayout.LayoutParams secondaryIconParams = enableOverviewIconMenu()
@@ -680,20 +693,23 @@
secondaryIconParams.gravity = TOP | START;
secondaryIconParams.topMargin = primaryIconParams.topMargin;
secondaryIconParams.setMarginStart(primaryIconParams.getMarginStart());
- if (deviceProfile.isLeftRightSplit) {
- if (isRtl) {
- int secondarySnapshotWidth = groupedTaskViewWidth - primarySnapshotWidth;
- primaryAppChipView.setSplitTranslationX(-secondarySnapshotWidth);
+ if (!inSplitSelection) {
+ if (deviceProfile.isLeftRightSplit) {
+ if (isRtl) {
+ int secondarySnapshotWidth = groupedTaskViewWidth - primarySnapshotWidth;
+ primaryAppChipView.setSplitTranslationX(-secondarySnapshotWidth);
+ } else {
+ secondaryAppChipView.setSplitTranslationX(primarySnapshotWidth);
+ }
} else {
- secondaryAppChipView.setSplitTranslationX(primarySnapshotWidth);
+ primaryAppChipView.setSplitTranslationX(0);
+ secondaryAppChipView.setSplitTranslationX(0);
+ int dividerThickness = Math.min(splitConfig.visualDividerBounds.width(),
+ splitConfig.visualDividerBounds.height());
+ secondaryAppChipView.setSplitTranslationY(
+ primarySnapshotHeight + (deviceProfile.isTablet ? 0
+ : dividerThickness));
}
- } else {
- primaryAppChipView.setSplitTranslationX(0);
- secondaryAppChipView.setSplitTranslationX(0);
- int dividerThickness = Math.min(splitConfig.visualDividerBounds.width(),
- splitConfig.visualDividerBounds.height());
- secondaryAppChipView.setSplitTranslationY(
- primarySnapshotHeight + (deviceProfile.isTablet ? 0 : dividerThickness));
}
} else if (deviceProfile.isLeftRightSplit) {
// We calculate the "midpoint" of the thumbnail area, and place the icons there.
@@ -716,46 +732,53 @@
if (deviceProfile.isSeascape()) {
primaryIconParams.gravity = TOP | (isRtl ? END : START);
secondaryIconParams.gravity = TOP | (isRtl ? END : START);
- if (splitConfig.initiatedFromSeascape) {
- // if the split was initiated from seascape,
- // the task on the right (secondary) is slightly larger
- primaryIconView.setTranslationX(bottomToMidpointOffset - taskIconHeight);
- secondaryIconView.setTranslationX(bottomToMidpointOffset);
- } else {
- // if not,
- // the task on the left (primary) is slightly larger
- primaryIconView.setTranslationX(bottomToMidpointOffset + insetOffset
- - taskIconHeight);
- secondaryIconView.setTranslationX(bottomToMidpointOffset + insetOffset);
+ if (!inSplitSelection) {
+ if (splitConfig.initiatedFromSeascape) {
+ // if the split was initiated from seascape,
+ // the task on the right (secondary) is slightly larger
+ primaryIconView.setTranslationX(bottomToMidpointOffset - taskIconHeight);
+ secondaryIconView.setTranslationX(bottomToMidpointOffset);
+ } else {
+ // if not,
+ // the task on the left (primary) is slightly larger
+ primaryIconView.setTranslationX(bottomToMidpointOffset + insetOffset
+ - taskIconHeight);
+ secondaryIconView.setTranslationX(bottomToMidpointOffset + insetOffset);
+ }
}
} else {
primaryIconParams.gravity = TOP | (isRtl ? START : END);
secondaryIconParams.gravity = TOP | (isRtl ? START : END);
- if (!splitConfig.initiatedFromSeascape) {
- // if the split was initiated from landscape,
- // the task on the left (primary) is slightly larger
- primaryIconView.setTranslationX(-bottomToMidpointOffset);
- secondaryIconView.setTranslationX(-bottomToMidpointOffset + taskIconHeight);
- } else {
- // if not,
- // the task on the right (secondary) is slightly larger
- primaryIconView.setTranslationX(-bottomToMidpointOffset - insetOffset);
- secondaryIconView.setTranslationX(-bottomToMidpointOffset - insetOffset
- + taskIconHeight);
+ if (!inSplitSelection) {
+ if (!splitConfig.initiatedFromSeascape) {
+ // if the split was initiated from landscape,
+ // the task on the left (primary) is slightly larger
+ primaryIconView.setTranslationX(-bottomToMidpointOffset);
+ secondaryIconView.setTranslationX(-bottomToMidpointOffset + taskIconHeight);
+ } else {
+ // if not,
+ // the task on the right (secondary) is slightly larger
+ primaryIconView.setTranslationX(-bottomToMidpointOffset - insetOffset);
+ secondaryIconView.setTranslationX(-bottomToMidpointOffset - insetOffset
+ + taskIconHeight);
+ }
}
}
} else {
primaryIconParams.gravity = TOP | CENTER_HORIZONTAL;
- // shifts icon half a width left (height is used here since icons are square)
- primaryIconView.setTranslationX(-(taskIconHeight / 2f));
secondaryIconParams.gravity = TOP | CENTER_HORIZONTAL;
- secondaryIconView.setTranslationX(taskIconHeight / 2f);
+ if (!inSplitSelection) {
+ // shifts icon half a width left (height is used here since icons are square)
+ primaryIconView.setTranslationX(-(taskIconHeight / 2f));
+ secondaryIconView.setTranslationX(taskIconHeight / 2f);
+ }
}
- if (!enableOverviewIconMenu()) {
+ if (!enableOverviewIconMenu() && !inSplitSelection) {
primaryIconView.setTranslationY(0);
secondaryIconView.setTranslationY(0);
}
+
primaryIconView.setLayoutParams(primaryIconParams);
secondaryIconView.setLayoutParams(secondaryIconParams);
}
diff --git a/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt b/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt
index 06a0685..b8d0412 100644
--- a/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt
@@ -184,7 +184,8 @@
parentHeight: Int,
splitBoundsConfig: SplitConfigurationOptions.SplitBounds,
dp: DeviceProfile,
- isRtl: Boolean
+ isRtl: Boolean,
+ inSplitSelection: Boolean
)
/**
@@ -235,7 +236,8 @@
groupedTaskViewWidth: Int,
isRtl: Boolean,
deviceProfile: DeviceProfile,
- splitConfig: SplitConfigurationOptions.SplitBounds
+ splitConfig: SplitConfigurationOptions.SplitBounds,
+ inSplitSelection: Boolean
)
/*
diff --git a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
index a972e8c..bc91911 100644
--- a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
@@ -277,6 +277,10 @@
iconAppChipView.setRotation(degreesRotated)
}
+ /**
+ * @param inSplitSelection Whether user currently has a task from this task group staged for
+ * split screen. Currently this state is not reachable in fake seascape.
+ */
override fun measureGroupedTaskViewThumbnailBounds(
primarySnapshot: View,
secondarySnapshot: View,
@@ -284,7 +288,8 @@
parentHeight: Int,
splitBoundsConfig: SplitBounds,
dp: DeviceProfile,
- isRtl: Boolean
+ isRtl: Boolean,
+ inSplitSelection: Boolean
) {
val primaryParams = primarySnapshot.layoutParams as FrameLayout.LayoutParams
val secondaryParams = secondarySnapshot.layoutParams as FrameLayout.LayoutParams
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 256e29e..f708f4b 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -42,6 +42,8 @@
import android.window.TransitionInfo.Change
import android.window.WindowContainerToken
import androidx.annotation.VisibleForTesting
+import androidx.core.util.component1
+import androidx.core.util.component2
import com.android.app.animation.Interpolators
import com.android.launcher3.DeviceProfile
import com.android.launcher3.Flags.enableOverviewIconMenu
@@ -225,10 +227,22 @@
ObjectAnimator.ofFloat(iconView.splitTranslationY, MULTI_PROPERTY_VALUE, 0f)
)
}
+
+ val splitBoundsConfig =
+ (taskContainer.taskView as? GroupedTaskView)?.splitBoundsConfig ?: return
+ val (primarySnapshotViewSize, secondarySnapshotViewSize) =
+ taskContainer.taskView.pagedOrientationHandler.getGroupedTaskViewSizes(
+ deviceProfile,
+ splitBoundsConfig,
+ taskViewWidth,
+ taskViewHeight,
+ )
+ val snapshotViewSize =
+ 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 - snapshot.width) / 2f
- val finalScaleX: Float = taskViewWidth.toFloat() / snapshot.width
+ val centerThumbnailTranslationX: Float = (taskViewWidth - snapshotViewSize.x) / 2f
+ val finalScaleX: Float = taskViewWidth.toFloat() / snapshotViewSize.x
builder.add(
ObjectAnimator.ofFloat(snapshot, View.TRANSLATION_X, centerThumbnailTranslationX)
)
@@ -262,13 +276,13 @@
// thumbnail needs to take that into account. We should migrate to only using
// translations otherwise this asymmetry causes problems..
if (isPrimaryTaskSplitting) {
- centerThumbnailTranslationY = (thumbnailSize - snapshot.height) / 2f
+ centerThumbnailTranslationY = (thumbnailSize - snapshotViewSize.y) / 2f
centerThumbnailTranslationY +=
deviceProfile.overviewTaskThumbnailTopMarginPx.toFloat()
} else {
- centerThumbnailTranslationY = (thumbnailSize - snapshot.height) / 2f
+ centerThumbnailTranslationY = (thumbnailSize - snapshotViewSize.y) / 2f
}
- val finalScaleY: Float = thumbnailSize.toFloat() / snapshot.height
+ val finalScaleY: Float = thumbnailSize.toFloat() / snapshotViewSize.y
builder.add(
ObjectAnimator.ofFloat(snapshot, View.TRANSLATION_Y, centerThumbnailTranslationY)
)
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 1af12f1..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;
@@ -52,7 +52,6 @@
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
-import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -116,7 +115,6 @@
private static final String TAG = "SplitSelectStateCtor";
private RecentsViewContainer mContainer;
- private final Handler mHandler;
private final RecentsModel mRecentTasksModel;
@Nullable
private Runnable mActivityBackCallback;
@@ -182,12 +180,11 @@
}
};
- public SplitSelectStateController(RecentsViewContainer container, Handler handler,
+ public SplitSelectStateController(RecentsViewContainer container,
StateManager stateManager, DepthController depthController,
StatsLogManager statsLogManager, SystemUiProxy systemUiProxy, RecentsModel recentsModel,
Runnable activityBackCallback) {
mContainer = container;
- mHandler = handler;
mStatsLogManager = statsLogManager;
mSystemUiProxy = systemUiProxy;
mStateManager = stateManager;
@@ -366,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);
}
/**
@@ -374,7 +371,7 @@
* ratio and no callback.
*/
public void launchSplitTasks() {
- launchSplitTasks(SNAP_TO_50_50, null);
+ launchSplitTasks(SNAP_TO_2_50_50, null);
}
/**
@@ -568,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);
}
}
@@ -746,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/SystemWindowManagerProxy.java b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
index c3270dc..f3b984b8 100644
--- a/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
+++ b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
@@ -15,6 +15,7 @@
*/
package com.android.quickstep.util;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.view.Display.DEFAULT_DISPLAY;
import android.content.Context;
@@ -30,6 +31,8 @@
import com.android.launcher3.util.WindowBounds;
import com.android.launcher3.util.window.CachedDisplayInfo;
import com.android.launcher3.util.window.WindowManagerProxy;
+import com.android.quickstep.SystemUiProxy;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import java.util.List;
import java.util.Set;
@@ -66,6 +69,24 @@
}
@Override
+ public boolean showLockedTaskbarOnHome(Context displayInfoContext) {
+ if (!DesktopModeStatus.canEnterDesktopMode(displayInfoContext)) {
+ return false;
+ }
+ if (!DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(displayInfoContext)) {
+ return false;
+ }
+ final boolean isFreeformDisplay = displayInfoContext.getResources().getConfiguration()
+ .windowConfiguration.getWindowingMode() == WINDOWING_MODE_FREEFORM;
+ return isFreeformDisplay;
+ }
+
+ @Override
+ public boolean isHomeVisible(Context context) {
+ return SystemUiProxy.INSTANCE.get(context).getHomeVisibilityState().isHomeVisible();
+ }
+
+ @Override
public int getRotation(Context displayInfoContext) {
return displayInfoContext.getResources().getConfiguration().windowConfiguration
.getRotation();
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/DigitalWellBeingToast.kt b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.kt
index 7b97c23..c07b7fb 100644
--- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.kt
+++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.kt
@@ -59,10 +59,10 @@
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
- defStyleRes: Int = 0
+ defStyleRes: Int = 0,
) : TextView(context, attrs, defStyleAttr, defStyleRes) {
- private val recentsViewContainer =
- RecentsViewContainer.containerFromContext<RecentsViewContainer>(context)
+ private val recentsViewContainer: RecentsViewContainer =
+ RecentsViewContainer.containerFromContext(context)
private val launcherApps: LauncherApps? = context.getSystemService(LauncherApps::class.java)
@@ -138,7 +138,7 @@
usageLimit =
launcherApps?.getAppUsageLimit(
task.topComponent.packageName,
- UserHandle.of(task.key.userId)
+ UserHandle.of(task.key.userId),
)
} catch (e: Exception) {
Log.e(TAG, "Error initializing digital well being toast", e)
@@ -162,7 +162,7 @@
task: Task,
taskView: TaskView,
snapshotView: View,
- @StagePosition stagePosition: Int
+ @StagePosition stagePosition: Int,
) {
this.task = task
this.taskView = taskView
@@ -201,7 +201,7 @@
private fun getReadableDuration(
duration: Duration,
- @StringRes durationLessThanOneMinuteStringId: Int
+ @StringRes durationLessThanOneMinuteStringId: Int,
): String {
val hours = Math.toIntExact(duration.toHours())
val minutes = Math.toIntExact(duration.minusHours(hours.toLong()).toMinutes())
@@ -211,7 +211,7 @@
MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.NARROW)
.formatMeasures(
Measure(hours, MeasureUnit.HOUR),
- Measure(minutes, MeasureUnit.MINUTE)
+ Measure(minutes, MeasureUnit.MINUTE),
)
// Apply FormatWidth.WIDE if only the hour part is non-zero (unless forced).
hours > 0 ->
@@ -239,7 +239,7 @@
@VisibleForTesting
fun getBannerText(
remainingTime: Long = appRemainingTimeMs,
- forContentDesc: Boolean = false
+ forContentDesc: Boolean = false,
): String {
val duration =
Duration.ofMillis(
@@ -250,7 +250,7 @@
val readableDuration =
getReadableDuration(
duration,
- R.string.shorter_duration_less_than_one_minute /* forceFormatWidth */
+ R.string.shorter_duration_less_than_one_minute, /* forceFormatWidth */
)
val splitBannerConfig = getSplitBannerConfig()
return when {
@@ -277,7 +277,7 @@
Log.e(
TAG,
"Failed to open app usage settings for task " + task.topComponent.packageName,
- e
+ e,
)
}
}
@@ -285,13 +285,13 @@
private fun getContentDescriptionForTask(
task: Task,
appUsageLimitTimeMs: Long,
- appRemainingTimeMs: Long
+ appRemainingTimeMs: Long,
): String? =
if (appUsageLimitTimeMs >= 0 && appRemainingTimeMs >= 0)
context.getString(
R.string.task_contents_description_with_remaining_time,
task.titleDescription,
- getBannerText(appRemainingTimeMs, true /* forContentDesc */)
+ getBannerText(appRemainingTimeMs, true /* forContentDesc */),
)
else task.titleDescription
@@ -310,7 +310,7 @@
recentsViewContainer.deviceProfile,
splitBounds,
taskView.layoutParams.width,
- taskView.layoutParams.height
+ taskView.layoutParams.height,
)
if (stagePosition == STAGE_POSITION_TOP_OR_LEFT) {
snapshotWidth = groupedTaskSize.first.x
@@ -327,7 +327,7 @@
recentsViewContainer.deviceProfile,
snapshotWidth,
snapshotHeight,
- this
+ this,
)
}
@@ -340,7 +340,7 @@
recentsViewContainer.deviceProfile,
taskView.snapshotViews,
task.key.id,
- this
+ this,
)
this.translationX = translationX
this.splitOffsetTranslationY = translationY
@@ -372,7 +372,7 @@
if (taskView.containsMultipleTasks())
context.getString(
R.string.split_app_usage_settings,
- TaskUtils.getTitle(context, task)
+ TaskUtils.getTitle(context, task),
)
else context.getString(R.string.accessibility_app_usage_settings)
return AccessibilityNodeInfo.AccessibilityAction(getAccessibilityActionId(), label)
@@ -394,7 +394,7 @@
/** Used for grid task view, only showing icon and time */
SPLIT_GRID_BANNER_LARGE,
/** Used for grid task view, only showing icon */
- SPLIT_GRID_BANNER_SMALL
+ SPLIT_GRID_BANNER_SMALL,
}
val OPEN_APP_USAGE_SETTINGS_TEMPLATE: Intent = Intent(Settings.ACTION_APP_USAGE_SETTINGS)
diff --git a/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java b/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java
index 4ea7753..f17be05 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java
@@ -32,7 +32,6 @@
import com.android.launcher3.R;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
-import com.android.launcher3.widget.RoundedCornerEnforcement;
import java.util.stream.IntStream;
@@ -171,8 +170,7 @@
/** Corner radius from source view's outline, or enforced view. */
private static float getOutlineRadius(LauncherAppWidgetHostView hostView, View v) {
- if (RoundedCornerEnforcement.isRoundedCornerEnabled()
- && hostView.hasEnforcedCornerRadius()) {
+ if (hostView.hasEnforcedCornerRadius()) {
return hostView.getEnforcedCornerRadius();
} else if (v.getOutlineProvider() instanceof RemoteViewOutlineProvider
&& v.getClipToOutline()) {
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
index 3fd1a6b..92c1e93 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
@@ -65,31 +65,18 @@
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
setMeasuredDimension(widthSize, heightSize)
val splitBoundsConfig = splitBoundsConfig ?: return
- val initSplitTaskId = getThisTaskCurrentlyInSplitSelection()
- if (initSplitTaskId == INVALID_TASK_ID) {
- pagedOrientationHandler.measureGroupedTaskViewThumbnailBounds(
- taskContainers[0].snapshotView,
- taskContainers[1].snapshotView,
- widthSize,
- heightSize,
- splitBoundsConfig,
- container.deviceProfile,
- layoutDirection == LAYOUT_DIRECTION_RTL
- )
- } else {
- // Currently being split with this taskView, let the non-split selected thumbnail
- // take up full thumbnail area
- taskContainers
- .firstOrNull { it.task.key.id != initSplitTaskId }
- ?.snapshotView
- ?.measure(
- widthMeasureSpec,
- MeasureSpec.makeMeasureSpec(
- heightSize - container.deviceProfile.overviewTaskThumbnailTopMarginPx,
- MeasureSpec.EXACTLY
- )
- )
- }
+ val inSplitSelection = getThisTaskCurrentlyInSplitSelection() != INVALID_TASK_ID
+ pagedOrientationHandler.measureGroupedTaskViewThumbnailBounds(
+ taskContainers[0].snapshotView,
+ taskContainers[1].snapshotView,
+ widthSize,
+ heightSize,
+ splitBoundsConfig,
+ container.deviceProfile,
+ layoutDirection == LAYOUT_DIRECTION_RTL,
+ inSplitSelection,
+ )
+
if (!enableOverviewIconMenu()) {
updateIconPlacement()
}
@@ -173,6 +160,8 @@
val splitBoundsConfig = splitBoundsConfig ?: return
val taskIconHeight = container.deviceProfile.overviewTaskIconSizePx
val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL
+ val inSplitSelection = getThisTaskCurrentlyInSplitSelection() != INVALID_TASK_ID
+
if (enableOverviewIconMenu()) {
val groupedTaskViewSizes =
pagedOrientationHandler.getGroupedTaskViewSizes(
@@ -191,7 +180,8 @@
layoutParams.width,
isRtl,
container.deviceProfile,
- splitBoundsConfig
+ splitBoundsConfig,
+ inSplitSelection
)
} else {
pagedOrientationHandler.setSplitIconParams(
@@ -204,7 +194,8 @@
measuredWidth,
isRtl,
container.deviceProfile,
- splitBoundsConfig
+ splitBoundsConfig,
+ inSplitSelection
)
}
}
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index d20d0a5..00e57c2 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -16,17 +16,14 @@
package com.android.quickstep.views;
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 static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.WALLPAPER_ACTIVITY;
import android.annotation.TargetApi;
import android.content.Context;
@@ -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 {
@@ -268,7 +264,8 @@
super.onGestureAnimationStart(runningTasks, rotationTouchHelper);
DesktopVisibilityController desktopVisibilityController =
mContainer.getDesktopVisibilityController();
- if (!WALLPAPER_ACTIVITY.isEnabled(mContext) && desktopVisibilityController != null) {
+ if (!ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()
+ && desktopVisibilityController != null) {
// TODO: b/333533253 - Remove after flag rollout
desktopVisibilityController.setRecentsGestureStart();
}
@@ -291,7 +288,8 @@
}
}
super.onGestureAnimationEnd();
- if (!WALLPAPER_ACTIVITY.isEnabled(mContext) && desktopVisibilityController != null) {
+ if (!ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()
+ && desktopVisibilityController != null) {
// TODO: b/333533253 - Remove after flag rollout
desktopVisibilityController.setRecentsGestureEnd(endTarget);
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index bb46a2c..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;
@@ -258,7 +257,7 @@
* @param <STATE_TYPE> : the type of base state that will be used
*/
public abstract class RecentsView<
- CONTAINER_TYPE extends Context & RecentsViewContainer,
+ CONTAINER_TYPE extends Context & RecentsViewContainer & StatefulContainer<STATE_TYPE>,
STATE_TYPE extends BaseState<STATE_TYPE>> extends PagedView implements Insettable,
TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback,
TaskVisualsChangeListener {
@@ -325,25 +324,18 @@
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;
}
};
public static final int SCROLL_VIBRATION_PRIMITIVE =
- Utilities.ATLEAST_S ? VibrationEffect.Composition.PRIMITIVE_LOW_TICK : -1;
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK;
public static final float SCROLL_VIBRATION_PRIMITIVE_SCALE = 0.6f;
public static final VibrationEffect SCROLL_VIBRATION_FALLBACK =
VibrationConstants.EFFECT_TEXTURE_TICK;
@@ -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;
}
@@ -1209,6 +1211,7 @@
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
+
updateTaskStackListenerState();
mModel.getThumbnailCache().getHighResLoadingState().removeCallback(this);
mContainer.removeMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
@@ -1586,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);
}
@@ -1704,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;
}
@@ -1791,8 +1794,7 @@
@Override
protected void onScrollerAnimationAborted() {
- ActiveGestureLog.INSTANCE.addLog("scroller animation aborted",
- ActiveGestureErrorDetector.GestureEvent.SCROLLER_ANIMATION_ABORTED);
+ ActiveGestureProtoLogProxy.logOnScrollerAnimationAborted();
}
@Override
@@ -1872,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()) {
@@ -1911,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;
@@ -2743,9 +2743,6 @@
setEnableFreeScroll(false);
setEnableDrawingLiveTile(false);
setRunningTaskHidden(true);
- if (enableLargeDesktopWindowingTile()) {
- setNonRunningTaskCategoryHidden(true);
- }
setTaskIconScaledDown(true);
}
@@ -2869,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);
+ }
+ }
}
/**
@@ -2884,9 +2889,6 @@
setEnableDrawingLiveTile(mCurrentGestureEndTarget == GestureState.GestureEndTarget.RECENTS);
Log.d(TAG, "onGestureAnimationEnd - mEnableDrawingLiveTile: " + mEnableDrawingLiveTile);
setRunningTaskHidden(false);
- if (enableLargeDesktopWindowingTile()) {
- setNonRunningTaskCategoryHidden(false);
- }
animateUpTaskIconScale();
animateActionsViewIn();
@@ -3038,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;
@@ -3049,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) {
@@ -3143,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;
@@ -3166,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;
@@ -3175,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;
@@ -3186,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 {
@@ -3612,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) {
@@ -3645,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;
@@ -3671,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);
@@ -3803,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.
@@ -3971,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);
}
@@ -4146,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.
@@ -4179,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
@@ -4673,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;
@@ -4692,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
@@ -4709,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
@@ -4719,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();
@@ -4785,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;
@@ -4844,7 +4908,7 @@
}
distanceToOffscreen -= mLastComputedTaskEndPushOutDistance;
}
- return distanceToOffscreen * offsetProgress;
+ return distanceToOffscreen;
}
/**
@@ -4942,7 +5006,6 @@
mSplitSelectStateController.setAnimateCurrentTaskDismissal(
true /*animateCurrentTaskDismissal*/);
mSplitHiddenTaskViewIndex = indexOfChild(taskView);
- updateDesktopTaskVisibility(false /* visible */);
}
/**
@@ -4964,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);
+ }
+ }
}
}
@@ -4994,7 +5079,8 @@
mSplitSelectStateController.getSplitAnimationController()
.addInitialSplitFromPair(taskContainer, builder,
mContainer.getDeviceProfile(),
- mSplitHiddenTaskView.getWidth(), mSplitHiddenTaskView.getHeight(),
+ mSplitHiddenTaskView.getLayoutParams().width,
+ mSplitHiddenTaskView.getLayoutParams().height,
primaryTaskSelected);
builder.addOnFrameCallback(() -> {
if (!enableRefactorTaskThumbnail()) {
@@ -5179,7 +5265,6 @@
mSplitHiddenTaskView.setThumbnailVisibility(VISIBLE, INVALID_TASK_ID);
mSplitHiddenTaskView = null;
}
- updateDesktopTaskVisibility(true /* visible */);
}
private void safeRemoveDragLayerView(@Nullable View viewToRemove) {
@@ -5337,6 +5422,13 @@
mTempPointF);
setPivotX(mTempPointF.x);
setPivotY(mTempPointF.y);
+ runActionOnRemoteHandles(
+ remoteTargetHandle -> {
+ remoteTargetHandle.getTaskViewSimulator().setPivotOverride(
+ mTempPointF);
+ remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(
+ false);
+ });
}
});
} else if (!showAsGrid) {
@@ -5805,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);
@@ -5835,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/RecentsViewContainer.java b/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
index 8f19444..d8036aa 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
@@ -17,6 +17,7 @@
package com.android.quickstep.views;
import android.app.Activity;
+import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.LocusId;
@@ -31,7 +32,6 @@
import com.android.launcher3.BaseActivity;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.statehandlers.DesktopVisibilityController;
-import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.ScrimView;
@@ -44,7 +44,7 @@
* Returns an instance of an implementation of RecentsViewContainer
* @param context will find instance of recentsViewContainer from given context.
*/
- static <T extends RecentsViewContainer> T containerFromContext(Context context) {
+ static <T extends Context & RecentsViewContainer> T containerFromContext(Context context) {
if (context instanceof RecentsViewContainer) {
return (T) context;
} else if (context instanceof ContextWrapper) {
@@ -55,11 +55,6 @@
}
/**
- * Returns {@link SystemUiController} to manage various window flags to control system UI.
- */
- SystemUiController getSystemUiController();
-
- /**
* Returns {@link ScrimView}
*/
ScrimView getScrimView();
@@ -95,7 +90,7 @@
/**
* Returns overview actions view as a view
*/
- View getActionsView();
+ OverviewActionsView getActionsView();
/**
* @see BaseActivity#addForceInvisibleFlag(int)
@@ -143,12 +138,6 @@
void runOnBindToTouchInteractionService(Runnable r);
/**
- * @see Activity#getWindow()
- * @return Window
- */
- Window getWindow();
-
- /**
* @see
* BaseActivity#addMultiWindowModeChangedListener(BaseActivity.MultiWindowModeChangedListener)
* @param listener {@link BaseActivity.MultiWindowModeChangedListener}
@@ -177,6 +166,25 @@
boolean isRecentsViewVisible();
/**
+ * Begins transition to start home through container
+ */
+ default void startHome(){
+ // no op
+ }
+
+ /**
+ * Checks container to see if we can start home transition safely
+ */
+ boolean canStartHomeSafely();
+
+
+ /**
+ * Enter staged split directly from the current running app.
+ * @param leftOrTop if the staged split will be positioned left or top.
+ */
+ default void enterStageSplitFromRunningApp(boolean leftOrTop){}
+
+ /**
* Overwrites any logged item in Launcher that doesn't have a container with the
* {@link com.android.launcher3.touch.PagedOrientationHandler} in use for Overview.
*
diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
index 13c4f78..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
@@ -160,6 +160,8 @@
if (enableRefactorTaskThumbnail()) {
taskView.removeView(thumbnailView)
}
+ snapshotView.scaleX = 1f
+ snapshotView.scaleY = 1f
overlay.destroy()
}
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 b1ff4a1..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
@@ -56,36 +57,215 @@
)
@Test
- fun bubbleBarFlyoutView_noAvatar() {
- screenshotRule.screenshotTest("bubbleBarFlyoutView_noAvatar") { activity ->
+ fun bubbleBarFlyoutView_noAvatar_onRight() {
+ screenshotRule.screenshotTest("bubbleBarFlyoutView_noAvatar_onRight") { activity ->
activity.actionBar?.hide()
- val flyout = BubbleBarFlyoutView(context)
- flyout.setData(
+ val flyout =
+ BubbleBarFlyoutView(context, FakeBubbleBarFlyoutPositioner(isOnLeft = false))
+ flyout.showFromCollapsed(
BubbleBarFlyoutMessage(
senderAvatar = null,
senderName = "sender",
message = "message",
isGroupChat = false,
)
- )
+ ) {}
+ flyout.updateExpansionProgress(1f)
flyout
}
}
@Test
- fun bubbleBarFlyoutView_avatar() {
- screenshotRule.screenshotTest("bubbleBarFlyoutView_avatar") { activity ->
+ fun bubbleBarFlyoutView_noAvatar_onLeft() {
+ screenshotRule.screenshotTest("bubbleBarFlyoutView_noAvatar_onLeft") { activity ->
activity.actionBar?.hide()
- val flyout = BubbleBarFlyoutView(context)
- flyout.setData(
+ val flyout =
+ BubbleBarFlyoutView(context, FakeBubbleBarFlyoutPositioner(isOnLeft = true))
+ flyout.showFromCollapsed(
+ BubbleBarFlyoutMessage(
+ senderAvatar = null,
+ senderName = "sender",
+ message = "message",
+ isGroupChat = false,
+ )
+ ) {}
+ flyout.updateExpansionProgress(1f)
+ flyout
+ }
+ }
+
+ @Test
+ fun bubbleBarFlyoutView_noAvatar_longMessage() {
+ screenshotRule.screenshotTest("bubbleBarFlyoutView_noAvatar_longMessage") { activity ->
+ activity.actionBar?.hide()
+ 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
+ }
+ }
+
+ @Test
+ fun bubbleBarFlyoutView_avatar_onRight() {
+ screenshotRule.screenshotTest("bubbleBarFlyoutView_avatar_onRight") { activity ->
+ activity.actionBar?.hide()
+ val flyout =
+ BubbleBarFlyoutView(context, FakeBubbleBarFlyoutPositioner(isOnLeft = false))
+ flyout.showFromCollapsed(
BubbleBarFlyoutMessage(
senderAvatar = ColorDrawable(Color.RED),
senderName = "sender",
message = "message",
isGroupChat = true,
)
- )
+ ) {}
+ flyout.updateExpansionProgress(1f)
flyout
}
}
+
+ @Test
+ fun bubbleBarFlyoutView_avatar_onLeft() {
+ screenshotRule.screenshotTest("bubbleBarFlyoutView_avatar_onLeft") { activity ->
+ activity.actionBar?.hide()
+ val flyout =
+ BubbleBarFlyoutView(context, FakeBubbleBarFlyoutPositioner(isOnLeft = true))
+ flyout.showFromCollapsed(
+ BubbleBarFlyoutMessage(
+ senderAvatar = ColorDrawable(Color.RED),
+ senderName = "sender",
+ message = "message",
+ isGroupChat = true,
+ )
+ ) {}
+ flyout.updateExpansionProgress(1f)
+ flyout
+ }
+ }
+
+ @Test
+ fun bubbleBarFlyoutView_avatar_longMessage() {
+ screenshotRule.screenshotTest("bubbleBarFlyoutView_avatar_longMessage") { activity ->
+ activity.actionBar?.hide()
+ 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/TaskbarNavButtonControllerTest.java b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
index 399aea6..02d6218 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
@@ -20,6 +20,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -33,11 +34,13 @@
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.launcher3.contextualeducation.ContextualEduStatsManager;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TouchInteractionService;
import com.android.quickstep.util.AssistUtils;
+import com.android.systemui.contextualeducation.GestureType;
import org.junit.Before;
import org.junit.Test;
@@ -52,6 +55,10 @@
@Mock
SystemUiProxy mockSystemUiProxy;
+
+ @Mock
+ ContextualEduStatsManager mockContextualEduStatsManager;
+
@Mock
TouchInteractionService mockService;
@Mock
@@ -100,6 +107,7 @@
mockService,
mCallbacks,
mockSystemUiProxy,
+ mockContextualEduStatsManager,
mockHandler,
mockAssistUtils);
}
@@ -111,6 +119,13 @@
}
@Test
+ public void testPressBack_updateContextualEduData() {
+ mNavButtonController.onButtonClick(BUTTON_BACK, mockView);
+ verify(mockContextualEduStatsManager, times(1))
+ .updateEduStats(/* isTrackpad= */ eq(false), eq(GestureType.BACK));
+ }
+
+ @Test
public void testPressImeSwitcher() {
mNavButtonController.init(mockTaskbarControllers);
mNavButtonController.onButtonClick(BUTTON_IME_SWITCH, mockView);
@@ -195,12 +210,26 @@
}
@Test
+ public void testPressHome_updateContextualEduData() {
+ mNavButtonController.onButtonClick(BUTTON_HOME, mockView);
+ verify(mockContextualEduStatsManager, times(1))
+ .updateEduStats(/* isTrackpad= */ eq(false), eq(GestureType.HOME));
+ }
+
+ @Test
public void testPressRecents() {
mNavButtonController.onButtonClick(BUTTON_RECENTS, mockView);
assertThat(mOverviewToggleCount).isEqualTo(1);
}
@Test
+ public void testPressRecents_updateContextualEduData() {
+ mNavButtonController.onButtonClick(BUTTON_RECENTS, mockView);
+ verify(mockContextualEduStatsManager, times(1))
+ .updateEduStats(/* isTrackpad= */ eq(false), eq(GestureType.OVERVIEW));
+ }
+
+ @Test
public void testPressRecentsWithScreenPinned_noNavigationToOverview() {
mNavButtonController.updateSysuiFlags(SYSUI_STATE_SCREEN_PINNING);
mNavButtonController.onButtonClick(BUTTON_RECENTS, mockView);
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/BubbleBarInputConsumerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleBarInputConsumerTest.kt
index 785ec66..c8f50f7 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleBarInputConsumerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleBarInputConsumerTest.kt
@@ -49,6 +49,7 @@
@Mock private lateinit var bubbleDismissController: BubbleDismissController
@Mock private lateinit var bubbleBarPinController: BubbleBarPinController
@Mock private lateinit var bubblePinController: BubblePinController
+ @Mock private lateinit var bubbleBarSwipeController: BubbleBarSwipeController
@Mock private lateinit var bubbleCreator: BubbleCreator
@Mock private lateinit var motionEvent: MotionEvent
@@ -67,7 +68,8 @@
bubbleDismissController,
bubbleBarPinController,
bubblePinController,
- bubbleCreator
+ Optional.of(bubbleBarSwipeController),
+ bubbleCreator,
)
}
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
new file mode 100644
index 0000000..3b6952d
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleBarSwipeControllerTest.kt
@@ -0,0 +1,416 @@
+/*
+ * 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
+
+import android.animation.AnimatorTestRule
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController
+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
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.atLeastOnce
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@RunWith(AndroidJUnit4::class)
+class BubbleBarSwipeControllerTest {
+
+ companion object {
+ 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_UNDER_STASH = STASH_THRESHOLD - 10f
+ const val DOWN_OVER_STASH = STASH_THRESHOLD + 10f
+ }
+
+ private val context = ApplicationProvider.getApplicationContext<Context>()
+
+ @get:Rule(order = 0) val mockitoRule: MockitoRule = MockitoJUnit.rule()
+ @get:Rule(order = 1) val animatorTestRule: AnimatorTestRule = AnimatorTestRule(this)
+
+ private lateinit var bubbleBarSwipeController: BubbleBarSwipeController
+
+ @Mock private lateinit var bubbleBarController: BubbleBarController
+ @Mock private lateinit var bubbleBarViewController: BubbleBarViewController
+ @Mock private lateinit var bubbleStashController: BubbleStashController
+ @Mock private lateinit var bubbleStashedHandleViewController: BubbleStashedHandleViewController
+ @Mock private lateinit var bubbleDragController: BubbleDragController
+ @Mock private lateinit var bubbleDismissController: BubbleDismissController
+ @Mock private lateinit var bubbleBarPinController: BubbleBarPinController
+ @Mock private lateinit var bubblePinController: BubblePinController
+ @Mock private lateinit var bubbleCreator: BubbleCreator
+
+ @Before
+ fun setUp() {
+ val dimensionProvider =
+ object : BubbleBarSwipeController.DimensionProvider {
+ override val unstashThreshold: Int
+ get() = UNSTASH_THRESHOLD
+
+ override val expandThreshold: Int
+ get() = EXPAND_THRESHOLD
+
+ override val maxOverscroll: Int
+ get() = MAX_OVERSCROLL
+
+ override val stashThreshold: Int
+ get() = STASH_THRESHOLD
+ }
+ bubbleBarSwipeController = BubbleBarSwipeController(context, dimensionProvider)
+
+ val bubbleControllers =
+ BubbleControllers(
+ bubbleBarController,
+ bubbleBarViewController,
+ bubbleStashController,
+ Optional.of(bubbleStashedHandleViewController),
+ bubbleDragController,
+ bubbleDismissController,
+ bubbleBarPinController,
+ bubblePinController,
+ Optional.of(bubbleBarSwipeController),
+ bubbleCreator,
+ )
+
+ bubbleBarSwipeController.init(bubbleControllers)
+ }
+
+ // region Test that views have damped translation on swipe
+
+ private fun testViewsHaveDampedTranslationOnSwipe(swipe: Float) {
+ 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)
+ }
+ verify(bubbleStashedHandleViewController).setTranslationYForSwipe(dampedTranslation)
+ verify(bubbleBarViewController).setTranslationYForSwipe(dampedTranslation)
+ }
+
+ @Test
+ fun swipeUp_stashedBar_belowUnstashThreshold_viewsHaveDampedTranslation() {
+ setUpStashedBar()
+ testViewsHaveDampedTranslationOnSwipe(UP_BELOW_UNSTASH)
+ }
+
+ @Test
+ fun swipeUp_stashedBar_aboveUnstashThreshold_viewsHaveDampedTranslation() {
+ setUpStashedBar()
+ testViewsHaveDampedTranslationOnSwipe(UP_ABOVE_UNSTASH)
+ }
+
+ @Test
+ fun swipeUp_stashedBar_aboveExpandThreshold_viewsHaveDampedTranslation() {
+ setUpStashedBar()
+ testViewsHaveDampedTranslationOnSwipe(UP_ABOVE_EXPAND)
+ }
+
+ @Test
+ fun swipeUp_collapsedBar_aboveUnstashThreshold_viewsHaveDampedTranslation() {
+ setUpCollapsedBar()
+ testViewsHaveDampedTranslationOnSwipe(UP_ABOVE_UNSTASH)
+ }
+
+ @Test
+ fun swipeUp_collapsedBar_aboveExpandThreshold_viewsHaveDampedTranslation() {
+ setUpCollapsedBar()
+ 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()
+ bubbleBarSwipeController.swipeTo(swipe)
+ bubbleBarSwipeController.finish()
+ // We use a spring animation. Advance by 5 seconds to give it time to finish
+ animatorTestRule.advanceTimeBy(5000)
+ }
+ val handleSwipeTranslation = argumentCaptor<Float>()
+ val barSwipeTranslation = argumentCaptor<Float>()
+ verify(bubbleStashedHandleViewController, atLeastOnce())
+ .setTranslationYForSwipe(handleSwipeTranslation.capture())
+ verify(bubbleBarViewController, atLeastOnce())
+ .setTranslationYForSwipe(barSwipeTranslation.capture())
+
+ assertThat(handleSwipeTranslation.firstValue).isNonZero()
+ assertThat(handleSwipeTranslation.lastValue).isZero()
+
+ assertThat(barSwipeTranslation.firstValue).isNonZero()
+ assertThat(barSwipeTranslation.lastValue).isZero()
+ }
+
+ @Test
+ fun swipeUp_stashedBar_belowUnstashThreshold_animateTranslationToZeroOnFinish() {
+ setUpStashedBar()
+ testViewsTranslationResetOnFinish(UP_BELOW_UNSTASH)
+ }
+
+ @Test
+ fun swipeUp_stashedBar_aboveUnstashThreshold_animateTranslationToZeroOnFinish() {
+ setUpStashedBar()
+ testViewsTranslationResetOnFinish(UP_ABOVE_UNSTASH)
+ }
+
+ @Test
+ fun swipeUp_stashedBar_aboveExpandThreshold_animateTranslationToZeroOnFinish() {
+ setUpStashedBar()
+ testViewsTranslationResetOnFinish(UP_ABOVE_EXPAND)
+ }
+
+ @Test
+ fun swipeUp_collapsedBar_aboveUnstashThreshold_animateTranslationToZeroOnFinish() {
+ setUpCollapsedBar()
+ testViewsTranslationResetOnFinish(UP_ABOVE_UNSTASH)
+ }
+
+ @Test
+ fun swipeUp_collapsedBar_aboveExpandThreshold_animateTranslationToZeroOnFinish() {
+ setUpCollapsedBar()
+ testViewsTranslationResetOnFinish(UP_ABOVE_EXPAND)
+ }
+
+ @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 {
+ bubbleBarSwipeController.start()
+ bubbleBarSwipeController.swipeTo(UP_BELOW_UNSTASH)
+ }
+ verify(bubbleStashController, never()).showBubbleBar(any())
+ }
+
+ @Test
+ fun swipeUp_stashedBar_belowUnstashThreshold_isSwipeGestureFalse() {
+ setUpStashedBar()
+ getInstrumentation().runOnMainSync {
+ bubbleBarSwipeController.start()
+ bubbleBarSwipeController.swipeTo(UP_BELOW_UNSTASH)
+ }
+ assertThat(bubbleBarSwipeController.isSwipeGesture()).isFalse()
+ }
+
+ @Test
+ fun swipeUp_stashedBar_aboveUnstashThreshold_unstashBubbleBar() {
+ setUpStashedBar()
+ getInstrumentation().runOnMainSync {
+ bubbleBarSwipeController.start()
+ bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH)
+ }
+ verify(bubbleStashController).showBubbleBar(expandBubbles = false)
+ }
+
+ @Test
+ fun swipeUp_stashedBar_overUnstashThreshold_isSwipeGestureTrue() {
+ setUpStashedBar()
+ getInstrumentation().runOnMainSync {
+ bubbleBarSwipeController.start()
+ bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH)
+ }
+ assertThat(bubbleBarSwipeController.isSwipeGesture()).isTrue()
+ }
+
+ @Test
+ fun swipeUp_stashedBar_overUnstashThresholdMultipleTimes_unstashBubbleBarOnce() {
+ setUpStashedBar()
+ getInstrumentation().runOnMainSync {
+ bubbleBarSwipeController.start()
+ bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH)
+ bubbleBarSwipeController.swipeTo(UP_BELOW_UNSTASH)
+ bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH)
+ }
+ verify(bubbleStashController).showBubbleBar(expandBubbles = false)
+ }
+
+ @Test
+ fun swipeUp_stashedBar_overExpandThreshold_doesNotExpandBeforeFinish() {
+ setUpStashedBar()
+ getInstrumentation().runOnMainSync {
+ bubbleBarSwipeController.start()
+ bubbleBarSwipeController.swipeTo(UP_ABOVE_EXPAND)
+ }
+ verify(bubbleStashController).showBubbleBar(expandBubbles = false)
+ getInstrumentation().runOnMainSync { bubbleBarSwipeController.finish() }
+ verify(bubbleStashController).showBubbleBar(expandBubbles = true)
+ }
+
+ @Test
+ fun swipeUp_stashedBar_overExpandThreshold_isSwipeGestureTrue() {
+ setUpStashedBar()
+ getInstrumentation().runOnMainSync {
+ bubbleBarSwipeController.start()
+ bubbleBarSwipeController.swipeTo(UP_ABOVE_EXPAND)
+ }
+ assertThat(bubbleBarSwipeController.isSwipeGesture()).isTrue()
+ }
+
+ @Test
+ fun swipeUp_stashedBar_overExpandThresholdAndBackDown_doesNotExpandAfterFinish() {
+ setUpStashedBar()
+ getInstrumentation().runOnMainSync {
+ bubbleBarSwipeController.start()
+ bubbleBarSwipeController.swipeTo(UP_ABOVE_EXPAND)
+ bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH)
+ }
+ verify(bubbleStashController).showBubbleBar(expandBubbles = false)
+ getInstrumentation().runOnMainSync { bubbleBarSwipeController.finish() }
+ verify(bubbleStashController).showBubbleBar(expandBubbles = false)
+ }
+
+ @Test
+ 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_OVER_STASH)
+ bubbleBarSwipeController.finish()
+ }
+ verify(bubbleStashedHandleViewController, never()).setTranslationYForSwipe(any())
+ verify(bubbleBarViewController, never()).setTranslationYForSwipe(any())
+ verify(bubbleStashController, never()).showBubbleBar(any())
+ }
+
+ // endregion
+
+ // region Test swipe interactions on collapsed bar
+
+ @Test
+ fun swipeDown_collapsedBar_underStashThreshold_doesNotHideBar() {
+ setUpCollapsedBar()
+ getInstrumentation().runOnMainSync {
+ bubbleBarSwipeController.start()
+ bubbleBarSwipeController.swipeTo(DOWN_UNDER_STASH)
+ bubbleBarSwipeController.finish()
+ }
+ 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)
+ whenever(bubbleBarViewController.isExpanded).thenReturn(false)
+ }
+
+ private fun setUpCollapsedBar() {
+ whenever(bubbleStashController.isStashed).thenReturn(false)
+ whenever(bubbleStashController.isBubbleBarVisible()).thenReturn(true)
+ whenever(bubbleBarViewController.isExpanded).thenReturn(false)
+ }
+
+ private fun setUpExpandedBar() {
+ whenever(bubbleStashController.isStashed).thenReturn(false)
+ whenever(bubbleStashController.isBubbleBarVisible()).thenReturn(true)
+ whenever(bubbleBarViewController.isExpanded).thenReturn(true)
+ }
+}
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/TransientBubbleStashControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt
index d4a3b3a..412bdeb 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,6 +29,7 @@
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
@@ -36,6 +37,7 @@
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 +61,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
@@ -196,6 +198,93 @@
}
@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 {
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
index 2a0aa4c..87a7cda 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
@@ -41,11 +41,14 @@
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
import com.android.launcher3.LauncherRootView;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.statemanager.StatefulContainer;
import com.android.launcher3.util.SystemUiController;
+import com.android.quickstep.fallback.window.RecentsWindowManager;
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.RecentsViewContainer;
@@ -62,16 +65,14 @@
import java.util.HashMap;
public abstract class AbsSwipeUpHandlerTestCase<
- RECENTS_CONTAINER extends Context & RecentsViewContainer,
- STATE extends BaseState<STATE>,
- RECENTS_VIEW extends RecentsView<RECENTS_CONTAINER, STATE>,
+ RECENTS_CONTAINER extends Context & RecentsViewContainer & StatefulContainer<STATE>,
+ STATE extends BaseState<STATE>, RECENTS_VIEW extends RecentsView<RECENTS_CONTAINER, STATE>,
ACTIVITY_TYPE extends StatefulActivity<STATE> & RecentsViewContainer,
ACTIVITY_INTERFACE extends BaseActivityInterface<STATE, ACTIVITY_TYPE>,
SWIPE_HANDLER extends AbsSwipeUpHandler<RECENTS_CONTAINER, RECENTS_VIEW, STATE>> {
protected final Context mContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
- protected final TaskAnimationManager mTaskAnimationManager = new TaskAnimationManager(mContext);
protected final RecentsAnimationDeviceState mRecentsAnimationDeviceState =
new RecentsAnimationDeviceState(mContext, true);
protected final InputConsumerController mInputConsumerController =
@@ -105,6 +106,9 @@
/* minimizedHomeBounds= */ null,
new Bundle());
+ protected RecentsWindowManager mRecentsWindowManager;
+ protected TaskAnimationManager mTaskAnimationManager;
+
@Mock protected ACTIVITY_INTERFACE mActivityInterface;
@Mock protected ActivityInitListener<?> mActivityInitListener;
@Mock protected RecentsAnimationController mRecentsAnimationController;
@@ -119,6 +123,16 @@
public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Before
+ public void setUpTaskAnimationManager() {
+ runOnMainSync(() -> {
+ if(Flags.enableFallbackOverviewInWindow()){
+ mRecentsWindowManager = new RecentsWindowManager(mContext);
+ }
+ mTaskAnimationManager = new TaskAnimationManager(mContext, mRecentsWindowManager);
+ });
+ }
+
+ @Before
public void setUpRunningTaskInfo() {
mRunningTaskInfo.baseIntent = new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_HOME)
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/FallbackSwipeHandlerTestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/FallbackSwipeHandlerTestCase.java
index dd0b4b3..8d6906f 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/FallbackSwipeHandlerTestCase.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/FallbackSwipeHandlerTestCase.java
@@ -30,7 +30,7 @@
public class FallbackSwipeHandlerTestCase extends AbsSwipeUpHandlerTestCase<
RecentsActivity,
RecentsState,
- FallbackRecentsView,
+ FallbackRecentsView<RecentsActivity>,
RecentsActivity,
FallbackActivityInterface,
FallbackSwipeHandler> {
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.kt
index 7c48ea4..0a60774 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.kt
@@ -34,6 +34,7 @@
import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NOTIFICATION_DOT_ENABLED
import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_THEMED_ICON_DISABLED
import com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY
+import com.android.launcher3.util.DaggerSingletonTracker
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
@@ -62,6 +63,7 @@
@Mock private lateinit var mMockLogger: StatsLogManager.StatsLogger
@Captor private lateinit var mEventCaptor: ArgumentCaptor<StatsLogManager.EventEnum>
+ @Mock private lateinit var mTracker: DaggerSingletonTracker
private var mDefaultThemedIcons = false
private var mDefaultAllowRotation = false
@@ -79,7 +81,7 @@
// To match the default value of ALLOW_ROTATION
LauncherPrefs.get(mContext).put(item = ALLOW_ROTATION, value = false)
- mSystemUnderTest = SettingsChangeLogger(mContext, mStatsLogManager)
+ mSystemUnderTest = SettingsChangeLogger(mContext, mStatsLogManager, mTracker)
}
@After
@@ -90,7 +92,7 @@
@Test
fun loggingPrefs_correctDefaultValue() {
- val systemUnderTest = SettingsChangeLogger(mContext, mStatsLogManager)
+ val systemUnderTest = SettingsChangeLogger(mContext, mStatsLogManager, mTracker)
assertThat(systemUnderTest.loggingPrefs[ALLOW_ROTATION_PREFERENCE_KEY]!!.defaultValue)
.isFalse()
@@ -117,7 +119,7 @@
LauncherPrefs.get(mContext).put(item = ALLOW_ROTATION, value = true)
// This a new object so the values of mLoggablePrefs will be different
- SettingsChangeLogger(mContext, mStatsLogManager).logSnapshot(mInstanceId)
+ SettingsChangeLogger(mContext, mStatsLogManager, mTracker).logSnapshot(mInstanceId)
verify(mMockLogger, atLeastOnce()).log(mEventCaptor.capture())
val capturedEvents = mEventCaptor.allValues
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 fc4c4f6..cb70694 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
@@ -22,7 +22,6 @@
import android.content.ComponentName
import android.content.Intent
import android.graphics.Rect
-import android.os.Handler
import android.os.UserHandle
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.launcher3.LauncherState
@@ -37,10 +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 java.util.function.Consumer
+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
@@ -52,8 +51,10 @@
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
@RunWith(AndroidJUnit4::class)
class SplitSelectStateControllerTest {
@@ -63,11 +64,11 @@
private val statsLogManager: StatsLogManager = mock()
private val statsLogger: StatsLogger = mock()
private val stateManager: StateManager<LauncherState, StatefulActivity<LauncherState>> = mock()
- private val handler: Handler = mock()
private val context: RecentsViewContainer = mock()
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
@@ -75,6 +76,7 @@
private val nonPrimaryUserHandle = UserHandle(ActivityManager.RunningTaskInfo().userId + 10)
private var taskIdCounter = 0
+
private fun getUniqueId(): Int {
return ++taskIdCounter
}
@@ -87,13 +89,12 @@
splitSelectStateController =
SplitSelectStateController(
context,
- handler,
stateManager,
depthController,
statsLogManager,
systemUiProxy,
recentsModel,
- null /*activityBackCallback*/
+ null, /*activityBackCallback*/
)
}
@@ -103,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)
@@ -125,7 +126,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(nonMatchingComponent),
false /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -144,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)
@@ -162,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)
}
@@ -178,7 +179,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(matchingComponent),
false /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -197,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)
@@ -219,7 +220,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(nonPrimaryUserComponent),
false /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -240,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)
@@ -258,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)
@@ -275,7 +276,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(nonPrimaryUserComponent),
false /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -294,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)
@@ -312,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)
}
@@ -328,7 +329,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(matchingComponent),
false /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -351,7 +352,7 @@
val groupTask2 =
generateGroupTask(
ComponentName("pomegranate", "juice"),
- ComponentName(matchingPackage, matchingClass)
+ ComponentName(matchingPackage, matchingClass),
)
val tasks: ArrayList<GroupTask> = ArrayList()
tasks.add(groupTask2)
@@ -366,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)
}
@@ -382,7 +383,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(nonMatchingComponent, matchingComponent),
false /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -404,7 +405,7 @@
val groupTask2 =
generateGroupTask(
ComponentName("pomegranate", "juice"),
- ComponentName(matchingPackage, matchingClass)
+ ComponentName(matchingPackage, matchingClass),
)
val tasks: ArrayList<GroupTask> = ArrayList()
tasks.add(groupTask2)
@@ -418,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*/)
@@ -435,7 +436,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(matchingComponent, matchingComponent),
false /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -455,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)
@@ -474,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)
}
@@ -501,7 +502,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(matchingComponent, matchingComponent),
false /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -527,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)
@@ -553,7 +554,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(matchingComponent2, matchingComponent),
true /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -570,7 +571,7 @@
-1 /*stagePosition*/,
ItemInfo(),
null /*splitEvent*/,
- 10 /*alreadyRunningTask*/
+ 10, /*alreadyRunningTask*/
)
assertTrue(splitSelectStateController.isSplitSelectActive)
}
@@ -582,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)
}
@@ -625,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()
@@ -645,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),
)
}
@@ -654,7 +657,7 @@
task1ComponentName: ComponentName,
userHandle1: UserHandle,
task2ComponentName: ComponentName,
- userHandle2: UserHandle
+ userHandle2: UserHandle,
): GroupTask {
val task1 = Task()
var taskInfo = ActivityManager.RunningTaskInfo()
@@ -677,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/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 113b8a4..5ff2af7 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -395,7 +395,6 @@
@Test
@NavigationModeSwitch
@PortraitLandscape
- @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/325659406
public void testQuickSwitchFromHome() throws Exception {
startTestActivity(2);
mLauncher.goHome().quickSwitchToPreviousApp();
diff --git a/quickstep/tests/src/com/android/quickstep/TaskAnimationManagerTest.java b/quickstep/tests/src/com/android/quickstep/TaskAnimationManagerTest.java
index 28c8a4a..ec07b93 100644
--- a/quickstep/tests/src/com/android/quickstep/TaskAnimationManagerTest.java
+++ b/quickstep/tests/src/com/android/quickstep/TaskAnimationManagerTest.java
@@ -29,6 +29,8 @@
import androidx.test.filters.SmallTest;
+import com.android.quickstep.fallback.window.RecentsWindowManager;
+
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
@@ -42,6 +44,9 @@
private Context mContext;
@Mock
+ private RecentsWindowManager mRecentsWindowManager;
+
+ @Mock
private SystemUiProxy mSystemUiProxy;
private TaskAnimationManager mTaskAnimationManager;
@@ -49,7 +54,7 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mTaskAnimationManager = new TaskAnimationManager(mContext) {
+ mTaskAnimationManager = new TaskAnimationManager(mContext, mRecentsWindowManager) {
@Override
SystemUiProxy getSystemUiProxy() {
return mSystemUiProxy;
diff --git a/res/drawable/desktop_mode_ic_taskbar_menu_new_window.xml b/res/drawable/desktop_mode_ic_taskbar_menu_new_window.xml
new file mode 100644
index 0000000..b96a596
--- /dev/null
+++ b/res/drawable/desktop_mode_ic_taskbar_menu_new_window.xml
@@ -0,0 +1,25 @@
+<?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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
+ <path
+ android:pathData="M15 16V14H13V12.5H15V10.5H16.5V12.5H18.5V14H16.5V16H15ZM3.5 17C3.09722 17 2.74306 16.8542 2.4375 16.5625C2.14583 16.2569 2 15.9028 2 15.5V4.5C2 4.08333 2.14583 3.72917 2.4375 3.4375C2.74306 3.14583 3.09722 3 3.5 3H14.5C14.9167 3 15.2708 3.14583 15.5625 3.4375C15.8542 3.72917 16 4.08333 16 4.5V9H14.5V7H3.5V15.5H13.625V17H3.5ZM3.5 5.5H14.5V4.5H3.5V5.5ZM3.5 5.5V4.5V5.5Z"
+ android:fillColor="#1C1C14"/>
+</vector>
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-af/strings.xml b/res/values-af/strings.xml
index 490a7c2..4f62bda 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -31,6 +31,8 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Verdeelde skerm"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Programinligting vir %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Gebruikinstellings vir %1$s"</string>
+ <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
+ <skip />
<string name="save_app_pair" msgid="5647523853662686243">"Stoor apppaar"</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">"Hierdie apppaar word nie op hierdie toestel gesteun nie"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 7292eec..53dc4ba 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -31,6 +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>
+ <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-ar/strings.xml b/res/values-ar/strings.xml
index 06fc0a8..04c2f2f 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -31,6 +31,8 @@
<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="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-as/strings.xml b/res/values-as/strings.xml
index cd6e347..4d377c2 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -31,6 +31,8 @@
<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="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-az/strings.xml b/res/values-az/strings.xml
index b8d660f..4c2ba9b 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -31,6 +31,8 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Ekran bölünməsi"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s ilə bağlı tətbiq məlumatı"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s üzrə istifadə ayarları"</string>
+ <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
+ <skip />
<string name="save_app_pair" msgid="5647523853662686243">"Tətbiq cütünü saxlayın"</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">"Bu tətbiq cütü bu cihazda dəstəklənmir"</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 4d4764e..002c800 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -31,6 +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>
+ <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 641509e..0984f32 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -31,6 +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>
+ <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-bg/strings.xml b/res/values-bg/strings.xml
index 3ce3c5f..270374d 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -31,6 +31,8 @@
<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="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-bn/strings.xml b/res/values-bn/strings.xml
index 9b23590..5097784 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -31,6 +31,8 @@
<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="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 4a34da7..2b168f6 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -31,6 +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>
+ <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-ca/strings.xml b/res/values-ca/strings.xml
index c341ec7..77642f0 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -31,6 +31,8 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantalla dividida"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informació de l\'aplicació %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Configuració d\'ús de %1$s"</string>
+ <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
+ <skip />
<string name="save_app_pair" msgid="5647523853662686243">"Desa la parella d\'aplicacions"</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">"Aquesta parella d\'aplicacions no s\'admet en aquest dispositiu"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index d3512c9..08747e0 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -31,6 +31,8 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Rozdělit obrazovku"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informace o aplikaci %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Nastavení využití pro aplikaci %1$s"</string>
+ <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
+ <skip />
<string name="save_app_pair" msgid="5647523853662686243">"Uložit dvojici aplikací"</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">"Tento pár aplikací není na tomto zařízení podporován"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 8aae860..0bf8514 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -31,6 +31,8 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Opdel skærm"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Appinfo for %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Indstillinger for brug af %1$s"</string>
+ <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
+ <skip />
<string name="save_app_pair" msgid="5647523853662686243">"Gem appsammenknytning"</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">"Denne appsammenknytning understøttes ikke på enheden"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 374f5a1..c3628ea 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -31,6 +31,8 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Splitscreen"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"App-Info für %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Nutzungseinstellungen für %1$s"</string>
+ <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
+ <skip />
<string name="save_app_pair" msgid="5647523853662686243">"App-Paar speichern"</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">"Dieses App-Paar wird auf diesem Gerät nicht unterstützt"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index cafe86e..85c6a31 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -31,6 +31,8 @@
<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="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-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 1b0722d..aee49be 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -31,6 +31,8 @@
<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="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-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index de41d2c..8feccb0 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -31,6 +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>
+ <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-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 1b0722d..f7b04a3 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -31,6 +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>
+ <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-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 1b0722d..aee49be 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -31,6 +31,8 @@
<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="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-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index a856340..769567c 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -31,6 +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>
+ <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-rUS/strings.xml b/res/values-es-rUS/strings.xml
index ba1b0af..e051843 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -31,6 +31,8 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantalla dividida"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Información de la app de %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Configuración del uso de %1$s"</string>
+ <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
+ <skip />
<string name="save_app_pair" msgid="5647523853662686243">"Guardar vinculación"</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">"No se admite esta vinculación de apps en este dispositivo"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index ad12192..ddcee65 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -31,6 +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>
+ <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-et/strings.xml b/res/values-et/strings.xml
index 96d0b2c..844a0c7 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -31,6 +31,8 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Jagatud ekraanikuva"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Rakenduse teave: %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Kasutuse seaded: %1$s"</string>
+ <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
+ <skip />
<string name="save_app_pair" msgid="5647523853662686243">"Salvesta rakendusepaar"</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">"See rakendusepaar ei ole selles seadmes toetatud"</string>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index bc9b8c1..8c9375a 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -31,6 +31,8 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantaila zatitzea"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s aplikazioari buruzko informazioa"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s aplikazioaren erabilera-ezarpenak"</string>
+ <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
+ <skip />
<string name="save_app_pair" msgid="5647523853662686243">"Gorde aplikazio parea"</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">"Aplikazio pare hori ez da onartzen gailu honetan"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index c167194..c0a6fe3 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -31,6 +31,8 @@
<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="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>
@@ -130,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-fi/strings.xml b/res/values-fi/strings.xml
index 007d077..567a1ea 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -31,6 +31,8 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Jaettu näyttö"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Sovellustiedot: %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Käyttöasetus tälle: %1$s"</string>
+ <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
+ <skip />
<string name="save_app_pair" msgid="5647523853662686243">"Tallenna sovelluspari"</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">"Sovellusparia ei tueta tällä laitteella"</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index c443505..a886420 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -31,6 +31,8 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Écran divisé"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Renseignements sur l\'appli pour %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Paramètres d\'utilisation pour %1$s"</string>
+ <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
+ <skip />
<string name="save_app_pair" msgid="5647523853662686243">"Enr. paire d\'applis"</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">"Cette paire d\'applis n\'est pas prise en charge sur cet appareil"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 4f5d111..d96bd98 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -31,6 +31,8 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Écran partagé"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Infos sur l\'appli pour %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Paramètres d\'utilisation pour %1$s"</string>
+ <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
+ <skip />
<string name="save_app_pair" msgid="5647523853662686243">"Enregistrer une paire d\'applis"</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">"Cette paire d\'applications n\'est pas prise en charge sur cet appareil"</string>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index ff7c029..c224a19 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -31,6 +31,8 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantalla dividida"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Información da aplicación para %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Configuración de uso para %1$s"</string>
+ <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
+ <skip />
<string name="save_app_pair" msgid="5647523853662686243">"Gardar parella de apps"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"O dispositivo non admite este emparellamento de aplicacións"</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 872faef..732a8e4 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -31,6 +31,8 @@
<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="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-hi/strings.xml b/res/values-hi/strings.xml
index a44b874..d4fb0ca 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -31,6 +31,8 @@
<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="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-hr/strings.xml b/res/values-hr/strings.xml
index d9f2072..a9fd14e 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -31,6 +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>
+ <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>
@@ -189,7 +190,7 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filtrirajte"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Nije uspjelo: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Privatni prostor"</string>
- <string name="private_space_secondary_label" msgid="9203933341714508907">"Dodirnite da biste postavili ili otvorili"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"Dodirnite za postavljanje ili otvaranje"</string>
<string name="ps_container_title" msgid="4391796149519594205">"Privatno"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Postavke privatnog prostora"</string>
<string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Privatno, otključano."</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 6bc8b70..1a38777 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -31,6 +31,8 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Osztott képernyő"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Alkalmazásinformáció a következőhöz: %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"A(z) %1$s használati beállításai"</string>
+ <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
+ <skip />
<string name="save_app_pair" msgid="5647523853662686243">"Alkalmazáspár mentése"</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">"Ezt az alkalmazáspárt nem támogatja az eszköz"</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 69b320d..eaaf435 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -31,6 +31,8 @@
<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="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-in/strings.xml b/res/values-in/strings.xml
index 58a429f..62e9d9d 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -31,6 +31,8 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Layar terpisah"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Info aplikasi untuk %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Setelan penggunaan untuk %1$s"</string>
+ <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
+ <skip />
<string name="save_app_pair" msgid="5647523853662686243">"Simpan pasangan aplikasi"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Pasangan aplikasi ini tidak didukung di perangkat ini"</string>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 95bd21f..0014317 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -31,6 +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>
+ <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-it/strings.xml b/res/values-it/strings.xml
index 3c01cd4..100da6c 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -31,6 +31,8 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Schermo diviso"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informazioni sull\'app %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Impostazioni di utilizzo per %1$s"</string>
+ <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
+ <skip />
<string name="save_app_pair" msgid="5647523853662686243">"Salva coppia di app"</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">"Questa coppia di app non è supportata su questo dispositivo"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index f198166..f85e571 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -31,6 +31,8 @@
<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="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-ja/strings.xml b/res/values-ja/strings.xml
index d2f9a97..fc89041 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -31,6 +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>
+ <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 e67cc41..f099bcd 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -31,6 +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>
+ <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 d5ccae5..ebaacc9 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -31,6 +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>
+ <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 3c9135b..b05eeb0 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -31,6 +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>
+ <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>
@@ -117,7 +118,7 @@
<string name="folder_name_format_exact" msgid="8626242716117004803">"ថត៖ <xliff:g id="NAME">%1$s</xliff:g>, ធាតុ <xliff:g id="SIZE">%2$d</xliff:g>"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"ថត៖ <xliff:g id="NAME">%1$s</xliff:g>, ធាតុ <xliff:g id="SIZE">%2$d</xliff:g> ឬច្រើនជាងនេះ"</string>
<string name="app_pair_name_format" msgid="8134106404716224054">"គូកម្មវិធី៖ <xliff:g id="APP1">%1$s</xliff:g> និង <xliff:g id="APP2">%2$s</xliff:g>"</string>
- <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"ផ្ទាំងរូបភាព និងរចនាប័ទ្ម"</string>
+ <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"ផ្ទាំងរូបភាព និងរចនាបថ"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"កែអេក្រង់ដើម"</string>
<string name="settings_button_text" msgid="8873672322605444408">"ការកំណត់ទំព័រដើម"</string>
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"បានបិទដំណើរការដោយអ្នកគ្រប់គ្រងរបស់អ្នក"</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index ab84833..eda9fb8 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -31,6 +31,8 @@
<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="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-ko/strings.xml b/res/values-ko/strings.xml
index 318cd00..94ebd15 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -31,6 +31,8 @@
<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="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 856e2b2..753d2dd 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -31,6 +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>
+ <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-lo/strings.xml b/res/values-lo/strings.xml
index 3d1a6c9..741556a 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -31,6 +31,8 @@
<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="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 4c9bd9b..6879862 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -31,6 +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>
+ <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-lv/strings.xml b/res/values-lv/strings.xml
index 0a82705..de00e4d 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -31,6 +31,8 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Sadalīt ekrānu"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s: informācija par lietotni"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Lietojuma iestatījumi: %1$s"</string>
+ <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
+ <skip />
<string name="save_app_pair" msgid="5647523853662686243">"Saglabāt lietotņu pāri"</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">"Šis lietotņu pāris netiek atbalstīts šajā ierīcē"</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 887ca82..91b30c9 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -31,6 +31,8 @@
<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="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-ml/strings.xml b/res/values-ml/strings.xml
index dda5679..b9e68e0 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -31,6 +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>
+ <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-mn/strings.xml b/res/values-mn/strings.xml
index 49d71c2..bba7e16 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -31,6 +31,8 @@
<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="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-mr/strings.xml b/res/values-mr/strings.xml
index fdf864a..548e764 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -31,6 +31,8 @@
<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="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 b86f657..5ddf8a2 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -31,6 +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>
+ <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-my/strings.xml b/res/values-my/strings.xml
index 7e8fd14..e94dd8e 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -31,6 +31,8 @@
<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="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-nb/strings.xml b/res/values-nb/strings.xml
index e2be4ff..38003eb 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -31,6 +31,8 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Delt skjerm"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Appinformasjon for %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Bruksinnstillinger for %1$s"</string>
+ <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
+ <skip />
<string name="save_app_pair" msgid="5647523853662686243">"Lagre app-paret"</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">"Denne apptilkoblingen støttes ikke på denne enheten"</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index fa2e59b..4fae68b 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -31,6 +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>
+ <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-nl/strings.xml b/res/values-nl/strings.xml
index 9271b96..1c2bdc8 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -31,6 +31,8 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Gesplitst scherm"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"App-info voor %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Gebruiksinstellingen voor %1$s"</string>
+ <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
+ <skip />
<string name="save_app_pair" msgid="5647523853662686243">"App-paar opslaan"</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">"Dit app-paar wordt niet ondersteund op dit apparaat"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 98d52de..9a03926 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -31,6 +31,8 @@
<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="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-pa/strings.xml b/res/values-pa/strings.xml
index 782979e..461513c 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -31,6 +31,8 @@
<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="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-pl/strings.xml b/res/values-pl/strings.xml
index 71e569c..eeb9eb3 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -31,6 +31,8 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Podziel ekran"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacje o aplikacji: %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s – ustawienia użycia"</string>
+ <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
+ <skip />
<string name="save_app_pair" msgid="5647523853662686243">"Zapisz parę aplikacji"</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 para aplikacji nie jest obsługiwana na tym urządzeniu"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 1c44d9b..d2b214e 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -31,6 +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>
+ <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 3f44591..f864b3e 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -31,6 +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>
+ <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-ro/strings.xml b/res/values-ro/strings.xml
index b37d93b..fbab69c 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -31,6 +31,8 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Ecran împărțit"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informații despre aplicație pentru %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Setări de utilizare pentru %1$s"</string>
+ <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
+ <skip />
<string name="save_app_pair" msgid="5647523853662686243">"Salvează perechea de aplicații"</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">"Perechea de aplicații nu este acceptată pe acest dispozitiv"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 321bf37..126e3ad 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -31,6 +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>
+ <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 91c818a..328f2f5 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -31,6 +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>
+ <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-sk/strings.xml b/res/values-sk/strings.xml
index eaae7c0..c3b3422 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -31,6 +31,8 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Rozdeliť obrazovku"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informácie o aplikácii pre %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Nastavenia používania pre %1$s"</string>
+ <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
+ <skip />
<string name="save_app_pair" msgid="5647523853662686243">"Uložiť pár aplikácií"</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">"Tento pár aplikácií nie je v tomto zariadení podporovaný"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index dccf2f1..37a02e4 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -31,6 +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>
+ <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-sq/strings.xml b/res/values-sq/strings.xml
index 84484e8..d3bfce6 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -31,6 +31,8 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Ekrani i ndarë"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacioni i aplikacionit për %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Cilësimet e përdorimit për \"%1$s\""</string>
+ <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
+ <skip />
<string name="save_app_pair" msgid="5647523853662686243">"Ruaj çiftin e aplikacioneve"</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">"Ky çift aplikacionesh nuk mbështetet në këtë pajisje"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 64383f2..afaeb61 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -31,6 +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>
+ <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 47aed86..547f60e 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -31,6 +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>
+ <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-sw/strings.xml b/res/values-sw/strings.xml
index 5eadd1d..7d2768b 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -31,6 +31,8 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Gawa skrini"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Maelezo ya programu ya %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Mipangilio ya matumizi ya %1$s"</string>
+ <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
+ <skip />
<string name="save_app_pair" msgid="5647523853662686243">"Hifadhi jozi ya programu"</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">"Jozi hii ya programu haitumiki kwenye kifaa hiki"</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index d84485a..2ae4c30 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -31,6 +31,8 @@
<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="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-te/strings.xml b/res/values-te/strings.xml
index 8487e96..75dc7f0 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -31,6 +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>
+ <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-th/strings.xml b/res/values-th/strings.xml
index 71f4d15..a6d3b7e 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -31,6 +31,8 @@
<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="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-tl/strings.xml b/res/values-tl/strings.xml
index 7cf6a44..417962d 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -31,6 +31,8 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Split screen"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Impormasyon ng app para sa %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Mga setting ng paggamit para sa %1$s"</string>
+ <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
+ <skip />
<string name="save_app_pair" msgid="5647523853662686243">"I-save ang 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">"Hindi sinusuportahan sa device na ito ang pares ng app na ito"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index d55181c..0caa1f9 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -31,6 +31,8 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Bölünmüş ekran"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s uygulama bilgileri"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s ile ilgili kullanım ayarları"</string>
+ <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
+ <skip />
<string name="save_app_pair" msgid="5647523853662686243">"Uygulama çiftini kaydedin"</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">"Bu uygulama çifti bu cihazda desteklenmiyor"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index b5c1c70..e980d22 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -31,6 +31,8 @@
<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="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-ur/strings.xml b/res/values-ur/strings.xml
index 6fa76dd..4d59e17 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -31,6 +31,8 @@
<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="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-uz/strings.xml b/res/values-uz/strings.xml
index 21a8145..d2225c7 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -31,6 +31,8 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Ekranni ikkiga ajratish"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s ilovasi axboroti"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s uchun sarf sozlamalari"</string>
+ <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
+ <skip />
<string name="save_app_pair" msgid="5647523853662686243">"Ilova juftini saqlash"</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">"Bu ilova jufti ushbu qurilmada ishlamaydi"</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index b0bac73..4c14574 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -31,6 +31,8 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Chia đôi màn hình"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Thông tin ứng dụng cho %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Chế độ cài đặt mức sử dụng %1$s"</string>
+ <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
+ <skip />
<string name="save_app_pair" msgid="5647523853662686243">"Lưu cặp ứng dụng"</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">"Cặp ứng dụng này không hoạt động được trên thiết bị này"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 112b945..92591fb 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -31,6 +31,8 @@
<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="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-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index e63093e..05f3320 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -31,6 +31,8 @@
<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="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-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 25f9703..6ee3e99 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -31,6 +31,8 @@
<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="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-zu/strings.xml b/res/values-zu/strings.xml
index ec1f941..8d2e2f3 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -31,6 +31,8 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Hlukanisa isikrini"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Ulwazi lwe-App ye-%1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Amasethingi okusetshenziswa ka-%1$s"</string>
+ <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
+ <skip />
<string name="save_app_pair" msgid="5647523853662686243">"Londoloza i-app ebhangqiwe"</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">"Lokhu kubhanqwa kwe-app akusekelwa kule divayisi"</string>
diff --git a/res/values/config.xml b/res/values/config.xml
index 507ce9a..701e64a 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -67,7 +67,6 @@
<string name="main_process_initializer_class" translatable="false"></string>
<string name="app_launch_tracker_class" translatable="false"></string>
<string name="test_information_handler_class" translatable="false"></string>
- <string name="launcher_activity_logic_class" translatable="false"></string>
<string name="model_delegate_class" translatable="false"></string>
<string name="window_manager_proxy_class" translatable="false"></string>
<string name="secondary_display_predictions_class" translatable="false"></string>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index fd724a5..9d06021 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -45,6 +45,9 @@
<string name="split_app_info_accessibility">App info for %1$s</string>
<string name="split_app_usage_settings">Usage settings for %1$s</string>
+ <!-- Title for an option to open a new window for a given app -->
+ <string name="new_window_option_taskbar">New Window</string>
+
<!-- App pairs -->
<string name="save_app_pair">Save app pair</string>
<!-- App pair default title -->
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/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index fec94fe..2e75261 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -188,7 +188,7 @@
public SystemUiController getSystemUiController() {
if (mSystemUiController == null) {
- mSystemUiController = new SystemUiController(getWindow());
+ mSystemUiController = new SystemUiController(getWindow().getDecorView());
}
return mSystemUiController;
}
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index cc5baea..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 =
@@ -1056,6 +1056,7 @@
return mHotseatColumnSpan;
}
+ @VisibleForTesting
public int getHotseatWidthPx() {
return mHotseatWidthPx;
}
@@ -2213,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));
@@ -2327,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/FastScrollRecyclerView.java b/src/com/android/launcher3/FastScrollRecyclerView.java
index 6622e11..17084bb 100644
--- a/src/com/android/launcher3/FastScrollRecyclerView.java
+++ b/src/com/android/launcher3/FastScrollRecyclerView.java
@@ -60,6 +60,7 @@
mScrollbar = scrollbar;
mScrollbar.setRecyclerView(this);
mScrollbar.setFastScrollerLocation(location);
+ scrollToTop();
onUpdateScrollbar(0);
}
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/Launcher.java b/src/com/android/launcher3/Launcher.java
index 0bc192d..b0ec9b0 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -448,12 +448,10 @@
.logStart(LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION)
.logStart(LAUNCHER_LATENCY_STARTUP_ACTIVITY_ON_CREATE);
// Only use a hard-coded cookie since we only want to trace this once.
- if (Utilities.ATLEAST_S) {
- Trace.beginAsyncSection(
- DISPLAY_WORKSPACE_TRACE_METHOD_NAME, DISPLAY_WORKSPACE_TRACE_COOKIE);
- Trace.beginAsyncSection(DISPLAY_ALL_APPS_TRACE_METHOD_NAME,
- DISPLAY_ALL_APPS_TRACE_COOKIE);
- }
+ Trace.beginAsyncSection(
+ DISPLAY_WORKSPACE_TRACE_METHOD_NAME, DISPLAY_WORKSPACE_TRACE_COOKIE);
+ Trace.beginAsyncSection(DISPLAY_ALL_APPS_TRACE_METHOD_NAME,
+ DISPLAY_ALL_APPS_TRACE_COOKIE);
TraceHelper.INSTANCE.beginSection(ON_CREATE_EVT);
if (DEBUG_STRICT_MODE) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
@@ -732,13 +730,6 @@
public void onEnterAnimationComplete() {
super.onEnterAnimationComplete();
mRotationHelper.setCurrentTransitionRequest(REQUEST_NONE);
- // Starting with Android S, onEnterAnimationComplete is sent immediately
- // causing the surface to get removed before the animation completed (b/175345344).
- // Instead we rely on next user touch event to remove the view and optionally a callback
- // from system from Android T onwards.
- if (!Utilities.ATLEAST_S) {
- AbstractFloatingView.closeOpenViews(this, false, TYPE_ICON_SURFACE);
- }
}
@Override
@@ -2579,10 +2570,8 @@
public void bindAllApplications(AppInfo[] apps, int flags,
Map<PackageUserKey, Integer> packageUserKeytoUidMap) {
mModelCallbacks.bindAllApplications(apps, flags, packageUserKeytoUidMap);
- if (Utilities.ATLEAST_S) {
- Trace.endAsyncSection(DISPLAY_ALL_APPS_TRACE_METHOD_NAME,
- DISPLAY_ALL_APPS_TRACE_COOKIE);
- }
+ Trace.endAsyncSection(DISPLAY_ALL_APPS_TRACE_METHOD_NAME,
+ DISPLAY_ALL_APPS_TRACE_COOKIE);
}
/**
diff --git a/src/com/android/launcher3/LauncherApplication.java b/src/com/android/launcher3/LauncherApplication.java
index 490186a..4c82e56 100644
--- a/src/com/android/launcher3/LauncherApplication.java
+++ b/src/com/android/launcher3/LauncherApplication.java
@@ -18,6 +18,7 @@
import android.app.Application;
import com.android.launcher3.dagger.DaggerLauncherAppComponent;
+import com.android.launcher3.dagger.LauncherAppComponent;
import com.android.launcher3.dagger.LauncherBaseAppComponent;
/**
@@ -30,10 +31,18 @@
public void onCreate() {
super.onCreate();
MainProcessInitializer.initialize(this);
- mAppComponent = DaggerLauncherAppComponent.builder().appContext(this).build();
+ initDagger();
}
- public LauncherBaseAppComponent getAppComponent() {
- return mAppComponent;
+ public LauncherAppComponent getAppComponent() {
+ // Since supertype setters will return a supertype.builder and @Component.Builder types
+ // must not have any generic types.
+ // We need to cast mAppComponent to {@link LauncherAppComponent} since appContext()
+ // method is defined in the super class LauncherBaseComponent#Builder.
+ return (LauncherAppComponent) mAppComponent;
+ }
+
+ protected void initDagger() {
+ mAppComponent = DaggerLauncherAppComponent.builder().appContext(this).build();
}
}
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index ca1b2a9..7ad17d9 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -71,6 +71,7 @@
import com.android.launcher3.pm.PackageInstallInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.shortcuts.ShortcutRequest;
+import com.android.launcher3.util.ApplicationInfoWrapper;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.PackageManagerHelper;
@@ -446,8 +447,8 @@
IconCache iconCache = mApp.getIconCache();
final IntSet removedIds = new IntSet();
HashSet<WorkspaceItemInfo> archivedWorkspaceItemsToCacheRefresh = new HashSet<>();
- boolean isAppArchived = PackageManagerHelper.INSTANCE.get(mApp.getContext())
- .isAppArchivedForUser(packageName, user);
+ boolean isAppArchived =
+ new ApplicationInfoWrapper(mApp.getContext(), packageName, user).isArchived();
synchronized (dataModel) {
if (isAppArchived) {
// Remove package icon cache entry for archived app in case of a session
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index 7176733..d645734 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -11,6 +11,7 @@
import com.android.launcher3.graphics.SysUiScrim;
import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.statemanager.StatefulContainer;
import com.android.launcher3.util.window.WindowManagerProxy;
import java.util.Collections;
@@ -20,7 +21,7 @@
private final Rect mTempRect = new Rect();
- private final StatefulActivity mActivity;
+ private final StatefulContainer mStatefulContainer;
@ViewDebug.ExportedProperty(category = "launcher")
private static final List<Rect> SYSTEM_GESTURE_EXCLUSION_RECT =
@@ -36,24 +37,25 @@
public LauncherRootView(Context context, AttributeSet attrs) {
super(context, attrs);
- mActivity = StatefulActivity.fromContext(context);
+ mStatefulContainer = StatefulContainer.fromContext(context);
mSysUiScrim = new SysUiScrim(this);
}
private void handleSystemWindowInsets(Rect insets) {
// Update device profile before notifying the children.
- mActivity.getDeviceProfile().updateInsets(insets);
+ mStatefulContainer.getDeviceProfile().updateInsets(insets);
boolean resetState = !insets.equals(mInsets);
setInsets(insets);
if (resetState) {
- mActivity.getStateManager().reapplyState(true /* cancelCurrentAnimation */);
+ mStatefulContainer.getStateManager().reapplyState(true /* cancelCurrentAnimation */);
}
}
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- mActivity.handleConfigurationChanged(mActivity.getResources().getConfiguration());
+ mStatefulContainer.handleConfigurationChanged(
+ mStatefulContainer.getContext().getResources().getConfiguration());
insets = WindowManagerProxy.INSTANCE.get(getContext())
.normalizeWindowInsets(getContext(), insets, mTempRect);
@@ -72,7 +74,7 @@
}
public void dispatchInsets() {
- mActivity.getDeviceProfile().updateInsets(mInsets);
+ mStatefulContainer.getDeviceProfile().updateInsets(mInsets);
super.setInsets(mInsets);
}
diff --git a/src/com/android/launcher3/ModelCallbacks.kt b/src/com/android/launcher3/ModelCallbacks.kt
index d57f8a0..496d517 100644
--- a/src/com/android/launcher3/ModelCallbacks.kt
+++ b/src/com/android/launcher3/ModelCallbacks.kt
@@ -61,7 +61,7 @@
AbstractFloatingView.closeOpenViews(
launcher,
true,
- AbstractFloatingView.TYPE_ALL and AbstractFloatingView.TYPE_REBIND_SAFE.inv()
+ AbstractFloatingView.TYPE_ALL and AbstractFloatingView.TYPE_REBIND_SAFE.inv(),
)
workspaceLoading = true
@@ -76,7 +76,7 @@
TAG,
"startBinding: " +
"hotseat layout was vertical: ${launcher.hotseat?.isHasVerticalHotseat}" +
- " and is setting to ${launcher.deviceProfile.isVerticalBarLayout}"
+ " and is setting to ${launcher.deviceProfile.isVerticalBarLayout}",
)
launcher.hotseat?.resetLayout(launcher.deviceProfile.isVerticalBarLayout)
TraceHelper.INSTANCE.endSection()
@@ -88,14 +88,12 @@
pendingTasks: RunnableList,
onCompleteSignal: RunnableList,
workspaceItemCount: Int,
- isBindSync: Boolean
+ isBindSync: Boolean,
) {
- if (Utilities.ATLEAST_S) {
- Trace.endAsyncSection(
- TraceEvents.DISPLAY_WORKSPACE_TRACE_METHOD_NAME,
- TraceEvents.DISPLAY_WORKSPACE_TRACE_COOKIE
- )
- }
+ Trace.endAsyncSection(
+ TraceEvents.DISPLAY_WORKSPACE_TRACE_METHOD_NAME,
+ TraceEvents.DISPLAY_WORKSPACE_TRACE_COOKIE,
+ )
synchronouslyBoundPages = boundPages
pagesToBindSynchronously = LIntSet()
clearPendingBinds()
@@ -149,14 +147,14 @@
// Cache one page worth of icons
launcher.viewCache.setCacheSize(
R.layout.folder_application,
- deviceProfile.numFolderColumns * deviceProfile.numFolderRows
+ deviceProfile.numFolderColumns * deviceProfile.numFolderRows,
)
launcher.viewCache.setCacheSize(R.layout.folder_page, 2)
TraceHelper.INSTANCE.endSection()
launcher.workspace.removeExtraEmptyScreen(/* stripEmptyScreens= */ true)
launcher.workspace.pageIndicator.setPauseScroll(
/*pause=*/ false,
- deviceProfile.isTwoPanels
+ deviceProfile.isTwoPanels,
)
TestEventEmitter.INSTANCE.get(launcher).sendEvent(TestEvent.WORKSPACE_FINISH_LOADING)
}
@@ -182,7 +180,7 @@
val snackbar =
AbstractFloatingView.getOpenView<AbstractFloatingView>(
launcher,
- AbstractFloatingView.TYPE_SNACKBAR
+ AbstractFloatingView.TYPE_SNACKBAR,
)
snackbar?.post { snackbar.close(true) }
}
@@ -191,7 +189,7 @@
override fun bindAllApplications(
apps: Array<AppInfo?>?,
flags: Int,
- packageUserKeytoUidMap: Map<PackageUserKey?, Int?>?
+ packageUserKeytoUidMap: Map<PackageUserKey?, Int?>?,
) {
Preconditions.assertUIThread()
val hadWorkApps = launcher.appsView.shouldShowTabs()
@@ -312,7 +310,7 @@
val info =
PendingAddWidgetInfo(
widgetsListBaseEntry.mWidgets[0].widgetInfo,
- LauncherSettings.Favorites.CONTAINER_DESKTOP
+ LauncherSettings.Favorites.CONTAINER_DESKTOP,
)
launcher.addPendingItem(
info,
@@ -320,14 +318,14 @@
WorkspaceLayoutManager.FIRST_SCREEN_ID,
intArrayOf(0, 0),
info.spanX,
- info.spanY
+ info.spanY,
)
}
override fun bindScreens(orderedScreenIds: LIntArray) {
launcher.workspace.pageIndicator.setPauseScroll(
/*pause=*/ true,
- launcher.deviceProfile.isTwoPanels
+ launcher.deviceProfile.isTwoPanels,
)
val firstScreenPosition = 0
if (
@@ -354,7 +352,7 @@
override fun bindAppsAdded(
newScreens: LIntArray?,
addNotAnimated: java.util.ArrayList<ItemInfo?>?,
- addAnimated: java.util.ArrayList<ItemInfo?>?
+ addAnimated: java.util.ArrayList<ItemInfo?>?,
) {
// Add the new screens
if (newScreens != null) {
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 8d1e61f..b3cb948 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -22,7 +22,6 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
-import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.UserHandle;
@@ -44,7 +43,7 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.ApplicationInfoWrapper;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import java.net.URISyntaxException;
@@ -342,9 +341,8 @@
}
public void onLauncherResume() {
- // We use MATCH_UNINSTALLED_PACKAGES as the app can be on SD card as well.
- if (PackageManagerHelper.INSTANCE.get(mContext).getApplicationInfo(mPackageName,
- mDragObject.dragInfo.user, PackageManager.MATCH_UNINSTALLED_PACKAGES) == null) {
+ if (new ApplicationInfoWrapper(mContext, mPackageName, mDragObject.dragInfo.user)
+ .getInfo() == null) {
mDragObject.dragSource = mOriginal;
mOriginal.onDropCompleted(SecondaryDropTarget.this, mDragObject, true);
mStatsLogManager.logger().withInstanceId(mDragObject.logInstanceId)
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index fde7014..572274e 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;
@@ -122,9 +122,6 @@
public static final String[] EMPTY_STRING_ARRAY = new String[0];
public static final Person[] EMPTY_PERSON_ARRAY = new Person[0];
- @ChecksSdkIntAtLeast(api = VERSION_CODES.S)
- public static final boolean ATLEAST_S = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S;
-
@ChecksSdkIntAtLeast(api = VERSION_CODES.TIRAMISU, codename = "T")
public static final boolean ATLEAST_T = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU;
@@ -629,8 +626,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)
@@ -639,7 +635,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 1a59d82..9f6b40b 100644
--- a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
+++ b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
@@ -18,6 +18,9 @@
import android.content.Context;
+import com.android.launcher3.pm.InstallSessionHelper;
+import com.android.launcher3.util.DaggerSingletonTracker;
+
import dagger.BindsInstance;
/**
@@ -29,6 +32,9 @@
* See {@link LauncherAppComponent} for the one actually used by AOSP.
*/
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/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 85eb39b..a3cfe5c 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -68,7 +68,7 @@
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.pm.PinRequestHelper;
import com.android.launcher3.util.ApiWrapper;
-import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.ApplicationInfoWrapper;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.views.AbstractSlideInView;
import com.android.launcher3.views.BaseDragLayer;
@@ -164,8 +164,8 @@
finish();
return;
}
- ApplicationInfo info = PackageManagerHelper.INSTANCE.get(this)
- .getApplicationInfo(targetApp.packageName, targetApp.user, 0);
+ ApplicationInfo info = new ApplicationInfoWrapper(
+ this, targetApp.packageName, targetApp.user).getInfo();
if (info == null) {
finish();
return;
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 8b1f42b..a24f3ff 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -121,7 +121,7 @@
@Override
public void recreateControllers() {
- mControllers = mActivity.createTouchControllers();
+ mControllers = mContainer.createTouchControllers();
}
public ViewGroupFocusHelper getFocusIndicatorHelper() {
@@ -134,15 +134,15 @@
}
private boolean isEventOverAccessibleDropTargetBar(MotionEvent ev) {
- return isInAccessibleDrag() && isEventOverView(mActivity.getDropTargetBar(), ev);
+ return isInAccessibleDrag() && isEventOverView(mContainer.getDropTargetBar(), ev);
}
@Override
public boolean onInterceptHoverEvent(MotionEvent ev) {
- if (mActivity == null || mActivity.getWorkspace() == null) {
+ if (mContainer == null || mContainer.getWorkspace() == null) {
return false;
}
- AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
+ AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mContainer);
if (!(topView instanceof Folder)) {
return false;
} else {
@@ -197,7 +197,7 @@
private boolean isInAccessibleDrag() {
- return mActivity.getAccessibilityDelegate().isInAccessibleDrag();
+ return mContainer.getAccessibilityDelegate().isInAccessibleDrag();
}
@Override
@@ -210,12 +210,12 @@
@Override
public void addChildrenForAccessibility(ArrayList<View> childrenForAccessibility) {
- View topView = AbstractFloatingView.getTopOpenViewWithType(mActivity,
+ View topView = AbstractFloatingView.getTopOpenViewWithType(mContainer,
AbstractFloatingView.TYPE_ACCESSIBLE);
if (topView != null) {
addAccessibleChildToList(topView, childrenForAccessibility);
if (isInAccessibleDrag()) {
- addAccessibleChildToList(mActivity.getDropTargetBar(), childrenForAccessibility);
+ addAccessibleChildToList(mContainer.getDropTargetBar(), childrenForAccessibility);
}
} else {
super.addChildrenForAccessibility(childrenForAccessibility);
@@ -420,14 +420,14 @@
public void onViewAdded(View child) {
super.onViewAdded(child);
updateChildIndices();
- mActivity.onDragLayerHierarchyChanged();
+ mContainer.onDragLayerHierarchyChanged();
}
@Override
public void onViewRemoved(View child) {
super.onViewRemoved(child);
updateChildIndices();
- mActivity.onDragLayerHierarchyChanged();
+ mContainer.onDragLayerHierarchyChanged();
}
@Override
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/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 4af9e2f..40c0cc6 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -69,7 +69,6 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.WorkspaceLayoutManager;
import com.android.launcher3.apppairs.AppPairIcon;
@@ -207,15 +206,12 @@
mWorkspaceScreens.put(Workspace.SECOND_SCREEN_ID, rightPanel);
}
- if (Utilities.ATLEAST_S) {
- WallpaperColors wallpaperColors = wallpaperColorsOverride != null
- ? wallpaperColorsOverride
- : WallpaperManager.getInstance(context).getWallpaperColors(FLAG_SYSTEM);
- mWallpaperColorResources = wallpaperColors != null ? LocalColorExtractor.newInstance(
- context).generateColorsOverride(wallpaperColors) : null;
- } else {
- mWallpaperColorResources = null;
- }
+ WallpaperColors wallpaperColors = wallpaperColorsOverride != null
+ ? wallpaperColorsOverride
+ : WallpaperManager.getInstance(context).getWallpaperColors(FLAG_SYSTEM);
+ mWallpaperColorResources = wallpaperColors != null
+ ? LocalColorExtractor.newInstance(context).generateColorsOverride(wallpaperColors)
+ : null;
mAppWidgetHost = new LauncherPreviewAppWidgetHost(context);
}
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/graphics/SysUiScrim.java b/src/com/android/launcher3/graphics/SysUiScrim.java
index 077ddfc..d59fc19 100644
--- a/src/com/android/launcher3/graphics/SysUiScrim.java
+++ b/src/com/android/launcher3/graphics/SysUiScrim.java
@@ -32,10 +32,10 @@
import androidx.annotation.ColorInt;
import androidx.annotation.VisibleForTesting;
-import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatedFloat;
+import com.android.launcher3.statemanager.StatefulContainer;
import com.android.launcher3.testing.shared.ResourceUtils;
import com.android.launcher3.util.ScreenOnTracker;
import com.android.launcher3.util.ScreenOnTracker.ScreenOnListener;
@@ -84,7 +84,7 @@
private final int mBottomMaskHeight;
private final View mRoot;
- private final BaseDraggingActivity mActivity;
+ private final StatefulContainer mContainer;
private final boolean mHideSysUiScrim;
private boolean mSkipScrimAnimationForTest = false;
@@ -94,8 +94,8 @@
public SysUiScrim(View view) {
mRoot = view;
- mActivity = BaseDraggingActivity.fromContext(view.getContext());
- DisplayMetrics dm = mActivity.getResources().getDisplayMetrics();
+ mContainer = StatefulContainer.fromContext(view.getContext());
+ DisplayMetrics dm = mContainer.getContext().getResources().getDisplayMetrics();
mTopMaskHeight = ResourceUtils.pxFromDp(TOP_MASK_HEIGHT_DP, dm);
mBottomMaskHeight = ResourceUtils.pxFromDp(BOTTOM_MASK_HEIGHT_DP, dm);
@@ -130,7 +130,7 @@
ObjectAnimator oa = mSysUiAnimMultiplier.animateToValue(1);
oa.setDuration(600);
- oa.setStartDelay(mActivity.getWindow().getTransitionBackgroundFadeDuration());
+ oa.setStartDelay(mContainer.getWindow().getTransitionBackgroundFadeDuration());
oa.start();
mAnimateScrimOnNextDraw = false;
}
@@ -166,19 +166,19 @@
* horizontal
*/
public void onInsetsChanged(Rect insets) {
- DeviceProfile dp = mActivity.getDeviceProfile();
+ DeviceProfile dp = mContainer.getDeviceProfile();
mDrawTopScrim = insets.top > 0;
mDrawBottomScrim = !dp.isVerticalBarLayout() && !dp.isGestureMode && !dp.isTaskbarPresent;
}
@Override
public void onViewAttachedToWindow(View view) {
- ScreenOnTracker.INSTANCE.get(mActivity).addListener(mScreenOnListener);
+ ScreenOnTracker.INSTANCE.get(mContainer.getContext()).addListener(mScreenOnListener);
}
@Override
public void onViewDetachedFromWindow(View view) {
- ScreenOnTracker.INSTANCE.get(mActivity).removeListener(mScreenOnListener);
+ ScreenOnTracker.INSTANCE.get(mContainer.getContext()).removeListener(mScreenOnListener);
}
/**
@@ -213,7 +213,7 @@
}
private Bitmap createDitheredAlphaMask(int height, @ColorInt int[] colors, float[] positions) {
- DisplayMetrics dm = mActivity.getResources().getDisplayMetrics();
+ DisplayMetrics dm = mContainer.getContext().getResources().getDisplayMetrics();
int width = ResourceUtils.pxFromDp(ALPHA_MASK_BITMAP_WIDTH_DP, dm);
Bitmap dst = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8);
Canvas c = new Canvas(dst);
diff --git a/src/com/android/launcher3/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/ComponentWithLabel.java b/src/com/android/launcher3/icons/ComponentWithLabel.java
deleted file mode 100644
index 30575fc..0000000
--- a/src/com/android/launcher3/icons/ComponentWithLabel.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.icons;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.os.UserHandle;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.icons.cache.CachingLogic;
-
-public interface ComponentWithLabel {
-
- ComponentName getComponent();
-
- UserHandle getUser();
-
- CharSequence getLabel(PackageManager pm);
-
-
- class ComponentCachingLogic<T extends ComponentWithLabel> implements CachingLogic<T> {
-
- private final PackageManager mPackageManager;
- private final boolean mAddToMemCache;
-
- public ComponentCachingLogic(Context context, boolean addToMemCache) {
- mPackageManager = context.getPackageManager();
- mAddToMemCache = addToMemCache;
- }
-
- @Override
- @NonNull
- public ComponentName getComponent(@NonNull T object) {
- return object.getComponent();
- }
-
- @NonNull
- @Override
- public UserHandle getUser(@NonNull T object) {
- return object.getUser();
- }
-
- @NonNull
- @Override
- public CharSequence getLabel(@NonNull T object) {
- return object.getLabel(mPackageManager);
- }
-
- @NonNull
- @Override
- public BitmapInfo loadIcon(@NonNull Context context, @NonNull T object) {
- return BitmapInfo.LOW_RES_INFO;
- }
-
- @Override
- public boolean addToMemCache() {
- return mAddToMemCache;
- }
- }
-}
diff --git a/src/com/android/launcher3/icons/ComponentWithLabelAndIcon.java b/src/com/android/launcher3/icons/ComponentWithLabelAndIcon.java
deleted file mode 100644
index 0a52dd7..0000000
--- a/src/com/android/launcher3/icons/ComponentWithLabelAndIcon.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.icons;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.icons.BaseIconFactory.IconOptions;
-
-/**
- * Extension of ComponentWithLabel to also support loading icons
- */
-public interface ComponentWithLabelAndIcon extends ComponentWithLabel {
-
- /**
- * Provide an icon for this object
- */
- Drawable getFullResIcon(IconCache cache);
-
- class ComponentWithIconCachingLogic extends ComponentCachingLogic<ComponentWithLabelAndIcon> {
-
- public ComponentWithIconCachingLogic(Context context, boolean addToMemCache) {
- super(context, addToMemCache);
- }
-
- @NonNull
- @Override
- public BitmapInfo loadIcon(@NonNull Context context,
- @NonNull ComponentWithLabelAndIcon object) {
- Drawable d = object.getFullResIcon(LauncherAppState.getInstance(context)
- .getIconCache());
- if (d == null) {
- return super.loadIcon(context, object);
- }
- try (LauncherIcons li = LauncherIcons.obtain(context)) {
- return li.createBadgedIconBitmap(d, new IconOptions().setUser(object.getUser()));
- }
- }
- }
-}
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 44e448e..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;
@@ -54,9 +53,10 @@
import com.android.launcher3.Flags;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Utilities;
-import com.android.launcher3.icons.ComponentWithLabel.ComponentCachingLogic;
import com.android.launcher3.icons.cache.BaseIconCache;
+import com.android.launcher3.icons.cache.CachedObjectCachingLogic;
import com.android.launcher3.icons.cache.CachingLogic;
+import com.android.launcher3.icons.cache.LauncherActivityCachingLogic;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.IconRequestInfo;
@@ -65,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;
@@ -97,12 +96,10 @@
private final CachingLogic<ComponentWithLabel> mComponentWithLabelCachingLogic;
private final CachingLogic<LauncherActivityInfo> mLauncherActivityInfoCachingLogic;
- private final CachingLogic<ShortcutInfo> mShortcutCachingLogic;
private final LauncherApps mLauncherApps;
private final UserCache mUserManager;
private final InstantAppResolver mInstantAppResolver;
- private final IconProvider mIconProvider;
private final CancellableTask mCancelledTask;
private final SparseArray<BitmapInfo> mWidgetCategoryBitmapInfos;
@@ -112,14 +109,12 @@
public IconCache(Context context, InvariantDeviceProfile idp, String dbFileName,
IconProvider iconProvider) {
super(context, dbFileName, MODEL_EXECUTOR.getLooper(),
- idp.fillResIconDpi, idp.iconBitmapSize, true /* inMemoryCache */);
- mComponentWithLabelCachingLogic = new ComponentCachingLogic(context, false);
- mLauncherActivityInfoCachingLogic = LauncherActivityCachingLogic.newInstance(context);
- mShortcutCachingLogic = new ShortcutCachingLogic();
+ idp.fillResIconDpi, idp.iconBitmapSize, true /* inMemoryCache */, iconProvider);
+ mComponentWithLabelCachingLogic = new CachedObjectCachingLogic(context);
+ mLauncherActivityInfoCachingLogic = LauncherActivityCachingLogic.INSTANCE;
mLauncherApps = mContext.getSystemService(LauncherApps.class);
mUserManager = UserCache.INSTANCE.get(mContext);
mInstantAppResolver = InstantAppResolver.newInstance(mContext);
- mIconProvider = iconProvider;
mWidgetCategoryBitmapInfos = new SparseArray<>();
mCancelledTask = new CancellableTask(() -> null, MAIN_EXECUTOR, c -> { });
@@ -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,14 +327,17 @@
} else {
Intent intent = info.getIntent();
getTitleAndIcon(info, () -> mLauncherApps.resolveActivity(intent, info.user),
- true, useLowResIcon, info.isArchived());
+ true, useLowResIcon);
}
}
+ /**
+ * Loads and returns the icon for the provided object without adding it to memCache
+ */
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);
}
@@ -351,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.
@@ -482,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);
@@ -600,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) {
@@ -629,12 +602,6 @@
info.getAppLabel());
}
- @Override
- @NonNull
- protected String getIconSystemState(String packageName) {
- return mIconProvider.getSystemStateForPackage(mSystemState, packageName);
- }
-
/**
* Interface for receiving itemInfo with high-res icon.
*/
diff --git a/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java b/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java
deleted file mode 100644
index de2269c..0000000
--- a/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.icons;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.LauncherActivityInfo;
-import android.os.Build;
-import android.os.UserHandle;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.Flags;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.R;
-import com.android.launcher3.icons.BaseIconFactory.IconOptions;
-import com.android.launcher3.icons.cache.CachingLogic;
-import com.android.launcher3.util.ResourceBasedOverride;
-
-/**
- * Caching logic for LauncherActivityInfo.
- */
-public class LauncherActivityCachingLogic
- implements CachingLogic<LauncherActivityInfo>, ResourceBasedOverride {
-
- /**
- * Creates and returns a new instance
- */
- public static LauncherActivityCachingLogic newInstance(Context context) {
- return Overrides.getObject(LauncherActivityCachingLogic.class, context,
- R.string.launcher_activity_logic_class);
- }
-
- @NonNull
- @Override
- public ComponentName getComponent(@NonNull LauncherActivityInfo object) {
- return object.getComponentName();
- }
-
- @NonNull
- @Override
- public UserHandle getUser(@NonNull LauncherActivityInfo object) {
- return object.getUser();
- }
-
- @NonNull
- @Override
- public CharSequence getLabel(@NonNull LauncherActivityInfo object) {
- return object.getLabel();
- }
-
- @NonNull
- @Override
- public BitmapInfo loadIcon(@NonNull Context context, @NonNull LauncherActivityInfo object) {
- try (LauncherIcons li = LauncherIcons.obtain(context)) {
- IconOptions iconOptions = new IconOptions().setUser(object.getUser());
- iconOptions.mIsArchived = Flags.useNewIconForArchivedApps()
- && Build.VERSION.SDK_INT >= 35
- && object.getActivityInfo().isArchived;
- return li.createBadgedIconBitmap(
- LauncherAppState.getInstance(context)
- .getIconProvider()
- .getIcon(object, li.mFillResIconDpi),
- iconOptions
- );
- }
- }
-}
diff --git a/src/com/android/launcher3/icons/Legacy.kt b/src/com/android/launcher3/icons/Legacy.kt
new file mode 100644
index 0000000..3bf3bb2
--- /dev/null
+++ b/src/com/android/launcher3/icons/Legacy.kt
@@ -0,0 +1,31 @@
+/*
+ * 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 com.android.launcher3.icons.cache.CachedObject
+
+/**
+ * This files contains some definitions used during refactoring to avoid breaking changes.
+ *
+ * TODO(b/366237794) remove this file once refactoring is complete
+ */
+
+/** Temporary interface to allow easier refactoring */
+interface ComponentWithLabel : CachedObject<IconCache>
+
+/** Temporary interface to allow easier refactoring */
+interface ComponentWithLabelAndIcon : ComponentWithLabel
diff --git a/src/com/android/launcher3/icons/ShortcutCachingLogic.java b/src/com/android/launcher3/icons/ShortcutCachingLogic.java
deleted file mode 100644
index f40eda6..0000000
--- a/src/com/android/launcher3/icons/ShortcutCachingLogic.java
+++ /dev/null
@@ -1,115 +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.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 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/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index 427fb97..55bcb70 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -41,6 +41,7 @@
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.pm.PackageInstallInfo;
+import com.android.launcher3.util.ApplicationInfoWrapper;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.PackageManagerHelper;
@@ -103,8 +104,8 @@
}
// b/139663018 Short-circuit this logic if the icon is a system app
- if (PackageManagerHelper.isSystemApp(context,
- Objects.requireNonNull(item.getIntent()))) {
+ if (new ApplicationInfoWrapper(context,
+ Objects.requireNonNull(item.getIntent())).isSystem()) {
continue;
}
diff --git a/src/com/android/launcher3/model/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java
index 1f60f13..7bc9273 100644
--- a/src/com/android/launcher3/model/AllAppsList.java
+++ b/src/com/android/launcher3/model/AllAppsList.java
@@ -40,6 +40,7 @@
import com.android.launcher3.pm.PackageInstallInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.ApiWrapper;
+import com.android.launcher3.util.ApplicationInfoWrapper;
import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.SafeCloseable;
@@ -169,8 +170,8 @@
public AppInfo addPromiseApp(
Context context, PackageInstallInfo installInfo, boolean loadIcon) {
// only if not yet installed
- if (PackageManagerHelper.INSTANCE.get(context)
- .isAppInstalled(installInfo.packageName, installInfo.user)) {
+ if (new ApplicationInfoWrapper(context, installInfo.packageName, installInfo.user)
+ .isInstalled()) {
return null;
}
AppInfo promiseAppInfo = new AppInfo(installInfo);
diff --git a/src/com/android/launcher3/model/GridSizeMigrationUtil.java b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
index 8d2a7f9..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;
@@ -28,8 +29,6 @@
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
@@ -47,7 +46,6 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.GridOccupancy;
@@ -100,7 +98,7 @@
@VisibleForTesting
public static List<DbEntry> readAllEntries(SQLiteDatabase db, String tableName,
Context context) {
- DbReader dbReader = new DbReader(db, tableName, context, getValidPackages(context));
+ DbReader dbReader = new DbReader(db, tableName, context);
List<DbEntry> result = dbReader.loadAllWorkspaceEntries();
result.addAll(dbReader.loadHotseatEntries());
return result;
@@ -132,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.");
@@ -144,11 +143,10 @@
}
copyTable(source, TABLE_NAME, target.getWritableDatabase(), TMP_TABLE, context);
- HashSet<String> validPackages = getValidPackages(context);
long migrationStartTime = System.currentTimeMillis();
try (SQLiteTransaction t = new SQLiteTransaction(target.getWritableDatabase())) {
- DbReader srcReader = new DbReader(t.getDb(), TMP_TABLE, context, validPackages);
- DbReader destReader = new DbReader(t.getDb(), TABLE_NAME, context, validPackages);
+ DbReader srcReader = new DbReader(t.getDb(), TMP_TABLE, context);
+ DbReader destReader = new DbReader(t.getDb(), TABLE_NAME, context);
Point targetSize = new Point(destDeviceState.getColumns(), destDeviceState.getRows());
migrate(target, srcReader, destReader, destDeviceState.getNumHotseat(),
@@ -348,23 +346,6 @@
Utilities.createDbSelectionQuery(LauncherSettings.Favorites._ID, entryIds), null);
}
- private static HashSet<String> getValidPackages(Context context) {
- // Initialize list of valid packages. This contain all the packages which are already on
- // the device and packages which are being installed. Any item which doesn't belong to
- // this set is removed.
- // Since the loader removes such items anyway, removing these items here doesn't cause
- // any extra data loss and gives us more free space on the grid for better migration.
- HashSet<String> validPackages = new HashSet<>();
- for (PackageInfo info : context.getPackageManager()
- .getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES)) {
- validPackages.add(info.packageName);
- }
- InstallSessionHelper.INSTANCE.get(context)
- .getActiveSessions().keySet()
- .forEach(packageUserKey -> validPackages.add(packageUserKey.mPackageName));
- return validPackages;
- }
-
private static void solveGridPlacement(@NonNull final DatabaseHelper helper,
@NonNull final DbReader srcReader, @NonNull final DbReader destReader,
final int screenId, final int trgX, final int trgY,
@@ -461,18 +442,15 @@
private final SQLiteDatabase mDb;
private final String mTableName;
private final Context mContext;
- private final Set<String> mValidPackages;
private int mLastScreenId = -1;
private final Map<Integer, ArrayList<DbEntry>> mWorkspaceEntriesByScreenId =
new ArrayMap<>();
- public DbReader(SQLiteDatabase db, String tableName, Context context,
- Set<String> validPackages) {
+ public DbReader(SQLiteDatabase db, String tableName, Context context) {
mDb = db;
mTableName = tableName;
mContext = context;
- mValidPackages = validPackages;
}
protected List<DbEntry> loadHotseatEntries() {
@@ -504,7 +482,6 @@
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: {
entry.mIntent = c.getString(indexIntent);
- verifyIntent(c.getString(indexIntent));
break;
}
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: {
@@ -586,14 +563,12 @@
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: {
entry.mIntent = c.getString(indexIntent);
- verifyIntent(entry.mIntent);
break;
}
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: {
entry.mProvider = c.getString(indexAppWidgetProvider);
entry.appWidgetId = c.getInt(indexAppWidgetId);
ComponentName cn = ComponentName.unflattenFromString(entry.mProvider);
- verifyPackage(cn.getPackageName());
LauncherAppWidgetProviderInfo pInfo = widgetManagerHelper
.getLauncherAppWidgetInfo(entry.appWidgetId, cn);
@@ -656,7 +631,6 @@
try {
int id = c.getInt(0);
String intent = c.getString(1);
- verifyIntent(intent);
total++;
if (!entry.mFolderItems.containsKey(intent)) {
entry.mFolderItems.put(intent, new HashSet<>());
@@ -673,27 +647,6 @@
private Cursor queryWorkspace(String[] columns, String where) {
return mDb.query(mTableName, columns, where, null, null, null, null);
}
-
- /** Verifies if the mIntent should be restored. */
- private void verifyIntent(String intentStr)
- throws Exception {
- Intent intent = Intent.parseUri(intentStr, 0);
- if (intent.getComponent() != null) {
- verifyPackage(intent.getComponent().getPackageName());
- } else if (intent.getPackage() != null) {
- // Only verify package if the component was null.
- verifyPackage(intent.getPackage());
- }
- }
-
- /** Verifies if the package should be restored */
- private void verifyPackage(String packageName)
- throws Exception {
- if (!mValidPackages.contains(packageName)) {
- // TODO(b/151468819): Handle promise app icon restoration during grid migration.
- throw new Exception("Package not available");
- }
- }
}
public static class DbEntry extends ItemInfo implements Comparable<DbEntry> {
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 605accf..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,12 +70,13 @@
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.ComponentWithLabelAndIcon.ComponentWithIconCachingLogic;
import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.icons.LauncherActivityCachingLogic;
-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;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.AppPairInfo;
@@ -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.
@@ -298,13 +300,13 @@
IconCacheUpdateHandler updateHandler = mIconCache.getUpdateHandler();
setIgnorePackages(updateHandler);
updateHandler.updateIcons(allActivityList,
- LauncherActivityCachingLogic.newInstance(mApp.getContext()),
+ LauncherActivityCachingLogic.INSTANCE,
mApp.getModel()::onPackageIconsUpdated);
logASplit("update icon cache");
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();
@@ -360,7 +364,7 @@
}
updateHandler.updateIcons(allWidgetsList,
- new ComponentWithIconCachingLogic(mApp.getContext(), true),
+ new CachedObjectCachingLogic(mApp.getContext()),
mApp.getModel()::onWidgetLabelsUpdated);
logASplit("save widgets in icon cache");
@@ -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/SdCardAvailableReceiver.java b/src/com/android/launcher3/model/SdCardAvailableReceiver.java
index 5293316..9e3f0e1 100644
--- a/src/com/android/launcher3/model/SdCardAvailableReceiver.java
+++ b/src/com/android/launcher3/model/SdCardAvailableReceiver.java
@@ -24,7 +24,7 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
-import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.ApplicationInfoWrapper;
import com.android.launcher3.util.PackageUserKey;
import java.util.ArrayList;
@@ -52,7 +52,6 @@
@Override
public void onReceive(Context context, Intent intent) {
final LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
- final PackageManagerHelper pmHelper = PackageManagerHelper.INSTANCE.get(context);
for (PackageUserKey puk : mPackages) {
UserHandle user = puk.mUser;
@@ -60,7 +59,7 @@
final ArrayList<String> packagesUnavailable = new ArrayList<>();
if (!launcherApps.isPackageEnabled(puk.mPackageName, user)) {
- if (pmHelper.isAppOnSdcard(puk.mPackageName, user)) {
+ if (new ApplicationInfoWrapper(context, puk.mPackageName, user).isOnSdCard()) {
packagesUnavailable.add(puk.mPackageName);
} else {
packagesRemoved.add(puk.mPackageName);
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index 1916d23..b5a7382 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -24,11 +24,12 @@
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;
+import com.android.launcher3.util.ApplicationInfoWrapper;
import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.PackageManagerHelper;
import java.util.ArrayList;
import java.util.HashSet;
@@ -79,12 +80,11 @@
}
if (!matchingWorkspaceItems.isEmpty()) {
+ ApplicationInfoWrapper infoWrapper =
+ new ApplicationInfoWrapper(context, mPackageName, mUser);
if (mShortcuts.isEmpty()) {
- PackageManagerHelper packageManagerHelper =
- PackageManagerHelper.INSTANCE.get(context);
// Verify that the app is indeed installed.
- if (!packageManagerHelper.isAppInstalled(mPackageName, mUser)
- && !packageManagerHelper.isAppArchivedForUser(mPackageName, mUser)) {
+ if (!infoWrapper.isInstalled() && !infoWrapper.isArchived()) {
// App is not installed or archived, ignoring package events
return;
}
@@ -104,7 +104,6 @@
if (!fullDetails.isPinned()) {
continue;
}
-
String sid = fullDetails.getId();
nonPinnedIds.remove(sid);
matchingWorkspaceItems
@@ -112,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/WidgetItem.java b/src/com/android/launcher3/model/WidgetItem.java
index 3f88717..ac9f2d6 100644
--- a/src/com/android/launcher3/model/WidgetItem.java
+++ b/src/com/android/launcher3/model/WidgetItem.java
@@ -4,8 +4,6 @@
import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD;
import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX;
-import static com.android.launcher3.Utilities.ATLEAST_S;
-
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.ActivityInfo;
@@ -48,7 +46,7 @@
super(info.provider, info.getProfile());
label = iconCache.getTitleNoCache(info);
- description = ATLEAST_S ? info.loadDescription(context) : null;
+ description = info.loadDescription(context);
widgetInfo = info;
activityInfo = null;
@@ -107,7 +105,7 @@
/** Returns whether this {@link WidgetItem} has a preview layout that can be used. */
@SuppressLint("NewApi") // Already added API check.
public boolean hasPreviewLayout() {
- return ATLEAST_S && widgetInfo != null && widgetInfo.previewLayout != Resources.ID_NULL;
+ return widgetInfo != null && widgetInfo.previewLayout != Resources.ID_NULL;
}
/** Returns whether this {@link WidgetItem} is for a shortcut rather than an app widget. */
diff --git a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
index 90e47d6..c02336e 100644
--- a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
+++ b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
@@ -30,9 +30,9 @@
import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.LauncherAppState
import com.android.launcher3.LauncherSettings.Favorites
-import com.android.launcher3.Utilities
import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError
import com.android.launcher3.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
@@ -45,6 +45,7 @@
import com.android.launcher3.pm.UserCache
import com.android.launcher3.shortcuts.ShortcutKey
import com.android.launcher3.util.ApiWrapper
+import com.android.launcher3.util.ApplicationInfoWrapper
import com.android.launcher3.util.ComponentKey
import com.android.launcher3.util.PackageManagerHelper
import com.android.launcher3.util.PackageUserKey
@@ -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
@@ -97,7 +98,7 @@
// User has been deleted, remove the item.
c.markDeleted(
"User has been deleted for item id=${c.id}",
- RestoreError.PROFILE_DELETED
+ RestoreError.PROFILE_DELETED,
)
return
}
@@ -153,6 +154,7 @@
c.markDeleted("No target package for item id=${c.id}", RestoreError.MISSING_INFO)
return
}
+ val appInfoWrapper = ApplicationInfoWrapper(app.context, targetPkg, c.user)
var validTarget = launcherApps.isPackageEnabled(targetPkg, c.user)
// If it's a deep shortcut, we'll use pinned shortcuts to restore it
@@ -168,7 +170,7 @@
FileLog.d(
TAG,
"Activity not enabled for id=${c.id}, component=$cn, user=${c.user}." +
- " Will attempt to find fallback Activity for targetPkg=$targetPkg."
+ " Will attempt to find fallback Activity for targetPkg=$targetPkg.",
)
intent = pmHelper.getAppLaunchIntent(targetPkg, c.user)
if (intent != null) {
@@ -178,7 +180,7 @@
c.markDeleted(
"No Activities found for id=${c.id}, targetPkg=$targetPkg, component=$cn." +
" Unable to create launch Intent.",
- RestoreError.MISSING_INFO
+ RestoreError.MISSING_INFO,
)
return
}
@@ -213,13 +215,13 @@
else -> {
c.markDeleted(
"removing app that is not restored and not installing. package: $targetPkg",
- RestoreError.APP_NOT_INSTALLED
+ RestoreError.APP_NOT_INSTALLED,
)
return
}
}
}
- pmHelper.isAppOnSdcard(targetPkg, c.user) -> {
+ appInfoWrapper.isOnSdCard() -> {
// Package is present but not available.
disabledState =
disabledState or WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE
@@ -238,7 +240,7 @@
// Do not wait for external media load anymore.
c.markDeleted(
"Invalid package removed: $targetPkg",
- RestoreError.APP_NOT_INSTALLED
+ RestoreError.APP_NOT_INSTALLED,
)
return
}
@@ -270,20 +272,21 @@
// The shortcut is no longer valid.
c.markDeleted(
"Pinned shortcut not found from request. package=${key.packageName}, user=${c.user}",
- RestoreError.SHORTCUT_NOT_FOUND
+ RestoreError.SHORTCUT_NOT_FOUND,
)
return
}
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)
- if (pmHelper.isAppSuspended(pinnedShortcut.getPackage(), info.user)) {
+ 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()
@@ -295,7 +298,7 @@
info = c.loadSimpleWorkspaceItem()
// Shortcuts are only available on the primary profile
- if (!TextUtils.isEmpty(targetPkg) && pmHelper.isAppSuspended(targetPkg, c.user)) {
+ if (appInfoWrapper.isSuspended()) {
disabledState = disabledState or ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED
}
info.options = c.options
@@ -326,7 +329,7 @@
info.spanX = 1
info.spanY = 1
info.runtimeStatusFlags = info.runtimeStatusFlags or disabledState
- if (isSafeMode && !PackageManagerHelper.isSystemApp(app.context, intent)) {
+ if (isSafeMode && !appInfoWrapper.isSystem()) {
info.runtimeStatusFlags =
info.runtimeStatusFlags or ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE
}
@@ -337,7 +340,7 @@
activityInfo,
userCache.getUserInfo(c.user),
ApiWrapper.INSTANCE[app.context],
- pmHelper
+ pmHelper,
)
}
if (
@@ -445,7 +448,7 @@
", id=${c.id}," +
", appWidgetId=${c.appWidgetId}," +
", component=${component}",
- RestoreError.INVALID_LOCATION
+ RestoreError.INVALID_LOCATION,
)
return
}
@@ -456,7 +459,7 @@
", appWidgetId=${c.appWidgetId}," +
", component=${component}," +
", container=${c.container}",
- RestoreError.INVALID_LOCATION
+ RestoreError.INVALID_LOCATION,
)
return
}
@@ -470,7 +473,7 @@
TAG,
"processWidget: id=${c.id}" +
", appWidgetId=${c.appWidgetId}" +
- ", inflationResult=$inflationResult"
+ ", inflationResult=$inflationResult",
)
when (inflationResult.type) {
WidgetInflater.TYPE_DELETE -> {
@@ -487,7 +490,8 @@
(si == null) &&
(lapi == null) &&
!(Flags.enableSupportForArchiving() &&
- pmHelper.isAppArchived(component.packageName))
+ ApplicationInfoWrapper(app.context, component.packageName, c.user)
+ .isArchived())
) {
// Restore never started
c.markDeleted(
@@ -496,7 +500,7 @@
", appWidgetId=${c.appWidgetId}" +
", component=${component}" +
", restoreFlag:=${c.restoreFlag}",
- RestoreError.APP_NOT_INSTALLED
+ RestoreError.APP_NOT_INSTALLED,
)
return
} else if (
@@ -512,7 +516,7 @@
WidgetsModel.newPendingItemInfo(
app.context,
appWidgetInfo.providerName,
- appWidgetInfo.user
+ appWidgetInfo.user,
)
iconCache.getTitleAndIconForApp(appWidgetInfo.pendingItemInfo, false)
}
@@ -522,7 +526,7 @@
lapi,
app.context,
appWidgetInfo.spanX,
- appWidgetInfo.spanY
+ appWidgetInfo.spanY,
)
}
@@ -541,7 +545,7 @@
" processWidget: Widget ${lapi.component} minSizes not met: span=${appWidgetInfo.spanX}x${appWidgetInfo.spanY} minSpan=${lapi.minSpanX}x${lapi.minSpanY}," +
" id: ${c.id}," +
" appWidgetId: ${c.appWidgetId}," +
- " component=${component}"
+ " component=${component}",
)
logWidgetInfo(app.invariantDeviceProfile, lapi)
}
@@ -554,7 +558,7 @@
private fun logWidgetInfo(
idp: InvariantDeviceProfile,
- widgetProviderInfo: LauncherAppWidgetProviderInfo
+ widgetProviderInfo: LauncherAppWidgetProviderInfo,
) {
val cellSize = Point()
for (deviceProfile in idp.supportedProfiles) {
@@ -565,7 +569,7 @@
" available height: ${deviceProfile.availableHeightPx}," +
" cellLayoutBorderSpacePx Horizontal: ${deviceProfile.cellLayoutBorderSpacePx.x}," +
" cellLayoutBorderSpacePx Vertical: ${deviceProfile.cellLayoutBorderSpacePx.y}," +
- " cellSize: $cellSize"
+ " cellSize: $cellSize",
)
}
val widgetDimension = StringBuilder()
@@ -583,21 +587,19 @@
.append("defaultHeight: ")
.append(widgetProviderInfo.minHeight)
.append("\n")
- if (Utilities.ATLEAST_S) {
- widgetDimension
- .append("targetCellWidth: ")
- .append(widgetProviderInfo.targetCellWidth)
- .append("\n")
- .append("targetCellHeight: ")
- .append(widgetProviderInfo.targetCellHeight)
- .append("\n")
- .append("maxResizeWidth: ")
- .append(widgetProviderInfo.maxResizeWidth)
- .append("\n")
- .append("maxResizeHeight: ")
- .append(widgetProviderInfo.maxResizeHeight)
- .append("\n")
- }
+ widgetDimension
+ .append("targetCellWidth: ")
+ .append(widgetProviderInfo.targetCellWidth)
+ .append("\n")
+ .append("targetCellHeight: ")
+ .append(widgetProviderInfo.targetCellHeight)
+ .append("\n")
+ .append("maxResizeWidth: ")
+ .append(widgetProviderInfo.maxResizeWidth)
+ .append("\n")
+ .append("maxResizeHeight: ")
+ .append(widgetProviderInfo.maxResizeHeight)
+ .append("\n")
FileLog.d(TAG, widgetDimension.toString())
}
}
diff --git a/src/com/android/launcher3/model/data/AppInfo.java b/src/com/android/launcher3/model/data/AppInfo.java
index a4281f8..97b62b4 100644
--- a/src/com/android/launcher3/model/data/AppInfo.java
+++ b/src/com/android/launcher3/model/data/AppInfo.java
@@ -21,7 +21,6 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherActivityInfo;
import android.os.UserHandle;
import android.os.UserManager;
@@ -36,6 +35,7 @@
import com.android.launcher3.pm.PackageInstallInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.ApiWrapper;
+import com.android.launcher3.util.ApplicationInfoWrapper;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.UserIconInfo;
@@ -187,8 +187,8 @@
ApiWrapper apiWrapper, PackageManagerHelper pmHelper) {
final int oldProgressLevel = info.getProgressLevel();
final int oldRuntimeStatusFlags = info.runtimeStatusFlags;
- ApplicationInfo appInfo = lai.getApplicationInfo();
- if (PackageManagerHelper.isAppSuspended(appInfo)) {
+ ApplicationInfoWrapper appInfo = new ApplicationInfoWrapper(lai.getApplicationInfo());
+ if (appInfo.isSuspended()) {
info.runtimeStatusFlags |= FLAG_DISABLED_SUSPENDED;
} else {
info.runtimeStatusFlags &= ~FLAG_DISABLED_SUSPENDED;
@@ -200,8 +200,7 @@
info.runtimeStatusFlags &= ~FLAG_ARCHIVED;
}
}
- info.runtimeStatusFlags |= (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0
- ? FLAG_SYSTEM_NO : FLAG_SYSTEM_YES;
+ info.runtimeStatusFlags |= appInfo.isSystem() ? FLAG_SYSTEM_YES : FLAG_SYSTEM_NO;
if (Flags.privateSpaceRestrictAccessibilityDrag()) {
if (userIconInfo.isPrivate()) {
diff --git a/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java b/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
index f4dda55..361f09d 100644
--- a/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
@@ -21,7 +21,6 @@
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PIN_WIDGETS;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY;
-import static com.android.launcher3.Utilities.ATLEAST_S;
import android.appwidget.AppWidgetHostView;
import android.content.ComponentName;
@@ -233,16 +232,16 @@
if (providerInfo.isConfigurationOptional()) {
widgetFeatures |= FEATURE_OPTIONAL_CONFIGURATION;
}
- if (ATLEAST_S && providerInfo.previewLayout != Resources.ID_NULL) {
+ if (providerInfo.previewLayout != Resources.ID_NULL) {
widgetFeatures |= FEATURE_PREVIEW_LAYOUT;
}
- if (ATLEAST_S && providerInfo.targetCellWidth > 0 || providerInfo.targetCellHeight > 0) {
+ if (providerInfo.targetCellWidth > 0 || providerInfo.targetCellHeight > 0) {
widgetFeatures |= FEATURE_TARGET_CELL_SIZE;
}
if (providerInfo.minResizeWidth > 0 || providerInfo.minResizeHeight > 0) {
widgetFeatures |= FEATURE_MIN_SIZE;
}
- if (ATLEAST_S && providerInfo.maxResizeWidth > 0 || providerInfo.maxResizeHeight > 0) {
+ if (providerInfo.maxResizeWidth > 0 || providerInfo.maxResizeHeight > 0) {
widgetFeatures |= FEATURE_MAX_SIZE;
}
if (hostView instanceof LauncherAppWidgetHostView &&
diff --git a/src/com/android/launcher3/pm/InstallSessionHelper.java b/src/com/android/launcher3/pm/InstallSessionHelper.java
index e66f496..f36f595 100644
--- a/src/com/android/launcher3/pm/InstallSessionHelper.java
+++ b/src/com/android/launcher3/pm/InstallSessionHelper.java
@@ -17,7 +17,6 @@
package com.android.launcher3.pm;
import android.content.Context;
-import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
@@ -32,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.PackageManagerHelper;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.SafeCloseable;
@@ -48,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
@@ -65,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;
@@ -83,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
@@ -171,8 +181,7 @@
synchronized (mSessionVerifiedMap) {
if (!mSessionVerifiedMap.containsKey(pkg)) {
boolean hasSystemFlag = DEBUG || mAppContext.getPackageName().equals(pkg)
- || PackageManagerHelper.INSTANCE.get(mAppContext)
- .getApplicationInfo(pkg, user, ApplicationInfo.FLAG_SYSTEM) != null;
+ || new ApplicationInfoWrapper(mAppContext, pkg, user).isSystem();
mSessionVerifiedMap.put(pkg, hasSystemFlag);
}
}
@@ -245,8 +254,8 @@
&& sessionInfo.getInstallReason() == PackageManager.INSTALL_REASON_USER
&& sessionInfo.getAppIcon() != null
&& !TextUtils.isEmpty(sessionInfo.getAppLabel())
- && !PackageManagerHelper.INSTANCE.get(mAppContext).isAppInstalled(
- sessionInfo.getAppPackageName(), getUserHandle(sessionInfo));
+ && !new ApplicationInfoWrapper(mAppContext, sessionInfo.getAppPackageName(),
+ getUserHandle(sessionInfo)).isInstalled();
}
public InstallSessionTracker registerInstallTracker(
diff --git a/src/com/android/launcher3/pm/PinRequestHelper.java b/src/com/android/launcher3/pm/PinRequestHelper.java
index 667136a..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 {
@@ -77,8 +78,10 @@
WorkspaceItemInfo info = new WorkspaceItemInfo(si, context);
// Apply the unbadged icon synchronously using the caching logic directly and
// fetch the actual icon asynchronously.
- info.bitmap = new ShortcutCachingLogic().loadIcon(context, si);
- LauncherAppState.getInstance(context).getModel().updateAndBindWorkspaceItem(info, si);
+ LauncherAppState app = LauncherAppState.getInstance(context);
+ info.bitmap = CacheableShortcutCachingLogic.INSTANCE.loadIcon(
+ context, app.getIconCache(), new CacheableShortcutInfo(si, context));
+ app.getModel().updateAndBindWorkspaceItem(info, si);
return info;
} else {
return null;
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/secondarydisplay/SecondaryDragLayer.java b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
index 6e697d9..d5c87f4 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
@@ -65,7 +65,7 @@
@Override
public void recreateControllers() {
mControllers = new TouchController[]{new CloseAllAppsTouchController(),
- mActivity.getDragController()};
+ mContainer.getDragController()};
}
/**
@@ -79,10 +79,10 @@
mAppsView = findViewById(R.id.apps_view);
// Setup workspace
mWorkspace = findViewById(R.id.workspace_grid);
- mPinnedAppsAdapter = new PinnedAppsAdapter(mActivity, mAppsView.getAppsStore(),
+ mPinnedAppsAdapter = new PinnedAppsAdapter(mContainer, mAppsView.getAppsStore(),
this::onIconLongClicked);
mWorkspace.setAdapter(mPinnedAppsAdapter);
- mWorkspace.setNumColumns(mActivity.getDeviceProfile().inv.numColumns);
+ mWorkspace.setNumColumns(mContainer.getDeviceProfile().inv.numColumns);
}
/**
@@ -112,7 +112,7 @@
int height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(width, height);
- DeviceProfile grid = mActivity.getDeviceProfile();
+ DeviceProfile grid = mContainer.getDeviceProfile();
int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
@@ -153,17 +153,17 @@
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
- if (!mActivity.isAppDrawerShown()) {
+ if (!mContainer.isAppDrawerShown()) {
return false;
}
- if (AbstractFloatingView.getTopOpenView(mActivity) != null) {
+ if (AbstractFloatingView.getTopOpenView(mContainer) != null) {
return false;
}
if (ev.getAction() == MotionEvent.ACTION_DOWN
- && !isEventOverView(mActivity.getAppsView(), ev)) {
- mActivity.showAppDrawer(false);
+ && !isEventOverView(mContainer.getAppsView(), ev)) {
+ mContainer.showAppDrawer(false);
return true;
}
return false;
@@ -178,7 +178,7 @@
if (!(v instanceof BubbleTextView)) {
return false;
}
- if (PopupContainerWithArrow.getOpen(mActivity) != null) {
+ if (PopupContainerWithArrow.getOpen(mContainer) != null) {
// There is already an items container open, so don't open this one.
v.clearFocus();
return false;
@@ -187,32 +187,32 @@
if (!ShortcutUtil.supportsShortcuts(item)) {
return false;
}
- PopupDataProvider popupDataProvider = mActivity.getPopupDataProvider();
+ PopupDataProvider popupDataProvider = mContainer.getPopupDataProvider();
if (popupDataProvider == null) {
return false;
}
// order of this list will reflect in the popup
List<SystemShortcut> systemShortcuts = new ArrayList<>();
- systemShortcuts.add(APP_INFO.getShortcut(mActivity, item, v));
+ systemShortcuts.add(APP_INFO.getShortcut(mContainer, item, v));
// Hide redundant pin shortcut for app drawer icons if drag-n-drop is enabled.
- if (!FeatureFlags.SECONDARY_DRAG_N_DROP_TO_PIN.get() || !mActivity.isAppDrawerShown()) {
+ if (!FeatureFlags.SECONDARY_DRAG_N_DROP_TO_PIN.get() || !mContainer.isAppDrawerShown()) {
systemShortcuts.add(mPinnedAppsAdapter.getSystemShortcut(item, v));
}
int deepShortcutCount = popupDataProvider.getShortcutCountForItem(item);
final PopupContainerWithArrow<SecondaryDisplayLauncher> container;
- container = (PopupContainerWithArrow) mActivity.getLayoutInflater().inflate(
- R.layout.popup_container, mActivity.getDragLayer(), false);
+ container = (PopupContainerWithArrow) mContainer.getLayoutInflater().inflate(
+ R.layout.popup_container, mContainer.getDragLayer(), false);
container.populateAndShowRows((BubbleTextView) v, deepShortcutCount,
systemShortcuts);
container.requestFocus();
- if (!FeatureFlags.SECONDARY_DRAG_N_DROP_TO_PIN.get() || !mActivity.isAppDrawerShown()) {
+ if (!FeatureFlags.SECONDARY_DRAG_N_DROP_TO_PIN.get() || !mContainer.isAppDrawerShown()) {
return true;
}
DragOptions options = new DragOptions();
- DeviceProfile grid = mActivity.getDeviceProfile();
+ DeviceProfile grid = mContainer.getDeviceProfile();
options.intrinsicIconScaleFactor = (float) grid.allAppsIconSizePx / grid.iconSizePx;
options.preDragCondition = container.createPreDragCondition(false);
if (options.preDragCondition == null) {
@@ -229,7 +229,7 @@
mDragView = dragObject.dragView;
if (!shouldStartDrag(0)) {
mDragView.setOnScaleAnimEndCallback(() ->
- mActivity.beginDragShared(v, mActivity.getAppsView(), options));
+ mContainer.beginDragShared(v, mContainer.getAppsView(), options));
}
}
@@ -239,7 +239,7 @@
}
};
}
- mActivity.beginDragShared(v, mActivity.getAppsView(), options);
+ mContainer.beginDragShared(v, mContainer.getAppsView(), options);
return true;
}
}
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/statemanager/StatefulActivity.java b/src/com/android/launcher3/statemanager/StatefulActivity.java
index 28f2def..54b2eae 100644
--- a/src/com/android/launcher3/statemanager/StatefulActivity.java
+++ b/src/com/android/launcher3/statemanager/StatefulActivity.java
@@ -20,6 +20,7 @@
import static com.android.launcher3.LauncherState.FLAG_NON_INTERACTIVE;
+import android.content.Context;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Handler;
@@ -195,15 +196,15 @@
mOldRotation = rotation;
}
+ @Override
+ public Context getContext() {
+ return this;
+ }
+
/**
* Logic for when device configuration changes (rotation, screen size change, multi-window,
* etc.)
*/
protected abstract void onHandleConfigurationChanged();
- /**
- * Enter staged split directly from the current running app.
- * @param leftOrTop if the staged split will be positioned left or top.
- */
- public void enterStageSplitFromRunningApp(boolean leftOrTop) { }
}
diff --git a/src/com/android/launcher3/statemanager/StatefulContainer.java b/src/com/android/launcher3/statemanager/StatefulContainer.java
index 0cf0a27..b10af0a 100644
--- a/src/com/android/launcher3/statemanager/StatefulContainer.java
+++ b/src/com/android/launcher3/statemanager/StatefulContainer.java
@@ -20,6 +20,10 @@
import static com.android.launcher3.LauncherState.FLAG_CLOSE_POPUPS;
import static com.android.launcher3.statemanager.BaseState.FLAG_NON_INTERACTIVE;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.Configuration;
+
import androidx.annotation.CallSuper;
import com.android.launcher3.AbstractFloatingView;
@@ -36,6 +40,23 @@
ActivityContext {
/**
+ * Returns an instance of an implementation of StatefulContainer
+ *
+ * @param context will find instance of StatefulContainer from given context.
+ */
+ static <T extends StatefulContainer> T fromContext(Context context) {
+ if (context instanceof StatefulContainer) {
+ return (T) context;
+ } else if (context instanceof ContextWrapper) {
+ return fromContext(((ContextWrapper) context).getBaseContext());
+ } else {
+ throw new IllegalArgumentException("Cannot find StatefulContainer in parent tree");
+ }
+ }
+
+ Context getContext();
+
+ /**
* Creates a factory for atomic state animations
*/
default StateManager.AtomicAnimationFactory<STATE_TYPE> createAtomicAnimationFactory() {
@@ -54,12 +75,15 @@
/**
* Called when transition to state ends
+ *
* @param state current state of State_Type
*/
- default void onStateSetEnd(STATE_TYPE state) { }
+ default void onStateSetEnd(STATE_TYPE state) {
+ }
/**
* Called when transition to state starts
+ *
* @param state current state of State_Type
*/
@CallSuper
@@ -71,6 +95,7 @@
/**
* Returns true if the activity is in the provided state
+ *
* @param state current state of State_Type
*/
default boolean isInState(STATE_TYPE state) {
@@ -81,4 +106,8 @@
* Returns true if state change should transition with animation
*/
boolean shouldAnimateStateChange();
+
+ default void handleConfigurationChanged(Configuration configuration){
+ //no op
+ }
}
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 6d9b891..3a93981 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -168,21 +168,14 @@
}
case TestProtocol.REQUEST_TARGET_INSETS: {
- return getUIProperty(Bundle::putParcelable, activity -> {
- WindowInsets insets = activity.getWindow()
- .getDecorView().getRootWindowInsets();
- return Insets.max(
- insets.getSystemGestureInsets(),
- insets.getSystemWindowInsets());
- }, this::getCurrentActivity);
+ return getUIProperty(Bundle::putParcelable, insets -> Insets.max(
+ insets.getSystemGestureInsets(),
+ insets.getSystemWindowInsets()), this::getWindowInsets);
}
case TestProtocol.REQUEST_WINDOW_INSETS: {
- return getUIProperty(Bundle::putParcelable, activity -> {
- WindowInsets insets = activity.getWindow()
- .getDecorView().getRootWindowInsets();
- return insets.getSystemWindowInsets();
- }, this::getCurrentActivity);
+ return getUIProperty(Bundle::putParcelable,
+ WindowInsets::getSystemWindowInsets, this::getWindowInsets);
}
case TestProtocol.REQUEST_CELL_LAYOUT_BOARDER_HEIGHT: {
@@ -192,13 +185,13 @@
}
case TestProtocol.REQUEST_SYSTEM_GESTURE_REGION: {
- return getUIProperty(Bundle::putParcelable, activity -> {
- WindowInsetsCompat insets = WindowInsetsCompat.toWindowInsetsCompat(
- activity.getWindow().getDecorView().getRootWindowInsets());
+ return getUIProperty(Bundle::putParcelable, windowInsets -> {
+ WindowInsetsCompat insets =
+ WindowInsetsCompat.toWindowInsetsCompat(windowInsets);
return insets.getInsets(WindowInsetsCompat.Type.ime()
| WindowInsetsCompat.Type.systemGestures())
.toPlatformInsets();
- }, this::getCurrentActivity);
+ }, this::getWindowInsets);
}
case TestProtocol.REQUEST_ICON_HEIGHT: {
@@ -486,8 +479,9 @@
|| LauncherAppState.getInstance(mContext).getModel().isModelLoaded();
}
- protected Activity getCurrentActivity() {
- return Launcher.ACTIVITY_TRACKER.getCreatedActivity();
+ protected WindowInsets getWindowInsets(){
+ return Launcher.ACTIVITY_TRACKER.getCreatedActivity().getWindow().getDecorView()
+ .getRootWindowInsets();
}
/**
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 3817563..74a0966 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -40,11 +40,13 @@
import com.android.launcher3.LauncherState;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.contextualeducation.ContextualEduStatsManager;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.util.FlingBlockCheck;
import com.android.launcher3.util.TouchController;
+import com.android.systemui.contextualeducation.GestureType;
/**
* TouchController for handling state changes
@@ -388,6 +390,7 @@
} else {
logReachedState(mToState);
}
+ updateContextualEduStats(targetState);
}
protected void goToTargetState(LauncherState targetState) {
@@ -403,6 +406,18 @@
.setDuration(0).start();
}
+ private void updateContextualEduStats(LauncherState targetState) {
+ if (targetState == OVERVIEW) {
+ ContextualEduStatsManager.INSTANCE.get(
+ mLauncher).updateEduStats(mDetector.isTrackpadGesture(), GestureType.OVERVIEW);
+ } else if (targetState == ALL_APPS && !mDetector.isTrackpadGesture()) {
+ // Only update if it is touch gesture as trackpad gesture is not relevant for all apps
+ // which only provides keyboard education.
+ ContextualEduStatsManager.INSTANCE.get(
+ mLauncher).updateEduStats(/* isTrackpadGesture= */ false, GestureType.ALL_APPS);
+ }
+ }
+
private void logReachedState(LauncherState targetState) {
if (mStartState == targetState) {
return;
diff --git a/src/com/android/launcher3/touch/BaseSwipeDetector.java b/src/com/android/launcher3/touch/BaseSwipeDetector.java
index 52c3581..faac4a3 100644
--- a/src/com/android/launcher3/touch/BaseSwipeDetector.java
+++ b/src/com/android/launcher3/touch/BaseSwipeDetector.java
@@ -17,6 +17,8 @@
import static android.view.MotionEvent.INVALID_POINTER_ID;
+import static com.android.launcher3.MotionEventsUtils.isTrackpadMotionEvent;
+
import android.content.Context;
import android.graphics.PointF;
import android.util.Log;
@@ -64,6 +66,7 @@
protected PointF mSubtractDisplacement = new PointF();
@VisibleForTesting ScrollState mState = ScrollState.IDLE;
private boolean mIsSettingState;
+ protected boolean mIsTrackpadGesture;
protected boolean mIgnoreSlopWhenSettling;
protected Context mContext;
@@ -122,6 +125,10 @@
return mState == ScrollState.DRAGGING || mState == ScrollState.SETTLING;
}
+ public boolean isTrackpadGesture() {
+ return mIsTrackpadGesture;
+ }
+
public void finishedScrolling() {
setState(ScrollState.IDLE);
}
@@ -147,7 +154,7 @@
mLastPos.set(mDownPos);
mLastDisplacement.set(0, 0);
mDisplacement.set(0, 0);
-
+ mIsTrackpadGesture = isTrackpadMotionEvent(ev);
if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
setState(ScrollState.DRAGGING);
}
diff --git a/src/com/android/launcher3/util/ApplicationInfoWrapper.kt b/src/com/android/launcher3/util/ApplicationInfoWrapper.kt
new file mode 100644
index 0000000..e75b3bc
--- /dev/null
+++ b/src/com/android/launcher3/util/ApplicationInfoWrapper.kt
@@ -0,0 +1,115 @@
+/*
+ * 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.util
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.ApplicationInfo.FLAG_EXTERNAL_STORAGE
+import android.content.pm.ApplicationInfo.FLAG_INSTALLED
+import android.content.pm.ApplicationInfo.FLAG_SUSPENDED
+import android.content.pm.ApplicationInfo.FLAG_SYSTEM
+import android.content.pm.LauncherApps
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.NameNotFoundException
+import android.os.UserHandle
+import com.android.launcher3.Flags.enableSupportForArchiving
+import com.android.launcher3.Utilities.ATLEAST_V
+import kotlin.LazyThreadSafetyMode.NONE
+
+/**
+ * A set of utility methods around ApplicationInfo with support for fetching the actual info lazily
+ */
+class ApplicationInfoWrapper private constructor(provider: () -> ApplicationInfo?) {
+
+ constructor(appInfo: ApplicationInfo?) : this({ appInfo })
+
+ constructor(
+ ctx: Context,
+ pkg: String,
+ user: UserHandle,
+ ) : this({
+ try {
+ ctx.getSystemService(LauncherApps::class.java)
+ ?.getApplicationInfo(pkg, PackageManager.MATCH_UNINSTALLED_PACKAGES, user)
+ ?.let { ai ->
+ // its enabled and (either installed or archived)
+ if (
+ ai.enabled &&
+ (ai.flags.and(FLAG_INSTALLED) != 0 ||
+ (ATLEAST_V && enableSupportForArchiving() && ai.isArchived))
+ ) {
+ ai
+ } else {
+ null
+ }
+ }
+ } catch (e: NameNotFoundException) {
+ null
+ }
+ })
+
+ constructor(
+ ctx: Context,
+ intent: Intent,
+ ) : this(
+ provider@{
+ try {
+ val pm = ctx.packageManager
+ val packageName: String =
+ intent.component?.packageName
+ ?: intent.getPackage()
+ ?: return@provider pm.resolveActivity(
+ intent,
+ PackageManager.MATCH_DEFAULT_ONLY,
+ )
+ ?.activityInfo
+ ?.applicationInfo
+ pm.getApplicationInfo(packageName, 0)
+ } catch (e: NameNotFoundException) {
+ null
+ }
+ }
+ )
+
+ private val appInfo: ApplicationInfo? by lazy(NONE, provider)
+
+ private fun hasFlag(flag: Int) = appInfo?.let { it.flags.and(flag) != 0 } ?: false
+
+ /**
+ * Returns true if the app can possibly be on the SDCard. This is just a workaround and doesn't
+ * guarantee that the app is on SD card.
+ */
+ fun isOnSdCard() = hasFlag(FLAG_EXTERNAL_STORAGE)
+
+ /** Returns whether the target app is installed for a given user */
+ fun isInstalled() = hasFlag(FLAG_INSTALLED)
+
+ /**
+ * Returns whether the target app is suspended for a given user as per
+ * [android.app.admin.DevicePolicyManager.isPackageSuspended].
+ */
+ fun isSuspended() = hasFlag(FLAG_INSTALLED) && hasFlag(FLAG_SUSPENDED)
+
+ /** Returns whether the target app is archived for a given user */
+ fun isArchived() = ATLEAST_V && enableSupportForArchiving() && appInfo?.isArchived ?: false
+
+ /** Returns whether the target app is a system app */
+ fun isSystem() = hasFlag(FLAG_SYSTEM)
+
+ fun getInfo(): ApplicationInfo? = appInfo
+}
diff --git a/src/com/android/launcher3/util/DaggerSingletonObject.java b/src/com/android/launcher3/util/DaggerSingletonObject.java
new file mode 100644
index 0000000..b8cf2ae
--- /dev/null
+++ b/src/com/android/launcher3/util/DaggerSingletonObject.java
@@ -0,0 +1,44 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+
+import com.android.launcher3.LauncherApplication;
+import com.android.launcher3.dagger.LauncherAppComponent;
+
+import java.util.function.Function;
+
+/**
+ * A class to provide DaggerSingleton objects in a traditional way for
+ * {@link MainThreadInitializedObject}.
+ * We should delete this class at the end and use @Inject to get dagger provided singletons.
+ */
+
+public class DaggerSingletonObject<T extends SafeCloseable> {
+ private final Function<LauncherAppComponent, T> mFunction;
+
+ public DaggerSingletonObject(Function<LauncherAppComponent, T> function) {
+ mFunction = function;
+ }
+
+ public T get(Context context) {
+ LauncherAppComponent component =
+ ((LauncherApplication) context.getApplicationContext()).getAppComponent();
+ return mFunction.apply(component);
+ }
+}
diff --git a/src/com/android/launcher3/util/DaggerSingletonTracker.java b/src/com/android/launcher3/util/DaggerSingletonTracker.java
new file mode 100644
index 0000000..2946da1
--- /dev/null
+++ b/src/com/android/launcher3/util/DaggerSingletonTracker.java
@@ -0,0 +1,57 @@
+/*
+ * 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.util;
+
+import com.android.launcher3.dagger.LauncherAppSingleton;
+
+import java.util.ArrayList;
+
+import javax.inject.Inject;
+
+/**
+ * A tracker class for keeping track of Dagger created singletons.
+ * Dagger will take care of creating singletons. But we should take care of unregistering callbacks
+ * if at all registered during singleton construction.
+ * All singletons should be declared as SafeCloseable so that we can call close() method.
+ */
+@LauncherAppSingleton
+public class DaggerSingletonTracker implements SafeCloseable {
+
+ private final ArrayList<SafeCloseable> mLauncherAppSingletons = new ArrayList<>();
+
+ @Inject
+ DaggerSingletonTracker() {
+ }
+
+ /**
+ * Adds the SafeCloseable Singletons to the mLauncherAppSingletons list.
+ * This helps to track the singletons and close them appropriately.
+ * See {@link DaggerSingletonTracker#close()} and
+ * {@link MainThreadInitializedObject.SandboxContext#onDestroy()}
+ */
+ public void addCloseable(SafeCloseable closeable) {
+ mLauncherAppSingletons.add(closeable);
+ }
+
+ @Override
+ public void close() {
+ // Destroy in reverse order
+ for (int i = mLauncherAppSingletons.size() - 1; i >= 0; i--) {
+ mLauncherAppSingletons.get(i).close();
+ }
+ }
+}
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 072bcdf..c59cc81 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -15,7 +15,6 @@
*/
package com.android.launcher3.util;
-import static android.content.Intent.ACTION_CONFIGURATION_CHANGED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -33,7 +32,6 @@
import static com.android.launcher3.util.window.WindowManagerProxy.MIN_TABLET_WIDTH;
import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.Intent;
@@ -42,7 +40,6 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
-import android.os.Build;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -132,21 +129,15 @@
}
Display display = mDM.getDisplay(DEFAULT_DISPLAY);
- if (Utilities.ATLEAST_S) {
- mWindowContext = mContext.createWindowContext(display, TYPE_APPLICATION, null);
- mWindowContext.registerComponentCallbacks(this);
- } else {
- mWindowContext = null;
- mReceiver.register(mContext, ACTION_CONFIGURATION_CHANGED);
- }
+ mWindowContext = mContext.createWindowContext(display, TYPE_APPLICATION, null);
+ mWindowContext.registerComponentCallbacks(this);
// Initialize navigation mode change listener
mReceiver.registerPkgActions(mContext, TARGET_OVERLAY_PACKAGE, ACTION_OVERLAY_CHANGED);
WindowManagerProxy wmProxy = WindowManagerProxy.INSTANCE.get(context);
- Context displayInfoContext = getDisplayInfoContext(display);
- mInfo = new Info(displayInfoContext, wmProxy,
- wmProxy.estimateInternalDisplayBounds(displayInfoContext));
+ mInfo = new Info(mWindowContext, wmProxy,
+ wmProxy.estimateInternalDisplayBounds(mWindowContext));
FileLog.i(TAG, "(CTOR) perDisplayBounds: " + mInfo.mPerDisplayBounds);
}
@@ -161,7 +152,7 @@
&& mInfo.mIsTaskbarPinnedInDesktopMode != prefs.get(
TASKBAR_PINNING_IN_DESKTOP_MODE);
if (isTaskbarPinningChanged || isTaskbarPinningDesktopModeChanged) {
- handleInfoChange(mWindowContext.getDisplay());
+ notifyConfigChange();
}
};
@@ -188,13 +179,6 @@
}
/**
- * Handles info change for desktop mode.
- */
- public static void handleInfoChangeForDesktopMode(Context context) {
- INSTANCE.get(context).handleInfoChange(context.getDisplay());
- }
-
- /**
* Enables transient taskbar status for tests.
*/
@VisibleForTesting
@@ -217,6 +201,13 @@
return INSTANCE.get(context).getInfo().isPinnedTaskbar();
}
+ /**
+ * Returns whether the taskbar is forced to be pinned when home is visible.
+ */
+ public static boolean showLockedTaskbarOnHome(Context context) {
+ return INSTANCE.get(context).getInfo().showLockedTaskbarOnHome();
+ }
+
@Override
public void close() {
mDestroyed = true;
@@ -252,36 +243,22 @@
if (mDestroyed) {
return;
}
- boolean reconfigure = false;
if (ACTION_OVERLAY_CHANGED.equals(intent.getAction())) {
- reconfigure = true;
- } else if (ACTION_CONFIGURATION_CHANGED.equals(intent.getAction())) {
- Configuration config = mContext.getResources().getConfiguration();
- reconfigure = mInfo.fontScale != config.fontScale
- || mInfo.densityDpi != config.densityDpi;
- }
-
- if (reconfigure) {
- Log.d(TAG, "Configuration changed, notifying listeners");
- Display display = mDM.getDisplay(DEFAULT_DISPLAY);
- if (display != null) {
- handleInfoChange(display);
- }
+ Log.d(TAG, "Overlay changed, notifying listeners");
+ notifyConfigChange();
}
}
@UiThread
@Override
- @TargetApi(Build.VERSION_CODES.S)
public final void onConfigurationChanged(Configuration config) {
Log.d(TASKBAR_NOT_DESTROYED_TAG, "DisplayController#onConfigurationChanged: " + config);
- Display display = mWindowContext.getDisplay();
if (config.densityDpi != mInfo.densityDpi
|| config.fontScale != mInfo.fontScale
- || display.getRotation() != mInfo.rotation
|| !mInfo.mScreenSizeDp.equals(
- new PortraitSize(config.screenHeightDp, config.screenWidthDp))) {
- handleInfoChange(display);
+ new PortraitSize(config.screenHeightDp, config.screenWidthDp))
+ || mWindowContext.getDisplay().getRotation() != mInfo.rotation) {
+ notifyConfigChange();
}
}
@@ -304,17 +281,12 @@
return mInfo;
}
- private Context getDisplayInfoContext(Display display) {
- return Utilities.ATLEAST_S ? mWindowContext : mContext.createDisplayContext(display);
- }
-
@AnyThread
- @VisibleForTesting
- public void handleInfoChange(Display display) {
+ public void notifyConfigChange() {
WindowManagerProxy wmProxy = WindowManagerProxy.INSTANCE.get(mContext);
Info oldInfo = mInfo;
- Context displayInfoContext = getDisplayInfoContext(display);
+ Context displayInfoContext = mWindowContext;
Info newInfo = new Info(displayInfoContext, wmProxy, oldInfo.mPerDisplayBounds);
if (newInfo.densityDpi != oldInfo.densityDpi || newInfo.fontScale != oldInfo.fontScale
@@ -345,7 +317,8 @@
}
if ((newInfo.mIsTaskbarPinned != oldInfo.mIsTaskbarPinned)
|| (newInfo.mIsTaskbarPinnedInDesktopMode
- != oldInfo.mIsTaskbarPinnedInDesktopMode)) {
+ != oldInfo.mIsTaskbarPinnedInDesktopMode)
+ || newInfo.isPinnedTaskbar() != oldInfo.isPinnedTaskbar()) {
change |= CHANGE_TASKBAR_PINNING;
}
if (newInfo.mIsInDesktopMode != oldInfo.mIsInDesktopMode) {
@@ -399,6 +372,9 @@
private final boolean mIsInDesktopMode;
+ private final boolean mShowLockedTaskbarOnHome;
+ private final boolean mIsHomeVisible;
+
public Info(Context displayInfoContext) {
/* don't need system overrides for external displays */
this(displayInfoContext, new WindowManagerProxy(), new ArrayMap<>());
@@ -460,6 +436,8 @@
mIsTaskbarPinnedInDesktopMode = LauncherPrefs.get(displayInfoContext).get(
TASKBAR_PINNING_IN_DESKTOP_MODE);
mIsInDesktopMode = wmProxy.isInDesktopMode();
+ mShowLockedTaskbarOnHome = wmProxy.showLockedTaskbarOnHome(displayInfoContext);
+ mIsHomeVisible = wmProxy.isHomeVisible(displayInfoContext);
}
/**
@@ -476,6 +454,10 @@
return sTransientTaskbarStatusForTests;
}
if (enableTaskbarPinning()) {
+ // If Launcher is visible on the freeform display, ensure the taskbar is pinned.
+ if (mShowLockedTaskbarOnHome && mIsHomeVisible) {
+ return false;
+ }
if (mIsInDesktopMode) {
return !mIsTaskbarPinnedInDesktopMode;
}
@@ -543,6 +525,13 @@
return TYPE_PHONE;
}
}
+
+ /**
+ * Returns whether the taskbar is forced to be pinned when home is visible.
+ */
+ public boolean showLockedTaskbarOnHome() {
+ return mShowLockedTaskbarOnHome;
+ }
}
/**
diff --git a/src/com/android/launcher3/util/EdgeEffectCompat.java b/src/com/android/launcher3/util/EdgeEffectCompat.java
index ca37259..a949f50 100644
--- a/src/com/android/launcher3/util/EdgeEffectCompat.java
+++ b/src/com/android/launcher3/util/EdgeEffectCompat.java
@@ -19,8 +19,6 @@
import android.view.MotionEvent;
import android.widget.EdgeEffect;
-import com.android.launcher3.Utilities;
-
/**
* Extension of {@link EdgeEffect} to allow backwards compatibility
*/
@@ -30,21 +28,6 @@
super(context);
}
- @Override
- public float getDistance() {
- return Utilities.ATLEAST_S ? super.getDistance() : 0;
- }
-
- @Override
- public float onPullDistance(float deltaDistance, float displacement) {
- if (Utilities.ATLEAST_S) {
- return super.onPullDistance(deltaDistance, displacement);
- } else {
- onPull(deltaDistance, displacement);
- return deltaDistance;
- }
- }
-
public float onPullDistance(float deltaDistance, float displacement, MotionEvent ev) {
return onPullDistance(deltaDistance, displacement);
}
diff --git a/src/com/android/launcher3/util/ExecutorUtil.java b/src/com/android/launcher3/util/ExecutorUtil.java
new file mode 100644
index 0000000..efc0eec
--- /dev/null
+++ b/src/com/android/launcher3/util/ExecutorUtil.java
@@ -0,0 +1,37 @@
+/*
+ * 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.util;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
+import android.os.Looper;
+
+import java.util.concurrent.ExecutionException;
+
+public final class ExecutorUtil {
+
+ /**
+ * Executes runnable on {@link Looper#getMainLooper()}, otherwise fails with an exception.
+ */
+ public static void executeSyncOnMainOrFail(Runnable runnable) {
+ try {
+ MAIN_EXECUTOR.submit(runnable).get();
+ } catch (InterruptedException | ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/util/MainThreadInitializedObject.java b/src/com/android/launcher3/util/MainThreadInitializedObject.java
index 63f14bd..a7d5c13 100644
--- a/src/com/android/launcher3/util/MainThreadInitializedObject.java
+++ b/src/com/android/launcher3/util/MainThreadInitializedObject.java
@@ -18,13 +18,13 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.content.Context;
-import android.content.ContextWrapper;
import android.os.Looper;
import android.util.Log;
import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting;
+import com.android.launcher3.LauncherApplication;
import com.android.launcher3.util.ResourceBasedOverride.Overrides;
import java.util.ArrayList;
@@ -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);
@@ -118,7 +137,7 @@
* Abstract Context which allows custom implementations for
* {@link MainThreadInitializedObject} providers
*/
- public static class SandboxContext extends ContextWrapper implements SandboxApplication {
+ public static class SandboxContext extends LauncherApplication implements SandboxApplication {
private static final String TAG = "SandboxContext";
@@ -129,7 +148,8 @@
private boolean mDestroyed = false;
public SandboxContext(Context base) {
- super(base);
+ attachBaseContext(base);
+ initDagger();
}
@Override
@@ -137,7 +157,20 @@
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
for (int i = mOrderedObjects.size() - 1; i >= 0; i--) {
@@ -172,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/util/OverlayEdgeEffect.java b/src/com/android/launcher3/util/OverlayEdgeEffect.java
index d09d801..0623af7 100644
--- a/src/com/android/launcher3/util/OverlayEdgeEffect.java
+++ b/src/com/android/launcher3/util/OverlayEdgeEffect.java
@@ -46,6 +46,7 @@
return mDistance;
}
+ @Override
public float onPullDistance(float deltaDistance, float displacement) {
// Fallback implementation, will never actually get called
if (BuildConfig.IS_DEBUG_DEVICE) {
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 469e363..e51609a 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -24,13 +24,10 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
-import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Process;
@@ -42,7 +39,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.launcher3.Flags;
import com.android.launcher3.PendingAddItemInfo;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -89,69 +85,6 @@
public void close() { }
/**
- * Returns true if the app can possibly be on the SDCard. This is just a workaround and doesn't
- * guarantee that the app is on SD card.
- */
- public boolean isAppOnSdcard(@NonNull final String packageName,
- @NonNull final UserHandle user) {
- final ApplicationInfo info = getApplicationInfo(
- packageName, user, PackageManager.MATCH_UNINSTALLED_PACKAGES);
- return info != null && (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
- }
-
- /**
- * Returns whether the target app is suspended for a given user as per
- * {@link android.app.admin.DevicePolicyManager#isPackageSuspended}.
- */
- public boolean isAppSuspended(@NonNull final String packageName,
- @NonNull final UserHandle user) {
- final ApplicationInfo info = getApplicationInfo(packageName, user, 0);
- return info != null && isAppSuspended(info);
- }
-
- /**
- * Returns whether the target app is installed for a given user
- */
- public boolean isAppInstalled(@NonNull final String packageName,
- @NonNull final UserHandle user) {
- final ApplicationInfo info = getApplicationInfo(packageName, user, 0);
- return info != null;
- }
-
- /**
- * Returns whether the target app is archived for a given user
- */
- @SuppressWarnings("NewApi")
- public boolean isAppArchivedForUser(@NonNull final String packageName,
- @NonNull final UserHandle user) {
- if (!Flags.enableSupportForArchiving()) {
- return false;
- }
- final ApplicationInfo info = getApplicationInfo(
- // LauncherApps does not support long flags currently. Since archived apps are
- // subset of uninstalled apps, this filter also includes archived apps.
- packageName, user, PackageManager.MATCH_UNINSTALLED_PACKAGES);
- return info != null && info.isArchived;
- }
-
- /**
- * Returns whether the target app is in archived state
- */
- @SuppressWarnings("NewApi")
- public boolean isAppArchived(@NonNull final String packageName) {
- final ApplicationInfo info;
- try {
- info = mPm.getPackageInfo(packageName,
- PackageManager.PackageInfoFlags.of(
- PackageManager.MATCH_ARCHIVED_PACKAGES)).applicationInfo;
- return info.isArchived;
- } catch (NameNotFoundException e) {
- Log.e(TAG, "Failed to get applicationInfo for package: " + packageName, e);
- return false;
- }
- }
-
- /**
* Returns the installing app package for the given package
*/
public String getAppInstallerPackage(@NonNull final String packageName) {
@@ -164,20 +97,6 @@
}
/**
- * Returns the application info for the provided package or null
- */
- @Nullable
- public ApplicationInfo getApplicationInfo(@NonNull final String packageName,
- @NonNull final UserHandle user, final int flags) {
- try {
- ApplicationInfo info = mLauncherApps.getApplicationInfo(packageName, flags, user);
- return !isPackageInstalledOrArchived(info) || !info.enabled ? null : info;
- } catch (PackageManager.NameNotFoundException e) {
- return null;
- }
- }
-
- /**
* Returns the preferred launch activity intent for a given package.
*/
@Nullable
@@ -197,14 +116,6 @@
}
/**
- * Returns whether an application is suspended as per
- * {@link android.app.admin.DevicePolicyManager#isPackageSuspended}.
- */
- public static boolean isAppSuspended(ApplicationInfo info) {
- return (info.flags & ApplicationInfo.FLAG_SUSPENDED) != 0;
- }
-
- /**
* Starts the details activity for {@code info}
*/
public static void startDetailsActivityForInfo(Context context, ItemInfo info,
@@ -236,35 +147,6 @@
}
}
- public static boolean isSystemApp(@NonNull final Context context,
- @NonNull final Intent intent) {
- PackageManager pm = context.getPackageManager();
- ComponentName cn = intent.getComponent();
- String packageName = null;
- if (cn == null) {
- ResolveInfo info = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
- if ((info != null) && (info.activityInfo != null)) {
- packageName = info.activityInfo.packageName;
- }
- } else {
- packageName = cn.getPackageName();
- }
- if (packageName == null) {
- packageName = intent.getPackage();
- }
- if (packageName != null) {
- try {
- PackageInfo info = pm.getPackageInfo(packageName, 0);
- return (info != null) && (info.applicationInfo != null) &&
- ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
- } catch (NameNotFoundException e) {
- return false;
- }
- } else {
- return false;
- }
- }
-
/**
* Returns true if the intent is a valid launch intent for a launcher activity of an app.
* This is used to identify shortcuts which are different from the ones exposed by the
@@ -303,17 +185,7 @@
/** Returns the incremental download progress for the given shortcut's app. */
public static int getLoadingProgress(LauncherActivityInfo info) {
- if (Utilities.ATLEAST_S) {
- return (int) (100 * info.getLoadingProgress());
- }
- return 100;
- }
-
- /** Returns true in case app is installed on the device or in archived state. */
- @SuppressWarnings("NewApi")
- private boolean isPackageInstalledOrArchived(ApplicationInfo info) {
- return (info.flags & ApplicationInfo.FLAG_INSTALLED) != 0 || (
- Flags.enableSupportForArchiving() && info.isArchived);
+ return (int) (100 * info.getLoadingProgress());
}
/**
diff --git a/src/com/android/launcher3/util/SystemUiController.java b/src/com/android/launcher3/util/SystemUiController.java
index df54fd7..368b267 100644
--- a/src/com/android/launcher3/util/SystemUiController.java
+++ b/src/com/android/launcher3/util/SystemUiController.java
@@ -17,7 +17,6 @@
package com.android.launcher3.util;
import android.view.View;
-import android.view.Window;
import androidx.annotation.IntDef;
@@ -54,11 +53,11 @@
})
public @interface SystemUiControllerFlags {}
- private final Window mWindow;
+ private final View mView;
private final int[] mStates = new int[5];
- public SystemUiController(Window window) {
- mWindow = window;
+ public SystemUiController(View view) {
+ mView = view;
}
public void updateUiState(int uiState, boolean isLight) {
@@ -72,14 +71,14 @@
}
mStates[uiState] = flags;
- int oldFlags = mWindow.getDecorView().getSystemUiVisibility();
+ int oldFlags = mView.getSystemUiVisibility();
// Apply the state flags in priority order
int newFlags = oldFlags;
for (int stateFlag : mStates) {
newFlags = getSysUiVisibilityFlags(stateFlag, newFlags);
}
if (newFlags != oldFlags) {
- mWindow.getDecorView().setSystemUiVisibility(newFlags);
+ mView.setSystemUiVisibility(newFlags);
}
}
@@ -88,7 +87,7 @@
*/
public int getBaseSysuiVisibility() {
return getSysUiVisibilityFlags(
- mStates[UI_STATE_BASE_WINDOW], mWindow.getDecorView().getSystemUiVisibility());
+ mStates[UI_STATE_BASE_WINDOW], mView.getSystemUiVisibility());
}
private int getSysUiVisibilityFlags(int stateFlag, int currentVisibility) {
diff --git a/src/com/android/launcher3/util/Themes.java b/src/com/android/launcher3/util/Themes.java
index 60951ba..104040a 100644
--- a/src/com/android/launcher3/util/Themes.java
+++ b/src/com/android/launcher3/util/Themes.java
@@ -52,10 +52,8 @@
}
public static int getActivityThemeRes(Context context, int wallpaperColorHints) {
- boolean supportsDarkText = Utilities.ATLEAST_S
- && (wallpaperColorHints & HINT_SUPPORTS_DARK_TEXT) != 0;
- boolean isMainColorDark = Utilities.ATLEAST_S
- && (wallpaperColorHints & HINT_SUPPORTS_DARK_THEME) != 0;
+ boolean supportsDarkText = (wallpaperColorHints & HINT_SUPPORTS_DARK_TEXT) != 0;
+ boolean isMainColorDark = (wallpaperColorHints & HINT_SUPPORTS_DARK_THEME) != 0;
if (Utilities.isDarkTheme(context)) {
return supportsDarkText ? R.style.AppTheme_Dark_DarkText
diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/src/com/android/launcher3/util/VibratorWrapper.java
index a4b8eb0..adb8f9d 100644
--- a/src/com/android/launcher3/util/VibratorWrapper.java
+++ b/src/com/android/launcher3/util/VibratorWrapper.java
@@ -31,8 +31,6 @@
import androidx.annotation.VisibleForTesting;
-import com.android.launcher3.Utilities;
-
/**
* Wrapper around {@link Vibrator} to easily perform haptic feedback where necessary.
*/
@@ -129,7 +127,7 @@
/** Indicates that Taskbar has been invoked. */
public void vibrateForTaskbarUnstash() {
- if (Utilities.ATLEAST_S && mVibrator.areAllPrimitivesSupported(PRIMITIVE_LOW_TICK)) {
+ if (mVibrator.areAllPrimitivesSupported(PRIMITIVE_LOW_TICK)) {
VibrationEffect primitiveLowTickEffect = VibrationEffect
.startComposition()
.addPrimitive(PRIMITIVE_LOW_TICK, LOW_TICK_SCALE)
diff --git a/src/com/android/launcher3/util/WallpaperColorHints.kt b/src/com/android/launcher3/util/WallpaperColorHints.kt
index 1361c1e..11d4c25 100644
--- a/src/com/android/launcher3/util/WallpaperColorHints.kt
+++ b/src/com/android/launcher3/util/WallpaperColorHints.kt
@@ -23,7 +23,6 @@
import android.content.Context
import androidx.annotation.MainThread
import androidx.annotation.VisibleForTesting
-import com.android.launcher3.Utilities
import com.android.launcher3.util.Executors.MAIN_EXECUTOR
import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
@@ -34,36 +33,34 @@
class WallpaperColorHints(private val context: Context) : SafeCloseable {
var hints: Int = 0
private set
+
private val wallpaperManager
get() = context.getSystemService(WallpaperManager::class.java)!!
+
private val onColorHintsChangedListeners = mutableListOf<OnColorHintListener>()
private val onClose: SafeCloseable
init {
- if (Utilities.ATLEAST_S) {
- hints = wallpaperManager.getWallpaperColors(FLAG_SYSTEM)?.colorHints ?: 0
- val onColorsChangedListener = OnColorsChangedListener { colors, which ->
- onColorsChanged(colors, which)
- }
+ hints = wallpaperManager.getWallpaperColors(FLAG_SYSTEM)?.colorHints ?: 0
+ val onColorsChangedListener = OnColorsChangedListener { colors, which ->
+ onColorsChanged(colors, which)
+ }
+ UI_HELPER_EXECUTOR.execute {
+ wallpaperManager.addOnColorsChangedListener(
+ onColorsChangedListener,
+ MAIN_EXECUTOR.handler,
+ )
+ }
+ onClose = SafeCloseable {
UI_HELPER_EXECUTOR.execute {
- wallpaperManager.addOnColorsChangedListener(
- onColorsChangedListener,
- MAIN_EXECUTOR.handler
- )
+ wallpaperManager.removeOnColorsChangedListener(onColorsChangedListener)
}
- onClose = SafeCloseable {
- UI_HELPER_EXECUTOR.execute {
- wallpaperManager.removeOnColorsChangedListener(onColorsChangedListener)
- }
- }
- } else {
- onClose = SafeCloseable {}
}
}
@MainThread
private fun onColorsChanged(colors: WallpaperColors?, which: Int) {
- if ((which and FLAG_SYSTEM) != 0 && Utilities.ATLEAST_S) {
+ if ((which and FLAG_SYSTEM) != 0) {
val newHints = colors?.colorHints ?: 0
if (newHints != hints) {
hints = newHints
@@ -86,6 +83,7 @@
@VisibleForTesting
@JvmField
val INSTANCE = MainThreadInitializedObject { WallpaperColorHints(it) }
+
@JvmStatic fun get(context: Context): WallpaperColorHints = INSTANCE.get(context)
}
}
diff --git a/src/com/android/launcher3/util/window/WindowManagerProxy.java b/src/com/android/launcher3/util/window/WindowManagerProxy.java
index 0817c0a..84b4a36 100644
--- a/src/com/android/launcher3/util/window/WindowManagerProxy.java
+++ b/src/com/android/launcher3/util/window/WindowManagerProxy.java
@@ -32,7 +32,6 @@
import static com.android.launcher3.util.RotationUtils.rotateRect;
import static com.android.launcher3.util.RotationUtils.rotateSize;
-import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -40,7 +39,6 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
-import android.os.Build;
import android.util.ArrayMap;
import android.util.Log;
import android.view.Display;
@@ -54,7 +52,6 @@
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
import com.android.launcher3.testing.shared.ResourceUtils;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.NavigationMode;
@@ -122,6 +119,20 @@
}
/**
+ * Returns if the pinned taskbar should be shown when home is visible.
+ */
+ public boolean showLockedTaskbarOnHome(Context displayInfoContext) {
+ return false;
+ }
+
+ /**
+ * Returns if the home is visible.
+ */
+ public boolean isHomeVisible(Context context) {
+ return false;
+ }
+
+ /**
* Returns the real bounds for the provided display after applying any insets normalization
*/
public WindowBounds getRealBounds(Context displayInfoContext, CachedDisplayInfo info) {
@@ -216,7 +227,7 @@
int screenWidthPx,
@NonNull WindowInsets windowInsets,
@NonNull WindowInsets.Builder insetsBuilder) {
- if (!isLargeScreen || !Utilities.ATLEAST_S) {
+ if (!isLargeScreen) {
return;
}
@@ -391,25 +402,16 @@
/**
* Returns a CachedDisplayInfo initialized for the current display
*/
- @TargetApi(Build.VERSION_CODES.S)
public CachedDisplayInfo getDisplayInfo(Context displayInfoContext) {
int rotation = getRotation(displayInfoContext);
- if (Utilities.ATLEAST_S) {
- WindowMetrics windowMetrics = displayInfoContext.getSystemService(WindowManager.class)
- .getMaximumWindowMetrics();
- return getDisplayInfo(windowMetrics, rotation);
- } else {
- Point size = new Point();
- Display display = getDisplay(displayInfoContext);
- display.getRealSize(size);
- return new CachedDisplayInfo(size, rotation);
- }
+ WindowMetrics windowMetrics = displayInfoContext.getSystemService(WindowManager.class)
+ .getMaximumWindowMetrics();
+ return getDisplayInfo(windowMetrics, rotation);
}
/**
* Returns a CachedDisplayInfo initialized for the current display
*/
- @TargetApi(Build.VERSION_CODES.S)
protected CachedDisplayInfo getDisplayInfo(WindowMetrics windowMetrics, int rotation) {
Point size = new Point(windowMetrics.getBounds().right, windowMetrics.getBounds().bottom);
return new CachedDisplayInfo(size, rotation,
@@ -478,8 +480,7 @@
}
}
}
- return Utilities.ATLEAST_S ? NavigationMode.NO_BUTTON :
- NavigationMode.THREE_BUTTONS;
+ return NavigationMode.NO_BUTTON;
}
@Override
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index d3160e0..b8481c5 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -26,9 +26,11 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import android.app.Activity;
import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
@@ -46,6 +48,7 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.AccessibilityDelegate;
+import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowInsetsController;
import android.view.inputmethod.InputMethodManager;
@@ -76,10 +79,11 @@
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.util.ActivityOptionsWrapper;
-import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.ApplicationInfoWrapper;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.SplitConfigurationOptions;
+import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.ViewCache;
import com.android.launcher3.widget.picker.model.WidgetPickerDataProvider;
@@ -175,6 +179,23 @@
BaseDragLayer getDragLayer();
/**
+ * @see Activity#getWindow()
+ * @return Window
+ */
+ @Nullable
+ default Window getWindow() {
+ return null;
+ }
+
+ /**
+ * @see Activity#getComponentName()
+ * @return ComponentName
+ */
+ default ComponentName getComponentName() {
+ return null;
+ }
+
+ /**
* The all apps container, if it exists in this context.
*/
default ActivityAllAppsContainerView<?> getAppsView() {
@@ -216,6 +237,11 @@
return null;
}
+ @Nullable
+ default SystemUiController getSystemUiController() {
+ return null;
+ }
+
/**
* Handler for actions taken on drop targets that require launcher
*/
@@ -391,7 +417,7 @@
View v, Intent intent, @Nullable ItemInfo item) {
Preconditions.assertUIThread();
Context context = (Context) this;
- if (isAppBlockedForSafeMode() && !PackageManagerHelper.isSystemApp(context, intent)) {
+ if (isAppBlockedForSafeMode() && !new ApplicationInfoWrapper(context, intent).isSystem()) {
Toast.makeText(context, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
return null;
}
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index 5d2d3f4..ea3fb3f 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -107,7 +107,7 @@
protected final RectF mSystemGestureRegion = new RectF();
private int mTouchDispatchState = 0;
- protected final T mActivity;
+ protected final T mContainer;
private final MultiValueAlpha mMultiValueAlpha;
// All the touch controllers for the view
@@ -121,7 +121,7 @@
public BaseDragLayer(Context context, AttributeSet attrs, int alphaChannelCount) {
super(context, attrs);
- mActivity = ActivityContext.lookupContext(context);
+ mContainer = ActivityContext.lookupContext(context);
mMultiValueAlpha = new MultiValueAlpha(this, alphaChannelCount);
}
@@ -159,7 +159,7 @@
}
mTouchCompleteListener = null;
} else if (action == MotionEvent.ACTION_DOWN) {
- mActivity.finishAutoCancelActionMode();
+ mContainer.finishAutoCancelActionMode();
}
return findActiveController(ev);
}
@@ -173,7 +173,7 @@
}
private TouchController findControllerToHandleTouch(MotionEvent ev) {
- AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
+ AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mContainer);
if (topView != null
&& (isEventWithinSystemGestureRegion(ev)
|| topView.canInterceptEventsInSystemGestureRegion())
@@ -207,7 +207,7 @@
@Override
public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
// Shortcuts can appear above folder
- View topView = AbstractFloatingView.getTopOpenViewWithType(mActivity,
+ View topView = AbstractFloatingView.getTopOpenViewWithType(mContainer,
AbstractFloatingView.TYPE_ACCESSIBLE);
if (topView != null) {
if (child == topView) {
@@ -222,7 +222,7 @@
@Override
public void addChildrenForAccessibility(ArrayList<View> childrenForAccessibility) {
- View topView = AbstractFloatingView.getTopOpenViewWithType(mActivity,
+ View topView = AbstractFloatingView.getTopOpenViewWithType(mContainer,
AbstractFloatingView.TYPE_ACCESSIBLE);
if (topView != null) {
// Only add the top view as a child for accessibility when it is open
@@ -458,7 +458,7 @@
@Override
protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
- View topView = AbstractFloatingView.getTopOpenView(mActivity);
+ View topView = AbstractFloatingView.getTopOpenView(mContainer);
if (topView != null) {
return topView.requestFocus(direction, previouslyFocusedRect);
} else {
@@ -468,7 +468,7 @@
@Override
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
- View topView = AbstractFloatingView.getTopOpenView(mActivity);
+ View topView = AbstractFloatingView.getTopOpenView(mContainer);
if (topView != null) {
topView.addFocusables(views, direction);
} else {
@@ -555,7 +555,7 @@
Insets gestureInsets = insets.getMandatorySystemGestureInsets();
int gestureInsetBottom = gestureInsets.bottom;
Insets imeInset = insets.getInsets(WindowInsets.Type.ime());
- DeviceProfile dp = mActivity.getDeviceProfile();
+ DeviceProfile dp = mContainer.getDeviceProfile();
if (dp.isTaskbarPresent) {
// Ignore taskbar gesture insets to avoid interfering with TouchControllers.
gestureInsetBottom = ResourceUtils.getNavbarSize(
diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java
index f6c4984..ce58de1 100644
--- a/src/com/android/launcher3/views/ScrimView.java
+++ b/src/com/android/launcher3/views/ScrimView.java
@@ -29,7 +29,6 @@
import androidx.annotation.Px;
import androidx.core.graphics.ColorUtils;
-import com.android.launcher3.BaseActivity;
import com.android.launcher3.Insettable;
import com.android.launcher3.util.SystemUiController;
@@ -143,7 +142,8 @@
private SystemUiController getSystemUiController() {
if (mSystemUiController == null) {
- mSystemUiController = BaseActivity.fromContext(getContext()).getSystemUiController();
+ mSystemUiController =
+ ActivityContext.lookupContext(getContext()).getSystemUiController();
}
return mSystemUiController;
}
diff --git a/src/com/android/launcher3/views/SpringRelativeLayout.java b/src/com/android/launcher3/views/SpringRelativeLayout.java
index 923eb19..a13152e 100644
--- a/src/com/android/launcher3/views/SpringRelativeLayout.java
+++ b/src/com/android/launcher3/views/SpringRelativeLayout.java
@@ -25,8 +25,6 @@
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory;
-import com.android.launcher3.Utilities;
-
/**
* View group to allow rendering overscroll effect in a child at the parent level
*/
@@ -46,10 +44,8 @@
public SpringRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- mEdgeGlowTop = Utilities.ATLEAST_S
- ? new EdgeEffect(context, attrs) : new EdgeEffect(context);
- mEdgeGlowBottom = Utilities.ATLEAST_S
- ? new EdgeEffect(context, attrs) : new EdgeEffect(context);
+ mEdgeGlowTop = new EdgeEffect(context, attrs);
+ mEdgeGlowBottom = new EdgeEffect(context, attrs);
setWillNotDraw(false);
}
diff --git a/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java
index 856f4b3..12a14c2 100644
--- a/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java
@@ -104,7 +104,7 @@
@UiThread
private void enforceRoundedCorners() {
- if (mEnforcedCornerRadius <= 0 || !RoundedCornerEnforcement.isRoundedCornerEnabled()) {
+ if (mEnforcedCornerRadius <= 0) {
resetRoundedCorners();
return;
}
diff --git a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
index 2817299..e100157 100644
--- a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
+++ b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
@@ -183,19 +183,14 @@
// Draw horizontal and vertical lines to represent individual columns.
final Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
+ boxRect = new RectF(/* left= */ 0, /* top= */ 0, /* right= */
+ previewWidthF, /* bottom= */ previewHeightF);
- if (Utilities.ATLEAST_S) {
- boxRect = new RectF(/* left= */ 0, /* top= */ 0, /* right= */
- previewWidthF, /* bottom= */ previewHeightF);
-
- p.setStyle(Paint.Style.FILL);
- p.setColor(Color.WHITE);
- float roundedCorner = mContext.getResources().getDimension(
- android.R.dimen.system_app_widget_background_radius);
- c.drawRoundRect(boxRect, roundedCorner, roundedCorner, p);
- } else {
- boxRect = drawBoxWithShadow(c, previewWidthF, previewHeightF);
- }
+ p.setStyle(Paint.Style.FILL);
+ p.setColor(Color.WHITE);
+ float roundedCorner = mContext.getResources().getDimension(
+ android.R.dimen.system_app_widget_background_radius);
+ c.drawRoundRect(boxRect, roundedCorner, roundedCorner, p);
p.setStyle(Paint.Style.STROKE);
p.setStrokeWidth(mContext.getResources()
@@ -218,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 3e4fd8c..1db3b5a 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
@@ -1,10 +1,9 @@
package com.android.launcher3.widget;
-import static com.android.launcher3.Utilities.ATLEAST_S;
-
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;
@@ -12,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;
@@ -116,15 +117,13 @@
getSpanY(widgetPadding, minResizeHeight, dp.cellLayoutBorderSpacePx.y,
cellSize.y));
- if (ATLEAST_S) {
- if (maxResizeWidth > 0) {
- maxSpanX = Math.min(maxSpanX, getSpanX(widgetPadding, maxResizeWidth,
- dp.cellLayoutBorderSpacePx.x, cellSize.x));
- }
- if (maxResizeHeight > 0) {
- maxSpanY = Math.min(maxSpanY, getSpanY(widgetPadding, maxResizeHeight,
- dp.cellLayoutBorderSpacePx.y, cellSize.y));
- }
+ if (maxResizeWidth > 0) {
+ maxSpanX = Math.min(maxSpanX, getSpanX(widgetPadding, maxResizeWidth,
+ dp.cellLayoutBorderSpacePx.x, cellSize.x));
+ }
+ if (maxResizeHeight > 0) {
+ maxSpanY = Math.min(maxSpanY, getSpanY(widgetPadding, maxResizeHeight,
+ dp.cellLayoutBorderSpacePx.y, cellSize.y));
}
spanX = Math.max(spanX,
@@ -135,18 +134,16 @@
cellSize.y));
}
- if (ATLEAST_S) {
- // Ensures maxSpan >= minSpan
- maxSpanX = Math.max(maxSpanX, minSpanX);
- maxSpanY = Math.max(maxSpanY, minSpanY);
+ // Ensures maxSpan >= minSpan
+ maxSpanX = Math.max(maxSpanX, minSpanX);
+ maxSpanY = Math.max(maxSpanY, minSpanY);
- // Use targetCellWidth/Height if it is within the min/max ranges.
- // Otherwise, use the span of minWidth/Height.
- if (targetCellWidth >= minSpanX && targetCellWidth <= maxSpanX
- && targetCellHeight >= minSpanY && targetCellHeight <= maxSpanY) {
- spanX = targetCellWidth;
- spanY = targetCellHeight;
- }
+ // Use targetCellWidth/Height if it is within the min/max ranges.
+ // Otherwise, use the span of minWidth/Height.
+ if (targetCellWidth >= minSpanX && targetCellWidth <= maxSpanX
+ && targetCellHeight >= minSpanY && targetCellHeight <= maxSpanY) {
+ spanX = targetCellWidth;
+ spanY = targetCellHeight;
}
// If minSpanX/Y > spanX/Y, ignore the minSpanX/Y to match the behavior described in
@@ -213,8 +210,7 @@
}
public boolean isConfigurationOptional() {
- return ATLEAST_S
- && isReconfigurable()
+ return isReconfigurable()
&& (getWidgetFeatures() & WIDGET_FEATURE_CONFIGURATION_OPTIONAL) != 0;
}
@@ -230,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/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java
index 8857774..130d533 100644
--- a/src/com/android/launcher3/widget/PendingItemDragHelper.java
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -136,9 +136,7 @@
Drawable p = new FastBitmapDrawable(new DatabaseWidgetPreviewLoader(launcher)
.generateWidgetPreview(
createWidgetInfo.info, maxWidth, previewSizeBeforeScale));
- if (RoundedCornerEnforcement.isRoundedCornerEnabled()) {
- p = new RoundDrawableWrapper(p, mEnforcedRoundedCornersForWidget);
- }
+ p = new RoundDrawableWrapper(p, mEnforcedRoundedCornersForWidget);
preview = p;
}
diff --git a/src/com/android/launcher3/widget/RoundedCornerEnforcement.java b/src/com/android/launcher3/widget/RoundedCornerEnforcement.java
index a2fac46..cadaf89 100644
--- a/src/com/android/launcher3/widget/RoundedCornerEnforcement.java
+++ b/src/com/android/launcher3/widget/RoundedCornerEnforcement.java
@@ -28,7 +28,6 @@
import androidx.annotation.Nullable;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
import java.util.ArrayList;
import java.util.List;
@@ -70,11 +69,6 @@
return background.getId() == android.R.id.background && background.getClipToOutline();
}
- /** Check if the app widget is in the deny list. */
- public static boolean isRoundedCornerEnabled() {
- return Utilities.ATLEAST_S;
- }
-
/**
* Computes the rounded rectangle needed for this app widget.
*
@@ -101,9 +95,6 @@
* in the given context.
*/
public static float computeEnforcedRadius(@NonNull Context context) {
- if (!Utilities.ATLEAST_S) {
- return 0;
- }
Resources res = context.getResources();
float systemRadius = res.getDimension(android.R.dimen.system_app_widget_background_radius);
float defaultRadius = res.getDimension(R.dimen.enforced_rounded_corner_max_radius);
diff --git a/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProvider.java b/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProvider.java
index 9253b37..f8dc6b0 100644
--- a/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProvider.java
+++ b/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProvider.java
@@ -24,7 +24,7 @@
import com.android.launcher3.R;
import com.android.launcher3.model.WidgetItem;
-import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.ApplicationInfoWrapper;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.ResourceBasedOverride;
@@ -62,14 +62,14 @@
// via the overridden WidgetRecommendationCategoryProvider resource.
Preconditions.assertWorkerThread();
- try (PackageManagerHelper pmHelper = new PackageManagerHelper(context)) {
- if (item.widgetInfo != null && item.widgetInfo.getComponent() != null) {
- ApplicationInfo applicationInfo = pmHelper.getApplicationInfo(
- item.widgetInfo.getComponent().getPackageName(), item.widgetInfo.getUser(),
- 0 /* flags */);
- if (applicationInfo != null) {
- return getCategoryFromApplicationCategory(applicationInfo.category);
- }
+ if (item.widgetInfo != null && item.widgetInfo.getComponent() != null) {
+ ApplicationInfo applicationInfo = new ApplicationInfoWrapper(
+ context,
+ item.widgetInfo.getComponent().getPackageName(),
+ item.widgetInfo.getUser())
+ .getInfo();
+ if (applicationInfo != null) {
+ return getCategoryFromApplicationCategory(applicationInfo.category);
}
}
return null;
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/GridSizeMigrationUtilTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt
index 761f06d..f57e8a1 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt
@@ -45,7 +45,6 @@
private lateinit var modelHelper: LauncherModelHelper
private lateinit var context: Context
- private lateinit var validPackages: Set<String>
private lateinit var idp: InvariantDeviceProfile
private lateinit var dbHelper: DatabaseHelper
private lateinit var db: SQLiteDatabase
@@ -68,24 +67,10 @@
DatabaseHelper(
context,
null,
- UserCache.INSTANCE.get(context)::getSerialNumberForUser
+ UserCache.INSTANCE.get(context)::getSerialNumberForUser,
) {}
db = dbHelper.writableDatabase
- validPackages =
- setOf(
- testPackage1,
- testPackage2,
- testPackage3,
- testPackage4,
- testPackage5,
- testPackage6,
- testPackage7,
- testPackage8,
- testPackage9,
- testPackage10
- )
-
idp = InvariantDeviceProfile.INSTANCE[context]
val userSerial = UserCache.INSTANCE[context].getSerialNumberForUser(Process.myUserHandle())
LauncherDbUtils.dropTable(db, TMP_TABLE)
@@ -126,8 +111,8 @@
idp.numDatabaseHotseatIcons = 4
idp.numColumns = 4
idp.numRows = 4
- val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
- val destReader = DbReader(db, TABLE_NAME, context, validPackages)
+ val srcReader = DbReader(db, TMP_TABLE, context)
+ val destReader = DbReader(db, TABLE_NAME, context)
GridSizeMigrationUtil.migrate(
dbHelper,
srcReader,
@@ -135,7 +120,7 @@
idp.numDatabaseHotseatIcons,
Point(idp.numColumns, idp.numRows),
DeviceGridState(context),
- DeviceGridState(idp)
+ DeviceGridState(idp),
)
// Check hotseat items
@@ -147,9 +132,8 @@
null,
SCREEN,
null,
- null
- )
- ?: throw IllegalStateException()
+ null,
+ ) ?: throw IllegalStateException()
assertThat(c.count).isEqualTo(idp.numDatabaseHotseatIcons)
@@ -178,9 +162,8 @@
null,
null,
null,
- null
- )
- ?: throw IllegalStateException()
+ null,
+ ) ?: throw IllegalStateException()
intentIndex = c.getColumnIndex(INTENT)
val cellXIndex = c.getColumnIndex(CELLX)
@@ -238,8 +221,8 @@
idp.numDatabaseHotseatIcons = 4
idp.numColumns = 4
idp.numRows = 4
- val readerGridA = DbReader(db, TMP_TABLE, context, validPackages)
- val readerGridB = DbReader(db, TABLE_NAME, context, validPackages)
+ val readerGridA = DbReader(db, TMP_TABLE, context)
+ val readerGridB = DbReader(db, TABLE_NAME, context)
// migrate from A -> B
GridSizeMigrationUtil.migrate(
dbHelper,
@@ -248,7 +231,7 @@
idp.numDatabaseHotseatIcons,
Point(idp.numColumns, idp.numRows),
DeviceGridState(context),
- DeviceGridState(idp)
+ DeviceGridState(idp),
)
// Check hotseat items in grid B
@@ -260,15 +243,14 @@
null,
SCREEN,
null,
- null
- )
- ?: throw IllegalStateException()
+ null,
+ ) ?: throw IllegalStateException()
// Expected hotseat items in grid B
// 2 1 3 4
verifyHotseat(
c,
idp,
- mutableListOf(testPackage2, testPackage1, testPackage3, testPackage4).toList()
+ mutableListOf(testPackage2, testPackage1, testPackage3, testPackage4).toList(),
)
// Check workspace items in grid B
@@ -280,9 +262,8 @@
null,
null,
null,
- null
- )
- ?: throw IllegalStateException()
+ null,
+ ) ?: throw IllegalStateException()
var locMap = parseLocMap(c)
// Expected items in grid B
// _ _ _ _
@@ -306,7 +287,7 @@
5,
Point(5, 5),
DeviceGridState(idp),
- DeviceGridState(context)
+ DeviceGridState(context),
)
// Check hotseat items in grid A
c =
@@ -317,15 +298,14 @@
null,
SCREEN,
null,
- null
- )
- ?: throw IllegalStateException()
+ null,
+ ) ?: throw IllegalStateException()
// Expected hotseat items in grid A
// 1 2 _ 3 4
verifyHotseat(
c,
idp,
- mutableListOf(testPackage1, testPackage2, null, testPackage3, testPackage4).toList()
+ mutableListOf(testPackage1, testPackage2, null, testPackage3, testPackage4).toList(),
)
// Check workspace items in grid A
@@ -337,9 +317,8 @@
null,
null,
null,
- null
- )
- ?: throw IllegalStateException()
+ null,
+ ) ?: throw IllegalStateException()
locMap = parseLocMap(c)
// Expected workspace items in grid A
// _ _ _ _ _
@@ -367,7 +346,7 @@
idp.numDatabaseHotseatIcons,
Point(idp.numColumns, idp.numRows),
DeviceGridState(context),
- DeviceGridState(idp)
+ DeviceGridState(idp),
)
// Check hotseat items in grid B
@@ -379,15 +358,14 @@
null,
SCREEN,
null,
- null
- )
- ?: throw IllegalStateException()
+ null,
+ ) ?: throw IllegalStateException()
// Expected hotseat items in grid B
// 2 1 3 4
verifyHotseat(
c,
idp,
- mutableListOf(testPackage2, testPackage1, testPackage3, testPackage4).toList()
+ mutableListOf(testPackage2, testPackage1, testPackage3, testPackage4).toList(),
)
// Check workspace items in grid B
@@ -399,9 +377,8 @@
null,
null,
null,
- null
- )
- ?: throw IllegalStateException()
+ null,
+ ) ?: throw IllegalStateException()
locMap = parseLocMap(c)
// Expected workspace items in grid B
// _ _ _ _
@@ -455,7 +432,7 @@
0,
testPackage1,
1,
- TMP_TABLE
+ TMP_TABLE,
),
addItem(
ITEM_TYPE_DEEP_SHORTCUT,
@@ -465,7 +442,7 @@
0,
testPackage2,
2,
- TMP_TABLE
+ TMP_TABLE,
),
addItem(
ITEM_TYPE_APPLICATION,
@@ -475,7 +452,7 @@
0,
testPackage3,
3,
- TMP_TABLE
+ TMP_TABLE,
),
addItem(
ITEM_TYPE_DEEP_SHORTCUT,
@@ -485,15 +462,15 @@
0,
testPackage4,
4,
- TMP_TABLE
- )
+ TMP_TABLE,
+ ),
)
val numSrcDatabaseHotseatIcons = srcHotseatItems.size
idp.numDatabaseHotseatIcons = 6
idp.numColumns = 4
idp.numRows = 4
- val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
- val destReader = DbReader(db, TABLE_NAME, context, validPackages)
+ val srcReader = DbReader(db, TMP_TABLE, context)
+ val destReader = DbReader(db, TABLE_NAME, context)
GridSizeMigrationUtil.migrate(
dbHelper,
srcReader,
@@ -501,7 +478,7 @@
idp.numDatabaseHotseatIcons,
Point(idp.numColumns, idp.numRows),
DeviceGridState(context),
- DeviceGridState(idp)
+ DeviceGridState(idp),
)
// Check hotseat items
@@ -513,9 +490,8 @@
null,
SCREEN,
null,
- null
- )
- ?: throw IllegalStateException()
+ null,
+ ) ?: throw IllegalStateException()
assertThat(c.count.toLong()).isEqualTo(numSrcDatabaseHotseatIcons.toLong())
val screenIndex = c.getColumnIndex(SCREEN)
@@ -550,8 +526,8 @@
idp.numDatabaseHotseatIcons = 4
idp.numColumns = 4
idp.numRows = 4
- val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
- val destReader = DbReader(db, TABLE_NAME, context, validPackages)
+ val srcReader = DbReader(db, TMP_TABLE, context)
+ val destReader = DbReader(db, TABLE_NAME, context)
GridSizeMigrationUtil.migrate(
dbHelper,
srcReader,
@@ -559,7 +535,7 @@
idp.numDatabaseHotseatIcons,
Point(idp.numColumns, idp.numRows),
DeviceGridState(context),
- DeviceGridState(idp)
+ DeviceGridState(idp),
)
// Check hotseat items
@@ -571,9 +547,8 @@
null,
SCREEN,
null,
- null
- )
- ?: throw IllegalStateException()
+ null,
+ ) ?: throw IllegalStateException()
assertThat(c.count.toLong()).isEqualTo(idp.numDatabaseHotseatIcons.toLong())
val screenIndex = c.getColumnIndex(SCREEN)
@@ -617,8 +592,8 @@
idp.numDatabaseHotseatIcons = 4
idp.numColumns = 5
idp.numRows = 5
- val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
- val destReader = DbReader(db, TABLE_NAME, context, validPackages)
+ val srcReader = DbReader(db, TMP_TABLE, context)
+ val destReader = DbReader(db, TABLE_NAME, context)
GridSizeMigrationUtil.migrate(
dbHelper,
srcReader,
@@ -626,7 +601,7 @@
idp.numDatabaseHotseatIcons,
Point(idp.numColumns, idp.numRows),
DeviceGridState(context),
- DeviceGridState(idp)
+ DeviceGridState(idp),
)
// Get workspace items
@@ -638,9 +613,8 @@
null,
null,
null,
- null
- )
- ?: throw IllegalStateException()
+ null,
+ ) ?: throw IllegalStateException()
val intentIndex = c.getColumnIndex(INTENT)
val screenIndex = c.getColumnIndex(SCREEN)
@@ -678,8 +652,8 @@
idp.numDatabaseHotseatIcons = 4
idp.numColumns = 4
idp.numRows = 4
- val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
- val destReader = DbReader(db, TABLE_NAME, context, validPackages)
+ val srcReader = DbReader(db, TMP_TABLE, context)
+ val destReader = DbReader(db, TABLE_NAME, context)
GridSizeMigrationUtil.migrate(
dbHelper,
srcReader,
@@ -687,7 +661,7 @@
idp.numDatabaseHotseatIcons,
Point(idp.numColumns, idp.numRows),
DeviceGridState(context),
- DeviceGridState(idp)
+ DeviceGridState(idp),
)
// Get workspace items
@@ -699,9 +673,8 @@
null,
null,
null,
- null
- )
- ?: throw IllegalStateException()
+ null,
+ ) ?: throw IllegalStateException()
val intentIndex = c.getColumnIndex(INTENT)
val screenIndex = c.getColumnIndex(SCREEN)
@@ -732,7 +705,7 @@
container: Int,
x: Int,
y: Int,
- packageName: String?
+ packageName: String?,
): Int {
return addItem(
type,
@@ -742,7 +715,7 @@
y,
packageName,
dbHelper.generateNewItemId(),
- TABLE_NAME
+ TABLE_NAME,
)
}
@@ -754,7 +727,7 @@
y: Int,
packageName: String?,
id: Int,
- tableName: String
+ tableName: String,
): Int {
val values = ContentValues()
values.put(_ID, id)
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
index 1d9c161..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()
@@ -118,11 +119,17 @@
`package` = "pkg"
putExtra(ShortcutKey.EXTRA_SHORTCUT_ID, "")
}
+ mockLauncherApps =
+ mock<LauncherApps>().apply {
+ whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true)
+ whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(true)
+ }
mockContext =
mock<Context>().apply {
whenever(packageManager).thenReturn(mock())
whenever(packageManager.getUserBadgedLabel(any(), any())).thenReturn("")
whenever(applicationContext).thenReturn(ApplicationProvider.getApplicationContext())
+ whenever(getSystemService(LauncherApps::class.java)).thenReturn(mockLauncherApps)
}
mockAppState =
mock<LauncherAppState>().apply {
@@ -135,11 +142,6 @@
whenever(getAppLaunchIntent(mComponentName.packageName, mUserHandle))
.thenReturn(intent)
}
- mockLauncherApps =
- mock<LauncherApps>().apply {
- whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true)
- whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(true)
- }
mockCursor =
mock(LoaderCursor::class.java, RETURNS_DEEP_STUBS).apply {
user = mUserHandle
@@ -193,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,
@@ -212,7 +214,7 @@
isSdCardReady = isSdCardReady,
shortcutKeyToPinnedShortcuts = shortcutKeyToPinnedShortcuts,
installingPkgs = installingPkgs,
- allDeepShortcuts = allDeepShortcuts
+ allDeepShortcuts = allDeepShortcuts,
)
@Test
@@ -351,7 +353,7 @@
" targetPkg=package," +
" component=ComponentInfo{package/class}." +
" Unable to create launch Intent.",
- MISSING_INFO
+ MISSING_INFO,
)
verify(mockCursor, times(0)).checkAndAddItem(any(), any(), anyOrNull())
}
@@ -386,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())
}
@@ -412,7 +415,7 @@
verify(mockCursor)
.markDeleted(
"Pinned shortcut not found from request. package=pkg, user=UserHandle{0}",
- "shortcut_not_found"
+ "shortcut_not_found",
)
}
@@ -451,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())
}
@@ -549,7 +553,7 @@
val inflationResult =
WidgetInflater.InflationResult(
type = WidgetInflater.TYPE_REAL,
- widgetInfo = expectedWidgetProviderInfo
+ widgetInfo = expectedWidgetProviderInfo,
)
mockWidgetInflater =
mock<WidgetInflater>().apply {
@@ -607,7 +611,7 @@
val inflationResult =
WidgetInflater.InflationResult(
type = WidgetInflater.TYPE_PENDING,
- widgetInfo = mockProviderInfo
+ widgetInfo = mockProviderInfo,
)
mockWidgetInflater =
mock<WidgetInflater>().apply {
@@ -662,7 +666,7 @@
verify(mockCursor)
.markDeleted(
"processWidget: Unrestored Pending widget removed: id=1, appWidgetId=0, component=$expectedComponentName, restoreFlag:=4",
- LauncherRestoreEventLogger.RestoreError.APP_NOT_INSTALLED
+ LauncherRestoreEventLogger.RestoreError.APP_NOT_INSTALLED,
)
}
@@ -670,12 +674,6 @@
fun `When widget inflation result is TYPE_DELETE then mark deleted`() {
// Given
val expectedProvider = "com.google.android.testApp/com.android.testApp.testAppProvider"
- val expectedComponentName = ComponentName.unflattenFromString(expectedProvider)
- val expectedPackage = expectedComponentName!!.packageName
- mockPmHelper =
- mock<PackageManagerHelper>().apply {
- whenever(isAppArchived(expectedPackage)).thenReturn(true)
- }
mockCursor =
mock<LoaderCursor>().apply {
itemType = ITEM_TYPE_APPWIDGET
@@ -694,7 +692,7 @@
type = WidgetInflater.TYPE_DELETE,
widgetInfo = null,
reason = "test_delete_reason",
- restoreErrorType = MISSING_WIDGET_PROVIDER
+ restoreErrorType = MISSING_WIDGET_PROVIDER,
)
mockWidgetInflater =
mock<WidgetInflater>().apply {
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/ApplicationInfoWrapperTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/ApplicationInfoWrapperTest.kt
new file mode 100644
index 0000000..86c3fd8
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/ApplicationInfoWrapperTest.kt
@@ -0,0 +1,131 @@
+/*
+ * 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.util
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.ApplicationInfo.FLAG_EXTERNAL_STORAGE
+import android.content.pm.ApplicationInfo.FLAG_INSTALLED
+import android.content.pm.ApplicationInfo.FLAG_SUSPENDED
+import android.content.pm.ApplicationInfo.FLAG_SYSTEM
+import android.content.pm.LauncherApps
+import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.whenever
+
+/** Unit tests for {@link ApplicationInfoWrapper}. */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ApplicationInfoWrapperTest {
+
+ @get:Rule val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT)
+
+ private lateinit var context: Context
+ private lateinit var launcherApps: LauncherApps
+
+ @Before
+ fun setup() {
+ context = Mockito.mock(Context::class.java)
+ launcherApps = Mockito.mock(LauncherApps::class.java)
+ whenever(context.getSystemService(eq(LauncherApps::class.java))).thenReturn(launcherApps)
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_SUPPORT_FOR_ARCHIVING)
+ fun archivedApp_appInfoIsNotNull() {
+ val applicationInfo = ApplicationInfo()
+ applicationInfo.isArchived = true
+ whenever(launcherApps.getApplicationInfo(eq(TEST_PACKAGE), any(), eq(TEST_USER)))
+ .thenReturn(applicationInfo)
+
+ val wrapper = ApplicationInfoWrapper(context, TEST_PACKAGE, TEST_USER)
+ assertNotNull(wrapper.getInfo())
+ assertTrue(wrapper.isArchived())
+ assertFalse(wrapper.isInstalled())
+ }
+
+ @Test
+ fun notInstalledApp_nullAppInfo() {
+ val applicationInfo = ApplicationInfo()
+ whenever(launcherApps.getApplicationInfo(eq(TEST_PACKAGE), any(), eq(TEST_USER)))
+ .thenReturn(applicationInfo)
+
+ val wrapper = ApplicationInfoWrapper(context, TEST_PACKAGE, TEST_USER)
+ assertNull(wrapper.getInfo())
+ assertFalse(wrapper.isInstalled())
+ }
+
+ @Test
+ fun appInfo_suspended() {
+ val wrapper =
+ ApplicationInfoWrapper(
+ ApplicationInfo().apply { flags = FLAG_INSTALLED.or(FLAG_SUSPENDED) }
+ )
+ assertTrue(wrapper.isSuspended())
+ }
+
+ @Test
+ fun appInfo_notSuspended() {
+ val wrapper = ApplicationInfoWrapper(ApplicationInfo())
+ assertFalse(wrapper.isSuspended())
+ }
+
+ @Test
+ fun appInfo_system() {
+ val wrapper = ApplicationInfoWrapper(ApplicationInfo().apply { flags = FLAG_SYSTEM })
+ assertTrue(wrapper.isSystem())
+ }
+
+ @Test
+ fun appInfo_notSystem() {
+ val wrapper = ApplicationInfoWrapper(ApplicationInfo())
+ assertFalse(wrapper.isSystem())
+ }
+
+ @Test
+ fun appInfo_onSDCard() {
+ val wrapper =
+ ApplicationInfoWrapper(ApplicationInfo().apply { flags = FLAG_EXTERNAL_STORAGE })
+ assertTrue(wrapper.isOnSdCard())
+ }
+
+ @Test
+ fun appInfo_notOnSDCard() {
+ val wrapper = ApplicationInfoWrapper(ApplicationInfo())
+ assertFalse(wrapper.isOnSdCard())
+ }
+
+ companion object {
+ const val TEST_PACKAGE = "com.android.test.package"
+ private val TEST_USER = UserHandle.of(3)
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt
index 41effa2..308f200 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt
@@ -38,7 +38,10 @@
import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
import com.android.launcher3.util.window.CachedDisplayInfo
import com.android.launcher3.util.window.WindowManagerProxy
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
import kotlin.math.min
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -79,7 +82,7 @@
WindowBounds(Rect(0, 0, width, height), Rect(0, inset, 0, 0), Surface.ROTATION_0),
WindowBounds(Rect(0, 0, height, width), Rect(0, inset, 0, 0), Surface.ROTATION_90),
WindowBounds(Rect(0, 0, width, height), Rect(0, inset, 0, 0), Surface.ROTATION_180),
- WindowBounds(Rect(0, 0, height, width), Rect(0, inset, 0, 0), Surface.ROTATION_270)
+ WindowBounds(Rect(0, 0, height, width), Rect(0, inset, 0, 0), Surface.ROTATION_270),
)
private val configuration =
Configuration(appContext.resources.configuration).apply {
@@ -111,6 +114,7 @@
whenever(windowManagerProxy.getRealBounds(any(), any())).thenAnswer { i ->
bounds[i.getArgument<CachedDisplayInfo>(1).rotation]
}
+ whenever(windowManagerProxy.showLockedTaskbarOnHome(any())).thenReturn(false)
whenever(windowManagerProxy.getNavigationMode(any())).thenReturn(NavigationMode.NO_BUTTON)
// Mock context
@@ -134,6 +138,13 @@
displayController.addChangeListener(displayInfoChangeListener)
}
+ @After
+ fun tearDown() {
+ // We need to reset the taskbar mode preference override even if a test throws an exception.
+ // Otherwise, it may break the following tests' assumptions.
+ DisplayController.enableTaskbarModePreferenceForTests(false)
+ }
+
@Test
@UiThreadTest
fun testRotation() {
@@ -167,7 +178,7 @@
@UiThreadTest
fun testTaskbarPinning() {
whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(true)
- displayController.handleInfoChange(display)
+ displayController.notifyConfigChange()
verify(displayInfoChangeListener)
.onDisplayInfoChanged(any(), any(), eq(CHANGE_TASKBAR_PINNING))
}
@@ -176,8 +187,24 @@
@UiThreadTest
fun testTaskbarPinningChangeInDesktopMode() {
whenever(launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)).thenReturn(false)
- displayController.handleInfoChange(display)
+ displayController.notifyConfigChange()
verify(displayInfoChangeListener)
.onDisplayInfoChanged(any(), any(), eq(CHANGE_TASKBAR_PINNING))
}
+
+ @Test
+ @UiThreadTest
+ fun testTaskbarPinningChangeInLockedTaskbarChange() {
+ whenever(windowManagerProxy.showLockedTaskbarOnHome(any())).thenReturn(true)
+ whenever(windowManagerProxy.isHomeVisible(any())).thenReturn(true)
+ whenever(windowManagerProxy.isInDesktopMode()).thenReturn(false)
+ whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(false)
+ DisplayController.enableTaskbarModePreferenceForTests(true)
+
+ assertTrue(displayController.getInfo().isTransientTaskbar())
+ displayController.notifyConfigChange()
+ verify(displayInfoChangeListener)
+ .onDisplayInfoChanged(any(), any(), eq(CHANGE_TASKBAR_PINNING))
+ assertFalse(displayController.getInfo().isTransientTaskbar())
+ }
}
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/PackageManagerHelperTest.java b/tests/multivalentTests/src/com/android/launcher3/util/PackageManagerHelperTest.java
deleted file mode 100644
index b5e797e..0000000
--- a/tests/multivalentTests/src/com/android/launcher3/util/PackageManagerHelperTest.java
+++ /dev/null
@@ -1,86 +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.util;
-
-import static com.android.launcher3.Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.LauncherApps;
-import android.content.pm.PackageManager;
-import android.os.UserHandle;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.runner.RunWith;
-
-/** Unit tests for {@link PackageManagerHelper}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public final class PackageManagerHelperTest {
- @Rule
- public ExpectedException exception = ExpectedException.none();
-
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
- private static final String TEST_PACKAGE = "com.android.test.package";
- private static final int TEST_USER = 2;
-
- private Context mContext;
- private LauncherApps mLauncherApps;
- private PackageManagerHelper mPackageManagerHelper;
-
- @Before
- public void setup() {
- mContext = mock(Context.class);
- mLauncherApps = mock(LauncherApps.class);
- when(mContext.getSystemService(eq(LauncherApps.class))).thenReturn(mLauncherApps);
- when(mContext.getResources()).thenReturn(
- InstrumentationRegistry.getInstrumentation().getTargetContext().getResources());
- mPackageManagerHelper = new PackageManagerHelper(mContext);
- }
-
- @Test
- @RequiresFlagsEnabled(FLAG_ENABLE_SUPPORT_FOR_ARCHIVING)
- public void getApplicationInfo_archivedApp_appInfoIsNotNull()
- throws PackageManager.NameNotFoundException {
- ApplicationInfo applicationInfo = new ApplicationInfo();
- applicationInfo.isArchived = true;
- when(mLauncherApps.getApplicationInfo(TEST_PACKAGE, 0 /* flags */,
- UserHandle.of(TEST_USER)))
- .thenReturn(applicationInfo);
-
- assertThat(mPackageManagerHelper.getApplicationInfo(TEST_PACKAGE, UserHandle.of(TEST_USER),
- 0 /* flags */))
- .isNotNull();
- }
-}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/SystemUiControllerTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/SystemUiControllerTest.kt
index 612fcd4..043bdac 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/SystemUiControllerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/SystemUiControllerTest.kt
@@ -52,7 +52,7 @@
fun setup() {
MockitoAnnotations.initMocks(this)
`when`(window.decorView).thenReturn(decorView)
- underTest = SystemUiController(window)
+ underTest = SystemUiController(window.decorView)
}
@Test
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/WidgetRecommendationCategoryProviderTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java
index 3024d26..8b6553f 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java
@@ -30,14 +30,16 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
-import android.content.ContextWrapper;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
import android.os.Process;
@@ -102,13 +104,8 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mContext = new ContextWrapper(getInstrumentation().getTargetContext()) {
- @Override
- public Object getSystemService(String name) {
- return LAUNCHER_APPS_SERVICE.equals(name) ? mLauncherApps : super.getSystemService(
- name);
- }
- };
+ mContext = spy(getInstrumentation().getTargetContext());
+ doReturn(mLauncherApps).when(mContext).getSystemService(LauncherApps.class);
mTestAppInfo.flags = FLAG_INSTALLED;
mTestProfile = new InvariantDeviceProfile();
mTestProfile.numRows = 5;
@@ -132,7 +129,7 @@
mTestAppInfo.category = testCategory.getKey();
when(mLauncherApps.getApplicationInfo(/*packageName=*/ eq(TEST_PACKAGE),
- /*flags=*/ eq(0),
+ /*flags=*/ anyInt(),
/*user=*/ eq(Process.myUserHandle())))
.thenReturn(mTestAppInfo);
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 b93c305..d553f47 100644
--- a/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt
+++ b/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt
@@ -20,6 +20,7 @@
import android.content.ComponentName
import android.content.Context
import android.content.Intent
+import android.content.pm.ApplicationInfo
import android.content.pm.LauncherApps
import android.content.pm.PackageInstaller
import android.content.pm.ShortcutInfo
@@ -28,14 +29,13 @@
import android.util.LongSparseArray
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.launcher3.Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING
import com.android.launcher3.LauncherAppState
import com.android.launcher3.LauncherSettings.Favorites
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.Utilities
+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
@@ -48,7 +48,6 @@
import com.android.launcher3.util.PackageUserKey
import com.android.launcher3.util.UserIconInfo
import com.android.launcher3.widget.WidgetInflater
-import com.android.launcher3.widget.WidgetSections
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -63,7 +62,6 @@
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
-import org.mockito.quality.Strictness
@RunWith(AndroidJUnit4::class)
class WorkspaceItemProcessorExtraTest {
@@ -87,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()
@@ -108,11 +106,17 @@
`package` = "pkg"
putExtra(ShortcutKey.EXTRA_SHORTCUT_ID, "")
}
+ mockLauncherApps =
+ mock<LauncherApps>().apply {
+ whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true)
+ whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(true)
+ }
mockContext =
mock<Context>().apply {
whenever(packageManager).thenReturn(mock())
whenever(packageManager.getUserBadgedLabel(any(), any())).thenReturn("")
whenever(applicationContext).thenReturn(ApplicationProvider.getApplicationContext())
+ whenever(getSystemService(LauncherApps::class.java)).thenReturn(mockLauncherApps)
}
mockAppState =
mock<LauncherAppState>().apply {
@@ -125,11 +129,6 @@
whenever(getAppLaunchIntent(mComponentName.packageName, mUserHandle))
.thenReturn(intent)
}
- mockLauncherApps =
- mock<LauncherApps>().apply {
- whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true)
- whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(true)
- }
mockCursor =
Mockito.mock(LoaderCursor::class.java, RETURNS_DEEP_STUBS).apply {
user = mUserHandle
@@ -163,138 +162,116 @@
@Test
fun `When Pending App Widget has not started restore then update db and add item`() {
-
- val mockitoSession =
- ExtendedMockito.mockitoSession()
- .strictness(Strictness.LENIENT)
- .mockStatic(WidgetSections::class.java)
- .startMocking()
- try {
- // Given
- val expectedProvider = "com.google.android.testApp/com.android.testApp.testAppProvider"
- val expectedComponentName =
- ComponentName.unflattenFromString(expectedProvider)!!.flattenToString()
- val expectedRestoreStatus = FLAG_UI_NOT_READY or FLAG_RESTORE_STARTED
- val expectedAppWidgetId = 0
- mockCursor.apply {
- itemType = ITEM_TYPE_APPWIDGET
- user = mUserHandle
- restoreFlag = FLAG_UI_NOT_READY
- container = CONTAINER_DESKTOP
- whenever(isOnWorkspaceOrHotseat).thenCallRealMethod()
- whenever(appWidgetProvider).thenReturn(expectedProvider)
- whenever(appWidgetId).thenReturn(expectedAppWidgetId)
- whenever(spanX).thenReturn(2)
- whenever(spanY).thenReturn(1)
- whenever(options).thenReturn(0)
- whenever(appWidgetSource).thenReturn(20)
- whenever(applyCommonProperties(any())).thenCallRealMethod()
- whenever(
- updater()
- .put(Favorites.APPWIDGET_PROVIDER, expectedComponentName)
- .put(Favorites.APPWIDGET_ID, expectedAppWidgetId)
- .put(Favorites.RESTORED, expectedRestoreStatus)
- .commit()
- )
- .thenReturn(1)
- }
- val inflationResult =
- WidgetInflater.InflationResult(
- type = WidgetInflater.TYPE_PENDING,
- widgetInfo = null
- )
- mockWidgetInflater =
- mock<WidgetInflater>().apply {
- whenever(inflateAppWidget(any())).thenReturn(inflationResult)
- }
- val packageUserKey = PackageUserKey("com.google.android.testApp", mUserHandle)
- mInstallingPkgs[packageUserKey] = PackageInstaller.SessionInfo()
-
- // When
- itemProcessorUnderTest =
- createWorkspaceItemProcessorUnderTest(widgetProvidersMap = mWidgetProvidersMap)
- itemProcessorUnderTest.processItem()
-
- // Then
- val expectedWidgetInfo =
- LauncherAppWidgetInfo().apply {
- appWidgetId = expectedAppWidgetId
- providerName = ComponentName.unflattenFromString(expectedProvider)
- restoreStatus = expectedRestoreStatus
- }
- verify(
- mockCursor
- .updater()
- .put(Favorites.APPWIDGET_PROVIDER, expectedProvider)
+ // Given
+ val expectedProvider = "com.google.android.testApp/com.android.testApp.testAppProvider"
+ val expectedComponentName =
+ ComponentName.unflattenFromString(expectedProvider)!!.flattenToString()
+ val expectedRestoreStatus = FLAG_UI_NOT_READY or FLAG_RESTORE_STARTED
+ val expectedAppWidgetId = 0
+ mockCursor.apply {
+ itemType = ITEM_TYPE_APPWIDGET
+ user = mUserHandle
+ restoreFlag = FLAG_UI_NOT_READY
+ container = CONTAINER_DESKTOP
+ whenever(isOnWorkspaceOrHotseat).thenCallRealMethod()
+ whenever(appWidgetProvider).thenReturn(expectedProvider)
+ whenever(appWidgetId).thenReturn(expectedAppWidgetId)
+ whenever(spanX).thenReturn(2)
+ whenever(spanY).thenReturn(1)
+ whenever(options).thenReturn(0)
+ whenever(appWidgetSource).thenReturn(20)
+ whenever(applyCommonProperties(any())).thenCallRealMethod()
+ whenever(
+ updater()
+ .put(Favorites.APPWIDGET_PROVIDER, expectedComponentName)
.put(Favorites.APPWIDGET_ID, expectedAppWidgetId)
.put(Favorites.RESTORED, expectedRestoreStatus)
+ .commit()
)
- .commit()
- val widgetInfoCaptor = ArgumentCaptor.forClass(LauncherAppWidgetInfo::class.java)
- verify(mockCursor).checkAndAddItem(widgetInfoCaptor.capture(), eq(mockBgDataModel))
- val actualWidgetInfo = widgetInfoCaptor.value
- with(actualWidgetInfo) {
- assertThat(providerName).isEqualTo(expectedWidgetInfo.providerName)
- assertThat(restoreStatus).isEqualTo(expectedWidgetInfo.restoreStatus)
- assertThat(targetComponent).isEqualTo(expectedWidgetInfo.targetComponent)
- assertThat(appWidgetId).isEqualTo(expectedWidgetInfo.appWidgetId)
+ .thenReturn(1)
+ }
+ val inflationResult =
+ WidgetInflater.InflationResult(type = WidgetInflater.TYPE_PENDING, widgetInfo = null)
+ mockWidgetInflater =
+ mock<WidgetInflater>().apply {
+ whenever(inflateAppWidget(any())).thenReturn(inflationResult)
}
- } finally {
- mockitoSession.finishMocking()
+ val packageUserKey = PackageUserKey("com.google.android.testApp", mUserHandle)
+ mInstallingPkgs[packageUserKey] = PackageInstaller.SessionInfo()
+
+ // When
+ itemProcessorUnderTest =
+ createWorkspaceItemProcessorUnderTest(widgetProvidersMap = mWidgetProvidersMap)
+ itemProcessorUnderTest.processItem()
+
+ // Then
+ val expectedWidgetInfo =
+ LauncherAppWidgetInfo().apply {
+ appWidgetId = expectedAppWidgetId
+ providerName = ComponentName.unflattenFromString(expectedProvider)
+ restoreStatus = expectedRestoreStatus
+ }
+ verify(
+ mockCursor
+ .updater()
+ .put(Favorites.APPWIDGET_PROVIDER, expectedProvider)
+ .put(Favorites.APPWIDGET_ID, expectedAppWidgetId)
+ .put(Favorites.RESTORED, expectedRestoreStatus)
+ )
+ .commit()
+ val widgetInfoCaptor = ArgumentCaptor.forClass(LauncherAppWidgetInfo::class.java)
+ verify(mockCursor).checkAndAddItem(widgetInfoCaptor.capture(), eq(mockBgDataModel))
+ val actualWidgetInfo = widgetInfoCaptor.value
+ with(actualWidgetInfo) {
+ assertThat(providerName).isEqualTo(expectedWidgetInfo.providerName)
+ assertThat(restoreStatus).isEqualTo(expectedWidgetInfo.restoreStatus)
+ assertThat(targetComponent).isEqualTo(expectedWidgetInfo.targetComponent)
+ assertThat(appWidgetId).isEqualTo(expectedWidgetInfo.appWidgetId)
}
}
@Test
@EnableFlags(FLAG_ENABLE_SUPPORT_FOR_ARCHIVING)
fun `When Archived Pending App Widget then checkAndAddItem`() {
- val mockitoSession =
- ExtendedMockito.mockitoSession().mockStatic(Utilities::class.java).startMocking()
- try {
- // Given
- val expectedProvider = "com.google.android.testApp/com.android.testApp.testAppProvider"
- val expectedComponentName = ComponentName.unflattenFromString(expectedProvider)
- val expectedPackage = expectedComponentName!!.packageName
- mockPmHelper =
- mock<PackageManagerHelper>().apply {
- whenever(isAppArchived(expectedPackage)).thenReturn(true)
- }
- mockCursor =
- mock<LoaderCursor>().apply {
- itemType = ITEM_TYPE_APPWIDGET
- id = 1
- user = UserHandle(1)
- restoreFlag = FLAG_UI_NOT_READY
- container = CONTAINER_DESKTOP
- whenever(isOnWorkspaceOrHotseat).thenCallRealMethod()
- whenever(appWidgetProvider).thenReturn(expectedProvider)
- whenever(appWidgetId).thenReturn(0)
- whenever(spanX).thenReturn(2)
- whenever(spanY).thenReturn(1)
- whenever(options).thenReturn(0)
- whenever(appWidgetSource).thenReturn(20)
- whenever(applyCommonProperties(any())).thenCallRealMethod()
- }
- mInstallingPkgs = hashMapOf()
- val inflationResult =
- WidgetInflater.InflationResult(
- type = WidgetInflater.TYPE_PENDING,
- widgetInfo = null
- )
- mockWidgetInflater =
- mock<WidgetInflater>().apply {
- whenever(inflateAppWidget(any())).thenReturn(inflationResult)
- }
- itemProcessorUnderTest =
- createWorkspaceItemProcessorUnderTest(widgetProvidersMap = mWidgetProvidersMap)
+ // Given
+ val expectedProvider = "com.google.android.testApp/com.android.testApp.testAppProvider"
+ val expectedComponentName = ComponentName.unflattenFromString(expectedProvider)
+ val expectedPackage = expectedComponentName!!.packageName
+ val expectedUser = UserHandle(1)
- // When
- itemProcessorUnderTest.processItem()
+ whenever(mockLauncherApps.getApplicationInfo(eq(expectedPackage), any(), eq(expectedUser)))
+ .thenReturn(ApplicationInfo().apply { isArchived = true })
+ mockCursor =
+ mock<LoaderCursor>().apply {
+ itemType = ITEM_TYPE_APPWIDGET
+ id = 1
+ user = expectedUser
+ restoreFlag = FLAG_UI_NOT_READY
+ container = CONTAINER_DESKTOP
+ whenever(isOnWorkspaceOrHotseat).thenCallRealMethod()
+ whenever(appWidgetProvider).thenReturn(expectedProvider)
+ whenever(appWidgetId).thenReturn(0)
+ whenever(spanX).thenReturn(2)
+ whenever(spanY).thenReturn(1)
+ whenever(options).thenReturn(0)
+ whenever(appWidgetSource).thenReturn(20)
+ whenever(applyCommonProperties(any())).thenCallRealMethod()
+ }
+ mInstallingPkgs = hashMapOf()
+ val inflationResult =
+ WidgetInflater.InflationResult(type = WidgetInflater.TYPE_PENDING, widgetInfo = null)
+ mockWidgetInflater =
+ mock<WidgetInflater>().apply {
+ whenever(inflateAppWidget(any())).thenReturn(inflationResult)
+ }
+ itemProcessorUnderTest =
+ createWorkspaceItemProcessorUnderTest(widgetProvidersMap = mWidgetProvidersMap)
- // Then
- verify(mockCursor).checkAndAddItem(any(), any())
- } finally {
- mockitoSession.finishMocking()
- }
+ // When
+ itemProcessorUnderTest.processItem()
+
+ // Then
+ verify(mockCursor).checkAndAddItem(any(), any())
}
private fun createWorkspaceItemProcessorUnderTest(
@@ -314,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,
@@ -333,6 +310,6 @@
isSdCardReady = isSdCardReady,
shortcutKeyToPinnedShortcuts = shortcutKeyToPinnedShortcuts,
installingPkgs = installingPkgs,
- allDeepShortcuts = allDeepShortcuts
+ allDeepShortcuts = allDeepShortcuts,
)
}
diff --git a/tests/src/com/android/launcher3/model/gridmigration/ValidGridMigrationUnitTest.kt b/tests/src/com/android/launcher3/model/gridmigration/ValidGridMigrationUnitTest.kt
index 7182cf3..03d0195 100644
--- a/tests/src/com/android/launcher3/model/gridmigration/ValidGridMigrationUnitTest.kt
+++ b/tests/src/com/android/launcher3/model/gridmigration/ValidGridMigrationUnitTest.kt
@@ -114,17 +114,14 @@
}
}
- private fun migrate(
- srcGrid: Grid,
- dstGrid: Grid,
- ): List<WorkspaceItem> {
+ private fun migrate(srcGrid: Grid, dstGrid: Grid): List<WorkspaceItem> {
val userSerial = UserCache.INSTANCE[context].getSerialNumberForUser(Process.myUserHandle())
val dbHelper =
DatabaseHelper(
context,
null,
{ UserCache.INSTANCE.get(context).getSerialNumberForUser(it) },
- {}
+ {},
)
Favorites.addTableToDb(dbHelper.writableDatabase, userSerial, false, srcGrid.tableName)
@@ -135,12 +132,12 @@
LauncherDbUtils.SQLiteTransaction(dbHelper.writableDatabase).use {
GridSizeMigrationUtil.migrate(
dbHelper,
- GridSizeMigrationUtil.DbReader(it.db, srcGrid.tableName, context, MockSet(1)),
- GridSizeMigrationUtil.DbReader(it.db, dstGrid.tableName, context, MockSet(1)),
+ GridSizeMigrationUtil.DbReader(it.db, srcGrid.tableName, context),
+ GridSizeMigrationUtil.DbReader(it.db, dstGrid.tableName, context),
dstGrid.size.x,
dstGrid.size,
srcGrid.toGridState(),
- dstGrid.toGridState()
+ dstGrid.toGridState(),
)
it.commit()
}
@@ -157,7 +154,7 @@
Grid(
tableName = Favorites.TMP_TABLE,
size = testCase.srcSize,
- items = generateItemsForTest(testCase.boards, REPEAT_AFTER)
+ items = generateItemsForTest(testCase.boards, REPEAT_AFTER),
)
val dstGrid =
Grid(tableName = Favorites.TABLE_NAME, size = testCase.targetSize, items = listOf())
@@ -175,13 +172,13 @@
Grid(
tableName = Favorites.TMP_TABLE,
size = testCase.srcSize,
- items = generateItemsForTest(testCase.boards, REPEAT_AFTER)
+ items = generateItemsForTest(testCase.boards, REPEAT_AFTER),
)
val dstGrid =
Grid(
tableName = Favorites.TABLE_NAME,
size = testCase.targetSize,
- items = generateItemsForTest(testCase.destBoards, REPEAT_AFTER_DST)
+ items = generateItemsForTest(testCase.destBoards, REPEAT_AFTER_DST),
)
validate(srcGrid, dstGrid, migrate(srcGrid, dstGrid))
}
@@ -199,7 +196,7 @@
Grid(
tableName = Favorites.TMP_TABLE,
size = testCase.srcSize,
- items = generateItemsForTest(testCase.boards, REPEAT_AFTER)
+ items = generateItemsForTest(testCase.boards, REPEAT_AFTER),
)
val dstGrid =
Grid(tableName = Favorites.TABLE_NAME, size = testCase.targetSize, items = listOf())
diff --git a/tests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt b/tests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt
index 3dd8dbc..a991981 100644
--- a/tests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt
+++ b/tests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt
@@ -18,6 +18,7 @@
import android.content.pm.ApplicationInfo
import android.content.pm.ApplicationInfo.FLAG_INSTALLED
+import android.content.pm.ApplicationInfo.FLAG_SYSTEM
import android.content.pm.LauncherApps
import android.content.pm.PackageInstaller
import android.content.pm.PackageManager
@@ -27,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
@@ -35,7 +37,9 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
import org.mockito.kotlin.whenever
@@ -50,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
@@ -59,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
@@ -126,13 +131,10 @@
fun `isTrustedPackage returns true if LauncherApps finds ApplicationInfo`() {
// Given
val expectedApplicationInfo =
- ApplicationInfo().apply {
- flags = flags or FLAG_INSTALLED
- enabled = true
- }
+ ApplicationInfo().apply { flags = FLAG_SYSTEM or FLAG_INSTALLED }
doReturn(expectedApplicationInfo)
.whenever(launcherApps)
- .getApplicationInfo(expectedAppPackage, ApplicationInfo.FLAG_SYSTEM, UserHandle(0))
+ .getApplicationInfo(eq(expectedAppPackage), any(), eq(UserHandle(0)))
// When
val actualResult = installSessionHelper.isTrustedPackage(expectedAppPackage, UserHandle(0))
// Then
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/ui/TaplWorkProfileTest.java b/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
index b2e413d..b38dd4b 100644
--- a/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
+++ b/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
@@ -45,7 +45,6 @@
import com.android.launcher3.allapps.WorkProfileManager;
import com.android.launcher3.tapl.LauncherInstrumentation;
import com.android.launcher3.util.TestUtil;
-import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
import com.android.launcher3.util.rule.TestStabilityRule.Stability;
import org.junit.After;
@@ -147,7 +146,6 @@
// Staging; will be promoted to presubmit if stable
@Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
- @ScreenRecord
@Test
public void toggleWorks() {
assumeTrue(mWorkProfileSetupSuccessful);
@@ -195,7 +193,6 @@
}
- @ScreenRecord // b/322823478
@Test
public void testEdu() {
assumeTrue(mWorkProfileSetupSuccessful);
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/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index b7ebfcd..e1bd686 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -123,10 +123,10 @@
TASK_SELECTOR);
final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
mLauncher.assertTrue(
- "All tasks not to the left of the swiped task",
- tasks.stream()
- .allMatch(
- t -> t.getVisibleBounds().right < centerX));
+ "Task(s) found to the right of the swiped task",
+ tasks.stream().allMatch(t ->
+ t.getVisibleBounds().right < centerX
+ || t.getVisibleBounds().centerX() == centerX));
}
}
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 21e93c5..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++;
@@ -2423,7 +2404,7 @@
eventChecker.setLogExclusionRule(event -> {
Matcher matcher = Pattern.compile("KeyEvent.*flags=0x([0-9a-fA-F]+)").matcher(event);
if (matcher.find()) {
- int keyEventFlags = Integer.parseInt(matcher.group(1), 16);
+ long keyEventFlags = Long.parseLong(matcher.group(1), 16);
// ignore KeyEvents with FLAG_CANCELED
return (keyEventFlags & KeyEvent.FLAG_CANCELED) != 0;
}
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,