Merge "Set psDivider to gone in collapse due to updateAdapterItem suppression in test." into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 38af572..aafa1f6 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -65,6 +65,13 @@
}
flag {
+ name: "enable_taskbar_connected_displays"
+ namespace: "launcher"
+ description: "Enables connected displays in taskbar."
+ bug: "362720616"
+}
+
+flag {
name: "enable_taskbar_customization"
namespace: "launcher"
description: "Enables taskbar customization framework."
@@ -433,3 +440,34 @@
description: "Enables tracking active gesture logs in ProtoLog"
bug: "293182501"
}
+
+flag {
+ name: "enable_recents_window_proto_log"
+ namespace: "launcher"
+ description: "Enables tracking recents window logs in ProtoLog"
+ bug: "292269949"
+}
+
+flag {
+ name: "enable_state_manager_proto_log"
+ namespace: "launcher"
+ description: "Enables tracking state manager logs in ProtoLog"
+ bug: "292269949"
+}
+
+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
+ }
+}
+
+flag {
+ name: "enable_tiered_widgets_by_default_in_picker"
+ namespace: "launcher"
+ description: "Shows filtered set of widgets by default and an option to show all widgets in the widget picker"
+ bug: "356127021"
+}
diff --git a/aconfig/launcher_overview.aconfig b/aconfig/launcher_overview.aconfig
index 853faf8..c59978f 100644
--- a/aconfig/launcher_overview.aconfig
+++ b/aconfig/launcher_overview.aconfig
@@ -36,7 +36,7 @@
name: "enable_large_desktop_windowing_tile"
namespace: "launcher_overview"
description: "Makes the desktop tiles larger and moves them to the front of the list in Overview."
- bug: "353947137"
+ bug: "357860832"
}
flag {
diff --git a/go/quickstep/src/com/android/launcher3/AppSharing.java b/go/quickstep/src/com/android/launcher3/AppSharing.java
index e15b132..a97fecc 100644
--- a/go/quickstep/src/com/android/launcher3/AppSharing.java
+++ b/go/quickstep/src/com/android/launcher3/AppSharing.java
@@ -37,12 +37,13 @@
import androidx.core.content.FileProvider;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.AppShareabilityChecker;
import com.android.launcher3.model.AppShareabilityJobService;
import com.android.launcher3.model.AppShareabilityManager;
import com.android.launcher3.model.AppShareabilityManager.ShareabilityStatus;
import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.views.ActivityContext;
@@ -114,17 +115,17 @@
* The Share App system shortcut, used to initiate p2p sharing of a given app
*/
public final class Share extends SystemShortcut<Launcher> {
- private final PopupDataProvider mPopupDataProvider;
private final boolean mSharingEnabledForUser;
private final Set<View> mBoundViews = Collections.newSetFromMap(new WeakHashMap<>());
private boolean mIsEnabled = true;
+ private StatsLogManager mStatsLogManager;
public Share(Launcher target, ItemInfo itemInfo, View originalView) {
super(R.drawable.ic_share, R.string.app_share_drop_target_label, target, itemInfo,
originalView);
- mPopupDataProvider = target.getPopupDataProvider();
-
+ mStatsLogManager = ActivityContext.lookupContext(originalView.getContext())
+ .getStatsLogManager();
mSharingEnabledForUser = bluetoothSharingEnabled(target);
if (!mSharingEnabledForUser) {
setEnabled(false);
@@ -150,8 +151,7 @@
@Override
public void onClick(View view) {
- ActivityContext.lookupContext(view.getContext())
- .getStatsLogManager().logger().log(LAUNCHER_SYSTEM_SHORTCUT_APP_SHARE_TAP);
+ mStatsLogManager.logger().log(LAUNCHER_SYSTEM_SHORTCUT_APP_SHARE_TAP);
if (!mIsEnabled) {
showCannotShareToast(view.getContext());
return;
@@ -240,6 +240,11 @@
public boolean isEnabled() {
return mIsEnabled;
}
+
+ @VisibleForTesting
+ void setStatsLogManager(StatsLogManager statsLogManager) {
+ mStatsLogManager = statsLogManager;
+ }
}
/**
diff --git a/quickstep/res/drawable/ic_external_display.xml b/quickstep/res/drawable/ic_external_display.xml
new file mode 100644
index 0000000..64c183e
--- /dev/null
+++ b/quickstep/res/drawable/ic_external_display.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:pathData="M320,840v-80L160,760q-33,0 -56.5,-23.5T80,680v-480q0,-33 23.5,-56.5T160,120h640q33,0 56.5,23.5T880,200v480q0,33 -23.5,56.5T800,760L640,760v80L320,840ZM160,680h640v-480L160,200v480ZM160,680v-480,480Z"
+ android:fillColor="#e8eaed"/>
+</vector>
diff --git a/quickstep/res/layout/bubblebar_flyout.xml b/quickstep/res/layout/bubblebar_flyout.xml
index fc1e914..e3338bf 100644
--- a/quickstep/res/layout/bubblebar_flyout.xml
+++ b/quickstep/res/layout/bubblebar_flyout.xml
@@ -19,7 +19,7 @@
xmlns:tools="http://schemas.android.com/tools">
<ImageView
- android:id="@+id/bubble_flyout_avatar"
+ android:id="@+id/bubble_flyout_icon"
android:layout_width="50dp"
android:layout_height="36dp"
android:paddingEnd="@dimen/bubblebar_flyout_avatar_message_space"
@@ -30,14 +30,14 @@
tools:src="#ff0000"/>
<TextView
- android:id="@+id/bubble_flyout_name"
+ android:id="@+id/bubble_flyout_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:fontFamily="@*android:string/config_bodyFontFamilyMedium"
android:maxLines="1"
android:ellipsize="end"
app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toEndOf="@id/bubble_flyout_avatar"
+ app:layout_constraintStart_toEndOf="@id/bubble_flyout_icon"
tools:text="Sender"/>
<TextView
@@ -47,8 +47,8 @@
android:fontFamily="@*android:string/config_bodyFontFamily"
android:maxLines="2"
android:ellipsize="end"
- app:layout_constraintTop_toBottomOf="@id/bubble_flyout_name"
- app:layout_constraintStart_toEndOf="@id/bubble_flyout_avatar"
+ app:layout_constraintTop_toBottomOf="@id/bubble_flyout_title"
+ app:layout_constraintStart_toEndOf="@id/bubble_flyout_icon"
tools:text="This is a message"/>
</merge>
diff --git a/quickstep/res/layout/customizable_taskbar.xml b/quickstep/res/layout/customizable_taskbar.xml
index e1a80ae..d988cbc 100644
--- a/quickstep/res/layout/customizable_taskbar.xml
+++ b/quickstep/res/layout/customizable_taskbar.xml
@@ -51,20 +51,26 @@
android:layout_width="match_parent"
android:layout_height="match_parent"/>
- <com.android.launcher3.taskbar.bubbles.BubbleBarView
- android:id="@+id/taskbar_bubbles"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/bubblebar_size_with_pointer"
- android:layout_gravity="bottom|end"
- android:layout_marginHorizontal="@dimen/transient_taskbar_bottom_margin"
- android:paddingTop="@dimen/bubblebar_pointer_visible_size"
- android:paddingEnd="@dimen/taskbar_icon_spacing"
- android:paddingStart="@dimen/taskbar_icon_spacing"
- android:visibility="gone"
- android:gravity="center"
- android:clipChildren="false"
- android:elevation="@dimen/bubblebar_elevation"
- />
+ <FrameLayout
+ android:id="@+id/taskbar_bubbles_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="false">
+
+ <com.android.launcher3.taskbar.bubbles.BubbleBarView
+ android:id="@+id/taskbar_bubbles"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/bubblebar_size_with_pointer"
+ android:layout_gravity="bottom|end"
+ android:layout_marginHorizontal="@dimen/transient_taskbar_bottom_margin"
+ android:paddingTop="@dimen/bubblebar_pointer_visible_size"
+ android:paddingEnd="@dimen/taskbar_icon_spacing"
+ android:paddingStart="@dimen/taskbar_icon_spacing"
+ android:visibility="gone"
+ android:gravity="center"
+ android:clipChildren="false"
+ android:elevation="@dimen/bubblebar_elevation" />
+ </FrameLayout>
<com.android.launcher3.taskbar.navbutton.NearestTouchFrame
android:id="@+id/navbuttons_view"
diff --git a/quickstep/res/layout/taskbar.xml b/quickstep/res/layout/taskbar.xml
index e8f3d9d..54f9ae8 100644
--- a/quickstep/res/layout/taskbar.xml
+++ b/quickstep/res/layout/taskbar.xml
@@ -35,17 +35,24 @@
android:layout_width="match_parent"
android:layout_height="match_parent"/>
- <com.android.launcher3.taskbar.bubbles.BubbleBarView
- android:id="@+id/taskbar_bubbles"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/bubblebar_size_with_pointer"
- android:layout_marginHorizontal="@dimen/transient_taskbar_bottom_margin"
- android:paddingTop="@dimen/bubblebar_pointer_visible_size"
- android:visibility="gone"
- android:gravity="center"
- android:layout_gravity="bottom"
- android:clipChildren="false"
- android:elevation="@dimen/bubblebar_elevation" />
+ <FrameLayout
+ android:id="@+id/taskbar_bubbles_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="false">
+
+ <com.android.launcher3.taskbar.bubbles.BubbleBarView
+ android:id="@+id/taskbar_bubbles"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/bubblebar_size_with_pointer"
+ android:layout_marginHorizontal="@dimen/transient_taskbar_bottom_margin"
+ android:paddingTop="@dimen/bubblebar_pointer_visible_size"
+ android:visibility="gone"
+ android:gravity="center"
+ android:layout_gravity="bottom"
+ android:clipChildren="false"
+ android:elevation="@dimen/bubblebar_elevation" />
+ </FrameLayout>
<com.android.launcher3.taskbar.navbutton.NearestTouchFrame
android:id="@+id/navbuttons_view"
diff --git a/quickstep/res/layout/transient_taskbar.xml b/quickstep/res/layout/transient_taskbar.xml
index f3c3383..3ec8046 100644
--- a/quickstep/res/layout/transient_taskbar.xml
+++ b/quickstep/res/layout/transient_taskbar.xml
@@ -38,18 +38,24 @@
android:layout_width="match_parent"
android:layout_height="match_parent"/>
- <com.android.launcher3.taskbar.bubbles.BubbleBarView
- android:id="@+id/taskbar_bubbles"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/bubblebar_size_with_pointer"
- android:layout_gravity="bottom|end"
- android:layout_marginHorizontal="@dimen/transient_taskbar_bottom_margin"
- android:paddingTop="@dimen/bubblebar_pointer_visible_size"
- android:visibility="gone"
- android:gravity="center"
- android:clipChildren="false"
- android:elevation="@dimen/bubblebar_elevation"
- />
+ <FrameLayout
+ android:id="@+id/taskbar_bubbles_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="false">
+
+ <com.android.launcher3.taskbar.bubbles.BubbleBarView
+ android:id="@+id/taskbar_bubbles"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/bubblebar_size_with_pointer"
+ android:layout_gravity="bottom|end"
+ android:layout_marginHorizontal="@dimen/transient_taskbar_bottom_margin"
+ android:paddingTop="@dimen/bubblebar_pointer_visible_size"
+ android:visibility="gone"
+ android:gravity="center"
+ android:clipChildren="false"
+ android:elevation="@dimen/bubblebar_elevation" />
+ </FrameLayout>
<com.android.launcher3.taskbar.navbutton.NearestTouchFrame
android:id="@+id/navbuttons_view"
diff --git a/quickstep/res/values-af/strings.xml b/quickstep/res/values-af/strings.xml
index 7d65403..5cbe556 100644
--- a/quickstep/res/values-af/strings.xml
+++ b/quickstep/res/values-af/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Speld vas"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Vormvry"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Rekenaar"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Werkskerm"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Geen onlangse items nie"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Programgebruikinstellings"</string>
@@ -139,8 +141,7 @@
<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="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Taakbalkoorloop"</string>
<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>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Maak almal toe"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"vou <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> uit"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"vou <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> in"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-am/strings.xml b/quickstep/res/values-am/strings.xml
index 28bd69a..ce12f9d 100644
--- a/quickstep/res/values-am/strings.xml
+++ b/quickstep/res/values-am/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"ሰካ"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"ነፃ ቅጽ"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ዴስክቶፕ"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"ዴስክቶፕ"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"ምንም የቅርብ ጊዜ ንጥሎች የሉም"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"የመተግበሪያ አጠቃቀም ቅንብሮች"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"ሁልጊዜ የተግባር አሞሌ ያሳዩ"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"የአሰሳ ሁነታን ይለውጡ"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"የተግባር አሞሌ አካፋይ"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"የተግባር አሞሌ ትርፍ ፍሰት"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ወደ ላይ/ግራ ይውሰዱ"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ወደ ታች/ቀኝ ይውሰዱ"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{ተጨማሪ መተግበሪያ}one{ተጨማሪ መተግበሪያ}other{ተጨማሪ መተግበሪያዎች}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"ሁሉንም አሰናብት"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>ን ዘርጋ"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>ን ሰብስብ"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-ar/strings.xml b/quickstep/res/values-ar/strings.xml
index 6ba2e5d..0f3a854 100644
--- a/quickstep/res/values-ar/strings.xml
+++ b/quickstep/res/values-ar/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"تثبيت"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"شكل مجاني"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"الكمبيوتر المكتبي"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"كمبيوتر مكتبي"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"ما مِن عناصر تم استخدامها مؤخرًا"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"إعدادات استخدام التطبيق"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"إظهار شريط التطبيقات دائمًا"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"تغيير وضع التنقل"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"مقسِّم شريط التطبيقات"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"القائمة الكاملة لشريط التطبيقات"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"الانتقال إلى يمين الشاشة أو أعلاها"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"الانتقال إلى يسار الشاشة أو أسفلها"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{تطبيق واحد آخر}zero{تطبيق آخر}two{تطبيقان آخران}few{تطبيقات أخرى}many{تطبيقًا آخر}other{تطبيق آخر}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"إغلاق الكل"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"توسيع <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"تصغير <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-as/strings.xml b/quickstep/res/values-as/strings.xml
index 486dc09..1d536e8 100644
--- a/quickstep/res/values-as/strings.xml
+++ b/quickstep/res/values-as/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"পিন"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ডেস্কটপ"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"ডেস্কটপ"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"কোনো শেহতীয়া বস্তু নাই"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"এপে ব্যৱহাৰ কৰা ডেটাৰ ছেটিং"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"টাস্কবাৰ সদায় দেখুৱাওক"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"নেভিগেশ্বন ম’ড সলনি কৰক"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"টাস্কবাৰ বিভাজক"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"টাস্কবাৰ অ’ভাৰফ্ল"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ওপৰৰ বাঁওফাললৈ নিয়ক"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"তলৰ সোঁফাললৈ নিয়ক"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{অধিক এপ্}one{অধিক এপ্}other{অধিক এপ্}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"সকলো অগ্ৰাহ্য কৰক"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> বিস্তাৰ কৰক"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> সংকোচন কৰক"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-az/strings.xml b/quickstep/res/values-az/strings.xml
index c483db3..2e6337a 100644
--- a/quickstep/res/values-az/strings.xml
+++ b/quickstep/res/values-az/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Sancın"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Sərbəst rejim"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Masaüstü"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Masaüstü"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Son elementlər yoxdur"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Tətbiq istifadə ayarları"</string>
@@ -139,8 +141,7 @@
<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="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Tapşırıqlar Paneli üzrə əlavə menyu"</string>
<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>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Hamısını kənarlaşdırın"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"genişləndirin: <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"yığcamlaşdırın: <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-b+sr+Latn/strings.xml b/quickstep/res/values-b+sr+Latn/strings.xml
index f08cf83..93a8d48 100644
--- a/quickstep/res/values-b+sr+Latn/strings.xml
+++ b/quickstep/res/values-b+sr+Latn/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Zakači"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Slobodni oblik"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Računar"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Računari"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nema nedavnih stavki"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Podešavanja korišćenja aplikacije"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Uvek prikazuj traku zadataka"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Promeni režim navigacije"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Razdelnik trake zadataka"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Preklopna traka zadataka"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Premesti gore levo"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Premesti dole desno"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{dodatna aplikacija}one{dodatna aplikacija}few{dodatne aplikacije}other{dodatnih aplikacija}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Odbaci sve"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"proširite oblačić <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"skupite oblačić <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-be/strings.xml b/quickstep/res/values-be/strings.xml
index fb5b556..3f5a617 100644
--- a/quickstep/res/values-be/strings.xml
+++ b/quickstep/res/values-be/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Замацаваць"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Адвольная форма"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Працоўны стол"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Працоўны стол"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Няма новых элементаў"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Налады выкарыстання праграмы"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Заўсёды паказваць панэль задач"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Змяніць рэжым навігацыі"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Раздзяляльнік панэлі задач"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Меню з пашырэннем панэлі задач"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Перамясціць уверх/улева"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Перамясціць уніз/управа"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{даступная праграма}one{даступная праграма}few{даступныя праграмы}many{даступных праграм}other{даступнай праграмы}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Закрыць усе"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>: разгарнуць"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>: згарнуць"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-bg/strings.xml b/quickstep/res/values-bg/strings.xml
index 3c0d840..adabb31 100644
--- a/quickstep/res/values-bg/strings.xml
+++ b/quickstep/res/values-bg/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Фиксиране"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Свободна форма"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"За компютър"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Настолен компютър"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Няма скорошни елементи"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Настройки за използването на приложенията"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Лентата на задачите винаги да се показва"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Промяна на режима на навигация"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Разделител на лентата на задачите"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Меню при препълване на лентата на задачите"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Преместване горе/вляво"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Преместване долу/вдясно"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{допълнително приложение}other{допълнителни приложения}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Отхвърляне на всички"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"разгъване на <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"свиване на <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-bn/strings.xml b/quickstep/res/values-bn/strings.xml
index a224dac..7d93d9b 100644
--- a/quickstep/res/values-bn/strings.xml
+++ b/quickstep/res/values-bn/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"পিন করুন"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"ফ্রি-ফর্ম"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ডেস্কটপ"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"ডেস্কটপ"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"কোনও সাম্প্রতিক আইটেম নেই"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"অ্যাপ ব্যবহারের সেটিংস"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"সবসময় টাস্কবার দেখুন"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"\'নেভিগেশন\' মোড পরিবর্তন করুন"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"টাস্কবার ডিভাইডার"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"টাস্কবার ওভারফ্লো"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"উপরে/বাঁদিকে সরান"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"নিচে/ডানদিকে সরান"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{আরও অ্যাপ}one{আরও অ্যাপ}other{আরও অ্যাপ}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"সব বাতিল করুন"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> বড় করুন"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> আড়াল করুন"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-bs/strings.xml b/quickstep/res/values-bs/strings.xml
index 6588dd0..2ac0ab1 100644
--- a/quickstep/res/values-bs/strings.xml
+++ b/quickstep/res/values-bs/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Zakači"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Slobodan oblik"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Radna površina"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Radna površina"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nema nedavnih stavki"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Postavke korištenja aplikacije"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Uvijek prikaži traku zadataka"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Promijeni način navigacije"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Razdjelnik trake zadataka"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Preklopni meni trake zadataka"</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>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Odbacivanje svega"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"proširivanje oblačića <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"sužavanje oblačića <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-ca/strings.xml b/quickstep/res/values-ca/strings.xml
index abb2984..1c27b08 100644
--- a/quickstep/res/values-ca/strings.xml
+++ b/quickstep/res/values-ca/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Fixa"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Format lliure"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Escriptori"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Escriptori"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"No hi ha cap element recent"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Configuració d\'ús d\'aplicacions"</string>
@@ -139,8 +141,7 @@
<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="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Menú addicional de la barra de tasques"</string>
<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>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Ignora-ho tot"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"desplega <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"replega <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-cs/strings.xml b/quickstep/res/values-cs/strings.xml
index 324b02a..2f8d4b0 100644
--- a/quickstep/res/values-cs/strings.xml
+++ b/quickstep/res/values-cs/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Připnout"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Neomezený režim"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Počítač"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Počítač"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Žádné položky z nedávné doby"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Nastavení využití aplikací"</string>
@@ -139,8 +141,7 @@
<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="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Přetečení panelu aplikací"</string>
<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>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Zavřít vše"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"rozbalit <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"sbalit <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-da/strings.xml b/quickstep/res/values-da/strings.xml
index ca30dda..4684af2 100644
--- a/quickstep/res/values-da/strings.xml
+++ b/quickstep/res/values-da/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Fastgør"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Frit format"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Computertilstand"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Computer"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Ingen nye elementer"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Indstillinger for appforbrug"</string>
@@ -139,8 +141,7 @@
<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="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Prikmenu på proceslinjen"</string>
<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>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Afvis alle"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"udvid <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"skjul <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-de/strings.xml b/quickstep/res/values-de/strings.xml
index f14eddf..155abb4 100644
--- a/quickstep/res/values-de/strings.xml
+++ b/quickstep/res/values-de/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Fixieren"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform-Modus"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktopmodus"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktopmodus"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Keine kürzlich verwendeten Elemente"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Einstellungen zur App-Nutzung"</string>
@@ -139,8 +141,7 @@
<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="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Dreipunkt-Menü der Taskleiste"</string>
<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>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Alle schließen"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"„<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>“ maximieren"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"„<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>“ minimieren"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-el/strings.xml b/quickstep/res/values-el/strings.xml
index e767c74..39e8916 100644
--- a/quickstep/res/values-el/strings.xml
+++ b/quickstep/res/values-el/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Καρφίτσωμα"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Ελεύθερη μορφή"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Υπολογιστής"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Υπολογιστής"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Δεν υπάρχουν πρόσφατα στοιχεία"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Ρυθμίσεις χρήσης εφαρμογής"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Εμφάνιση Γραμμής εργαλείων"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Αλλαγή τρόπου πλοήγησης"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Διαχωριστικό Γραμμής εργαλείων"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Υπερχείλιση γραμμής εργαλείων"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Μετακίνηση επάνω/αριστερά"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Μετακίνηση κάτω/δεξιά"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{ακόμη εφαρμογή}other{ακόμη εφαρμογές}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Παράβλεψη όλων"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"ανάπτυξη <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"σύμπτυξη <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-en-rAU/strings.xml b/quickstep/res/values-en-rAU/strings.xml
index a74a17b..bc73bfe 100644
--- a/quickstep/res/values-en-rAU/strings.xml
+++ b/quickstep/res/values-en-rAU/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Pin"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"No recent items"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"App usage settings"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Always show Taskbar"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Change navigation mode"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Taskbar divider"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Taskbar overflow"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Move to top/left"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Move to bottom/right"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{more app}other{more apps}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Dismiss all"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"expand <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"collapse <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-en-rCA/strings.xml b/quickstep/res/values-en-rCA/strings.xml
index e0787ca..da4effb 100644
--- a/quickstep/res/values-en-rCA/strings.xml
+++ b/quickstep/res/values-en-rCA/strings.xml
@@ -22,6 +22,7 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Pin"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
+ <string name="recent_task_option_external_display" msgid="4533840664313389484">"Move to external display"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"No recent items"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"App usage settings"</string>
@@ -154,4 +155,5 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Dismiss all"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"expand <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"collapse <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <string name="search_gesture_feature_title" msgid="1294044108313175306">"Circle to Search"</string>
</resources>
diff --git a/quickstep/res/values-en-rGB/strings.xml b/quickstep/res/values-en-rGB/strings.xml
index a74a17b..bc73bfe 100644
--- a/quickstep/res/values-en-rGB/strings.xml
+++ b/quickstep/res/values-en-rGB/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Pin"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"No recent items"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"App usage settings"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Always show Taskbar"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Change navigation mode"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Taskbar divider"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Taskbar overflow"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Move to top/left"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Move to bottom/right"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{more app}other{more apps}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Dismiss all"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"expand <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"collapse <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-en-rIN/strings.xml b/quickstep/res/values-en-rIN/strings.xml
index a74a17b..bc73bfe 100644
--- a/quickstep/res/values-en-rIN/strings.xml
+++ b/quickstep/res/values-en-rIN/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Pin"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"No recent items"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"App usage settings"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Always show Taskbar"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Change navigation mode"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Taskbar divider"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Taskbar overflow"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Move to top/left"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Move to bottom/right"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{more app}other{more apps}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Dismiss all"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"expand <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"collapse <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-es-rUS/strings.xml b/quickstep/res/values-es-rUS/strings.xml
index e6efd43..25bbba2 100644
--- a/quickstep/res/values-es-rUS/strings.xml
+++ b/quickstep/res/values-es-rUS/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Fijar"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Formato libre"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Escritorio"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Computadoras"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"No hay elementos recientes"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Configuración de uso de la app"</string>
@@ -139,8 +141,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 la Barra de tareas"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Barra de tareas ampliada"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Mover 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>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Descartar todo"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"expandir <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"contraer <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-es/strings.xml b/quickstep/res/values-es/strings.xml
index 527bdc3..a4d7a8b 100644
--- a/quickstep/res/values-es/strings.xml
+++ b/quickstep/res/values-es/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Fijar"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Formato libre"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Escritorio"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Ordenador"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"No hay nada reciente"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Ajustes de uso de la aplicación"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Barra de tareas visible"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Cambiar el modo de navegación"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Divisor de Barra de Tareas"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Barra de tareas ampliada"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Mover arriba/a la izquierda"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mover abajo/a la derecha"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{aplicación más}other{aplicaciones más}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Cerrar todo"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"desplegar <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"contraer <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-et/strings.xml b/quickstep/res/values-et/strings.xml
index 0e222bc..63d4d2f 100644
--- a/quickstep/res/values-et/strings.xml
+++ b/quickstep/res/values-et/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Kinnita"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Vabavorm"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Lauaarvuti režiim"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Töölaud"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Hiljutisi üksusi pole"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Rakenduse kasutuse seaded"</string>
@@ -139,8 +141,7 @@
<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="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Tegumiriba ületäide"</string>
<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>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Loobu kõigist"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"Toiminguriba <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> laiendamine"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"Toiminguriba <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ahendamine"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-eu/strings.xml b/quickstep/res/values-eu/strings.xml
index 7764cb8..3c0698c 100644
--- a/quickstep/res/values-eu/strings.xml
+++ b/quickstep/res/values-eu/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Ainguratu"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Modu librea"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Ordenagailua"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Mahaigaina"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Ez dago azkenaldi honetako ezer"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Aplikazioen erabileraren ezarpenak"</string>
@@ -139,8 +141,7 @@
<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="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Zereginen barraren luzapena"</string>
<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>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Baztertu guztiak"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"zabaldu <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"tolestu <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-fa/strings.xml b/quickstep/res/values-fa/strings.xml
index 05cf83e..8d5d15e 100644
--- a/quickstep/res/values-fa/strings.xml
+++ b/quickstep/res/values-fa/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"پین"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"حالت رایانه"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"رایانه"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"چیز جدیدی اینجا نیست"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"تنظیمات استفاده از برنامه"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"نوار وظیفه همیشه نشان داده شود"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"تغییر حالت پیمایش"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"جداکننده نوار وظیفه"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"سرریز نوار وظیفه"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"انتقال به بالا/ چپ"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"انتقال به پایین/ راست"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{برنامه دیگر}one{برنامه دیگر}other{برنامه دیگر}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"رد کردن همه"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"ازهم باز کردن <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"جمع کردن <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-fi/strings.xml b/quickstep/res/values-fi/strings.xml
index 73b1ba4..10ea9ee 100644
--- a/quickstep/res/values-fi/strings.xml
+++ b/quickstep/res/values-fi/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Kiinnitä"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Vapaamuotoinen"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Tietokone"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Tietokone"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Ei viimeaikaisia kohteita"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Sovelluksen käyttöasetukset"</string>
@@ -139,8 +141,7 @@
<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="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Tehtäväpalkin ylivuotu"</string>
<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>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Hylkää kaikki"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"laajenna <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"tiivistä <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-fr-rCA/strings.xml b/quickstep/res/values-fr-rCA/strings.xml
index a554881..b624b4f 100644
--- a/quickstep/res/values-fr-rCA/strings.xml
+++ b/quickstep/res/values-fr-rCA/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Épingler"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Forme libre"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Ordinateur de bureau"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Ordinateur de bureau"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Aucun élément récent"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Paramètres d\'utilisation de l\'appli"</string>
@@ -139,8 +141,7 @@
<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="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Barre des tâches à développer"</string>
<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>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Tout ignorer"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"Développer <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"Réduire <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-fr/strings.xml b/quickstep/res/values-fr/strings.xml
index 3711dcd..3f61d65 100644
--- a/quickstep/res/values-fr/strings.xml
+++ b/quickstep/res/values-fr/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Épingler"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Format libre"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Ordinateur"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Ordinateur"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Aucun élément récent"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Paramètres de consommation de l\'application"</string>
@@ -139,8 +141,7 @@
<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="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Développement de la barre des tâches"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Déplacer en haut ou à gauche"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Déplacer en bas ou à droite"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{autre application}one{autre application}other{autres applications}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Tout fermer"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"Développer <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"Réduire <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-gl/strings.xml b/quickstep/res/values-gl/strings.xml
index a2c4b73..d9d06a1 100644
--- a/quickstep/res/values-gl/strings.xml
+++ b/quickstep/res/values-gl/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Fixar"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Forma libre"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Escritorio"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Ordenador"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Non hai elementos recentes"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Configuración do uso de aplicacións"</string>
@@ -139,8 +141,7 @@
<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="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Menú adicional da barra de tarefas"</string>
<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>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Pechar todo"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"despregar <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"contraer <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-gu/strings.xml b/quickstep/res/values-gu/strings.xml
index dea52c3..7716d4d 100644
--- a/quickstep/res/values-gu/strings.xml
+++ b/quickstep/res/values-gu/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"પિન કરો"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"ફ્રિફોર્મ"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ડેસ્કટૉપ"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"ડેસ્કટૉપ"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"તાજેતરની કોઈ આઇટમ નથી"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ઍપ વપરાશનું સેટિંગ"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"હંમેશાં ટાસ્કબાર બતાવો"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"નૅવિગેશન મોડ બદલો"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"ટાસ્કબાર વિભાજક"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"ટાસ્કબાર ઓવરફ્લો"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"સૌથી ઉપર ડાબી બાજુએ ખસેડો"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"સૌથી નીચે જમણી બાજુએ ખસેડો"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{વધુ ઍપ}one{વધુ ઍપ}other{વધુ ઍપ}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"તમામ છોડી દો"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> મોટો કરો"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> નાનો કરો"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-hi/strings.xml b/quickstep/res/values-hi/strings.xml
index e81e942..02e23ec 100644
--- a/quickstep/res/values-hi/strings.xml
+++ b/quickstep/res/values-hi/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"पिन करें"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"फ़्रीफ़ॉर्म"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"डेस्कटॉप"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"डेस्कटॉप"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"हाल ही का कोई आइटम नहीं है"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ऐप्लिकेशन इस्तेमाल की सेटिंग"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"टास्कबार हमेशा दिखाएं"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"नेविगेशन का मोड बदलें"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"टास्कबार डिवाइडर"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"टास्कबार ओवरफ़्लो आइकॉन"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ऊपर/बाईं तरफ़ ले जाएं"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"नीचे/दाईं तरफ़ ले जाएं"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{ज़्यादा ऐप्लिकेशन}one{ज़्यादा ऐप्लिकेशन}other{ज़्यादा ऐप्लिकेशन}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"सभी खारिज करें"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> को बड़ा करें"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> को छोटा करें"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-hr/strings.xml b/quickstep/res/values-hr/strings.xml
index a2bf691..a742c01 100644
--- a/quickstep/res/values-hr/strings.xml
+++ b/quickstep/res/values-hr/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Prikvači"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Slobodni oblik"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Računalo"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Radna površina"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nema nedavnih stavki"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Postavke upotrebe aplikacija"</string>
@@ -91,7 +93,7 @@
<string name="allset_title" msgid="5021126669778966707">"Sve je spremno!"</string>
<string name="allset_hint" msgid="459504134589971527">"Prijeđite prstom prema gore da biste otvorili početni zaslon"</string>
<string name="allset_button_hint" msgid="2395219947744706291">"Dodirnite gumb početnog zaslona da biste prešli na početni zaslon"</string>
- <string name="allset_description_generic" msgid="5385500062202019855">"<xliff:g id="DEVICE">%1$s</xliff:g> je spreman za početak upotrebe"</string>
+ <string name="allset_description_generic" msgid="5385500062202019855">"Možete početi upotrebljavati <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="default_device_name" msgid="6660656727127422487">"Uređaj"</string>
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Postavke navigacije sustavom"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Podijeli"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Uvijek prikaži traku zadataka"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Promijeni način navigacije"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Razdjelnik trake sa zadacima"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Dodatni izbornik trake sa zadacima"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Premjesti gore/lijevo"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Premjesti dolje/desno"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{dodatna aplikacija}one{dodatna aplikacija}few{dodatne aplikacije}other{dodatnih aplikacija}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Odbaci sve"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"proširite oblačić <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"sažmite oblačić <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-hu/strings.xml b/quickstep/res/values-hu/strings.xml
index 4dc9974..439d6ed 100644
--- a/quickstep/res/values-hu/strings.xml
+++ b/quickstep/res/values-hu/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Kitűzés"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Szabad forma"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Asztali"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Asztali"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nincsenek mostanában használt elemek"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Alkalmazáshasználati beállítások"</string>
@@ -139,8 +141,7 @@
<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="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Feladatsáv túlcsordulása"</string>
<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>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Az összes elvetése"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> kibontása"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> összecsukása"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-hy/strings.xml b/quickstep/res/values-hy/strings.xml
index f4fcacf..87681c0 100644
--- a/quickstep/res/values-hy/strings.xml
+++ b/quickstep/res/values-hy/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Ամրացնել"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Կամայական ձև"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Համակարգիչ"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Համակարգիչ"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Այստեղ դեռ ոչինչ չկա"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Հավելվածի օգտագործման կարգավորումներ"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Միշտ ցույց տալ վահանակը"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Փոխել նավիգացիայի ռեժիմը"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Հավելվածների վահանակի բաժանիչ"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Հավելվածների վահանակի լրացուցիչ ընտրացանկ"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Տեղափոխել վերևի ձախ անկյուն"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Տեղափոխել ներքևի աջ անկյուն"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{լրացուցիչ հավելված}one{լրացուցիչ հավելված}other{լրացուցիչ հավելված}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Փակել բոլորը"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>. ծավալել"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>. ծալել"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-in/strings.xml b/quickstep/res/values-in/strings.xml
index 519dced..119ed6b 100644
--- a/quickstep/res/values-in/strings.xml
+++ b/quickstep/res/values-in/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Sematkan"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Format bebas"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Tidak ada item yang baru dibuka"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Setelan penggunaan aplikasi"</string>
@@ -139,8 +141,7 @@
<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="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Tambahan Taskbar"</string>
<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>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Tutup semua"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"luaskan <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"ciutkan <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-is/strings.xml b/quickstep/res/values-is/strings.xml
index 274e1da..68f4656 100644
--- a/quickstep/res/values-is/strings.xml
+++ b/quickstep/res/values-is/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Festa"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Frjálst snið"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Tölva"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Tölva"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Engin nýleg atriði"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Notkunarstillingar forrits"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Alltaf sýna forritastiku"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Breyta leiðsagnarstillingu"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Skipting forritastiku"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Yfirflæði á forritastiku"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Færa efst/til vinstri"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Færa neðst/til hægri"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{forrit til viðbótar}one{forrit til viðbótar}other{forrit til viðbótar}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Hunsa allt"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"stækka <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"minnka <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-it/strings.xml b/quickstep/res/values-it/strings.xml
index 445474e..0498c4e 100644
--- a/quickstep/res/values-it/strings.xml
+++ b/quickstep/res/values-it/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Blocca su schermo"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Forma libera"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nessun elemento recente"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Impostazioni di utilizzo delle app"</string>
@@ -139,8 +141,7 @@
<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="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Overflow barra delle app"</string>
<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>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Ignora tutte"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"espandi <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"comprimi <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-iw/strings.xml b/quickstep/res/values-iw/strings.xml
index 5bb51ce..ad52560 100644
--- a/quickstep/res/values-iw/strings.xml
+++ b/quickstep/res/values-iw/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"הצמדה"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"מצב חופשי"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"במחשב"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"מחשב"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"אין פריטים אחרונים"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"הגדרות שימוש באפליקציה"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"סרגל האפליקציות מוצג תמיד"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"שינוי מצב הניווט"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"המחיצה בסרגל האפליקציות"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"אפשרויות נוספות בסרגל האפליקציות"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"העברה לפינה השמאלית/העליונה"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"העברה לפינה הימנית/התחתונה"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{אפליקציה נוספת}one{אפליקציות נוספות}two{אפליקציות נוספות}other{אפליקציות נוספות}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"ביטול של הכול"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"הרחבה של <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"כיווץ של <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-ja/strings.xml b/quickstep/res/values-ja/strings.xml
index 0cff4c1..4cfe9ed 100644
--- a/quickstep/res/values-ja/strings.xml
+++ b/quickstep/res/values-ja/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"固定"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"フリーフォーム"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"デスクトップ"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"パソコン"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"最近のアイテムはありません"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"アプリの使用状況の設定"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"常にタスクバーを表示する"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"ナビゲーション モードを変更"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"タスクバーの区切り"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"タスクバーのオーバフロー"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"上 / 左に移動"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"下 / 右に移動"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{個のその他のアプリ}other{個のその他のアプリ}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"すべて解除"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>を開きます"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>を閉じます"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-ka/strings.xml b/quickstep/res/values-ka/strings.xml
index efff980..c02e9ad 100644
--- a/quickstep/res/values-ka/strings.xml
+++ b/quickstep/res/values-ka/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"ჩამაგრება"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"თავისუფალი ფორმა"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"დესკტოპი"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"დესკტოპი"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"ბოლოს გამოყენებული ერთეულები არ არის"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"აპების გამოყენების პარამეტრები"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"ამოცანათა ზოლის მუდამ ჩვენება"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"შეცვალეთ ნავიგაციის რეჟიმი"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"ამოცანათა ზოლის გამყოფი"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"ამოცანათა ზოლის გადავსება"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ზემოთ/მარცხნივ გადატანა"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ქვემოთ/მარჯვნივ გადატანა"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{სხვა აპი}other{სხვა აპი}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"ყველას დახურვა"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>-ის გაფართოება"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>-ის ჩაკეცვა"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-kk/strings.xml b/quickstep/res/values-kk/strings.xml
index 193b606..64b3e53 100644
--- a/quickstep/res/values-kk/strings.xml
+++ b/quickstep/res/values-kk/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Бекіту"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Еркін форма"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Компьютер"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Жұмыс үстелі"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Соңғы элементтер жоқ"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Қолданбаны пайдалану параметрлері"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Тапсырма жолағын үнемі көрсету"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Навигация режимін өзгерту"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Тапсырмалар жолағын бөлгіш"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"\"Тапсырмалар жолағы\" қосымша мәзірі"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Жоғары/солға жылжыту"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Төмен/оңға жылжыту"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{қосымша қолданба}other{қосымша қолданба}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Барлығын жабу"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>: жаю"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>: жию"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-km/strings.xml b/quickstep/res/values-km/strings.xml
index aadfd67..d962ba5 100644
--- a/quickstep/res/values-km/strings.xml
+++ b/quickstep/res/values-km/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"ខ្ទាស់"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"មុខងារទម្រង់សេរី"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ដែសថប"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"អេក្រង់ដើម"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"មិនមានធាតុថ្មីៗទេ"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ការកំណត់ការប្រើប្រាស់កម្មវិធី"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"បង្ហាញរបារកិច្ចការជានិច្ច"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"ប្ដូរមុខងាររុករក"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"បន្ទាត់ខណ្ឌចែករបារកិច្ចការ"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"ម៉ឺនុយបន្ថែមរបារកិច្ចការ"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ផ្លាស់ទីទៅខាងលើ/ឆ្វេង"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ផ្លាស់ទីទៅខាងក្រោម/ស្ដាំ"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{កម្មវិធីច្រើនទៀត}other{កម្មវិធីច្រើនទៀត}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"ច្រានចោលទាំងអស់"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"ពង្រីក <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"បង្រួម <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-kn/strings.xml b/quickstep/res/values-kn/strings.xml
index 9a04ffd..3a05e21 100644
--- a/quickstep/res/values-kn/strings.xml
+++ b/quickstep/res/values-kn/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"ಪಿನ್ ಮಾಡಿ"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"ಮುಕ್ತಸ್ವರೂಪ"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ಡೆಸ್ಕ್ಟಾಪ್"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"ಡೆಸ್ಕ್ಟಾಪ್"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"ಯಾವುದೇ ಇತ್ತೀಚಿನ ಐಟಂಗಳಿಲ್ಲ"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ಆ್ಯಪ್ ಬಳಕೆಯ ಸೆಟ್ಟಿಂಗ್ಗಳು"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"ಯಾವಾಗಲೂ ಟಾಸ್ಕ್ಬಾರ್ ತೋರಿಸಿ"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"ನ್ಯಾವಿಗೇಶನ್ ಮೋಡ್ ಬದಲಾಯಿಸಿ"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"ಟಾಸ್ಕ್ಬಾರ್ ಡಿವೈಡರ್"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"ಟಾಸ್ಕ್ ಬಾರ್ ಓವರ್ಫ್ಲೋ"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ಮೇಲಿನ/ಎಡಭಾಗಕ್ಕೆ ಸರಿಸಿ"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ಕೆಳಗಿನ/ಬಲಭಾಗಕ್ಕೆ ಸರಿಸಿ"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{ಹೆಚ್ಚಿನ ಆ್ಯಪ್}one{ಹೆಚ್ಚಿನ ಆ್ಯಪ್ಗಳು}other{ಹೆಚ್ಚಿನ ಆ್ಯಪ್ಗಳು}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"ಎಲ್ಲವನ್ನು ವಜಾಗೊಳಿಸಿ"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ಅನ್ನು ವಿಸ್ತೃತಗೊಳಿಸಿ"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ಅನ್ನು ಕುಗ್ಗಿಸಿ"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-ko/strings.xml b/quickstep/res/values-ko/strings.xml
index 38d5bbb..6a7ba74 100644
--- a/quickstep/res/values-ko/strings.xml
+++ b/quickstep/res/values-ko/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"고정"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"자유 형식"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"데스크톱"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"데스크톱"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"최근 항목이 없습니다."</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"앱 사용 설정"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"태스크 바 항상 표시"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"탐색 모드 변경"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"태스크 바 분할"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"태스크 바 오버플로"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"상단/왼쪽으로 이동"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"하단/오른쪽으로 이동"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{추가 앱}other{추가 앱}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"모두 닫기"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> 펼치기"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> 접기"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-ky/strings.xml b/quickstep/res/values-ky/strings.xml
index 8eba2a5..e92b920 100644
--- a/quickstep/res/values-ky/strings.xml
+++ b/quickstep/res/values-ky/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Кадап коюу"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Эркин форма режими"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Компьютер"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Компьютер"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Акыркы колдонмолор жок"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Колдонмону пайдалануу параметрлери"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Такта ар дайым көрүнсүн"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Өтүү режимин өзгөртүү"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Тапшырмалар панелин бөлгүч"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"\"Тапшырмалар панели\" кошумча менюсу"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Жогорку/сол бурчка жылдыруу"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Төмөнкү/оң бурчка жылдыруу"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{колдонмо бар}other{колдонмо бар}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Баарын четке кагуу"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> жайып көрсөтүү"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> жыйыштыруу"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-lo/strings.xml b/quickstep/res/values-lo/strings.xml
index 284cdc7..f45c8ce 100644
--- a/quickstep/res/values-lo/strings.xml
+++ b/quickstep/res/values-lo/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"ປັກໝຸດ"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"ຮູບແບບອິດສະຫລະ"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ເດັສທັອບ"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"ເດັສທັອບ"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"ບໍ່ມີລາຍການຫຼ້າສຸດ"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ການຕັ້ງຄ່າການນຳໃຊ້ແອັບ"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"ສະແດງແຖບໜ້າວຽກສະເໝີ"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"ປ່ຽນໂໝດການນຳທາງ"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"ເສັ້ນແບ່ງແຖບໜ້າວຽກ"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"ສ່ວນເພີ່ມເຕີມຂອງແຖບໜ້າວຽກ"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ຍ້າຍໄປຊ້າຍ/ເທິງ"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ຍ້າຍໄປຂວາ/ລຸ່ມ"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{ແອັບເພີ່ມເຕີມ}other{ແອັບເພີ່ມເຕີມ}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"ປິດທັງໝົດ"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"ຂະຫຍາຍ <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"ຫຍໍ້ <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ລົງ"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-lt/strings.xml b/quickstep/res/values-lt/strings.xml
index 1c20015..c488859 100644
--- a/quickstep/res/values-lt/strings.xml
+++ b/quickstep/res/values-lt/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Prisegti"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Laisva forma"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Stalinis kompiuteris"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Stalinis kompiuteris"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nėra jokių naujausių elementų"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Programos naudojimo nustatymai"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Visada rodyti užduočių juostą"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Keisti naršymo režimą"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Užduočių juostos daliklis"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Užduočių juostos perpildymas"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Perkelti aukštyn, kairėn"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Perkelti žemyn, dešinėn"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{papildoma programa}one{papildoma programa}few{papildomos programos}many{papildomos programos}other{papildomų programų}}"</string>
@@ -155,4 +156,5 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Atsisakyti visų"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"išskleisti „<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>“"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"sutraukti „<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>“"</string>
+ <string name="search_gesture_feature_title" msgid="1294044108313175306">"Paieška apibrėžiant"</string>
</resources>
diff --git a/quickstep/res/values-lv/strings.xml b/quickstep/res/values-lv/strings.xml
index 408f3c3..23e64a7 100644
--- a/quickstep/res/values-lv/strings.xml
+++ b/quickstep/res/values-lv/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Piespraust"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Brīva forma"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Dators"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Darbvirsma"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nav nesenu vienumu."</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Lietotņu izmantošanas iestatījumi"</string>
@@ -139,8 +141,7 @@
<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="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Uzdevumu joslas pārpilde"</string>
<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>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Nerādīt nevienu"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"izvērst “<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>”"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"sakļaut “<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>”"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-mk/strings.xml b/quickstep/res/values-mk/strings.xml
index 588578e..f0588e8 100644
--- a/quickstep/res/values-mk/strings.xml
+++ b/quickstep/res/values-mk/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Закачи"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Работна површина"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"За компјутер"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Нема неодамнешни ставки"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Поставки за користење на апликациите"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Секогаш прикажувај „Лента“"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Променете режим на навигација"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Разделник на „Лента со задачи“"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Проширено балонче на „Лента со задачи“"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Премести горе лево"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Премести долу десно"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{дополнителна апликација}one{дополнителна апликација}other{дополнителни апликации}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Отфрли ги сите"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"прошири <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"собери <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-ml/strings.xml b/quickstep/res/values-ml/strings.xml
index bb5c014..833090c 100644
--- a/quickstep/res/values-ml/strings.xml
+++ b/quickstep/res/values-ml/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"പിൻ ചെയ്യുക"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"ഫ്രീഫോം"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ഡെസ്ക്ടോപ്പ്"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"ഡെസ്ക്ടോപ്പ്"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"സമീപകാല ഇനങ്ങൾ ഒന്നുമില്ല"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ആപ്പ് ഉപയോഗ ക്രമീകരണം"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"ടാസ്ക്ബാർ എപ്പോഴും കാണിക്കൂ"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"നാവിഗേഷൻ മോഡ് മാറ്റുക"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"ടാസ്ക്ബാർ ഡിവൈഡർ"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"ടാസ്ക്ബാർ ഓവർഫ്ലോ"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"മുകളിലേക്കോ ഇടത്തേക്കോ നീക്കുക"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"താഴേക്കോ വലത്തേക്കോ നീക്കുക"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{കൂടുതൽ ആപ്പ്}other{കൂടുതൽ ആപ്പുകൾ}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"എല്ലാം ഡിസ്മിസ് ചെയ്യുക"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> വികസിപ്പിക്കുക"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ചുരുക്കുക"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-mn/strings.xml b/quickstep/res/values-mn/strings.xml
index 9223139..100b8ed 100644
--- a/quickstep/res/values-mn/strings.xml
+++ b/quickstep/res/values-mn/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Бэхлэх"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Чөлөөтэй хувьсах"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Компьютер"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Дэлгэц"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Сүүлийн үеийн зүйл алга"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Апп ашиглалтын тохиргоо"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Ажлын хэсгийг үргэлж харуулах"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Навигацын горимыг өөрчлөх"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Ажлын хэсгийг хуваагч"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Ажлын хэсгийн урт цэс"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Зүүн дээд хэсэг рүү зөөх"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Баруун доод хэсэг рүү зөөх"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{бусад апп}other{бусад апп}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Бүгдийг үл хэрэгсэх"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>-г дэлгэх"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>-г хураах"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-mr/strings.xml b/quickstep/res/values-mr/strings.xml
index 3eef060..870cf4e 100644
--- a/quickstep/res/values-mr/strings.xml
+++ b/quickstep/res/values-mr/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"पिन करा"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"फ्रीफॉर्म"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"डेस्कटॉप"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"डेस्कटॉप"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"कोणतेही अलीकडील आयटम नाहीत"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"अॅप वापर सेटिंग्ज"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"नेहमी टास्कबार दाखवा"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"नेव्हिगेशन मोड बदला"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"टास्कबार विभाजक"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"टास्कबार ओव्हरफ्लो"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"सर्वात वरती/डावीकडे हलवा"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"तळाशी/उजवीकडे हलवा"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{आणखी अॅप}other{आणखी अॅप्स}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"सर्व डिसमिस करा"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> चा विस्तार करा"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> कोलॅप्स करा"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-ms/strings.xml b/quickstep/res/values-ms/strings.xml
index 9bdc973..bed2fce 100644
--- a/quickstep/res/values-ms/strings.xml
+++ b/quickstep/res/values-ms/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Semat"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Bentuk bebas"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Tiada item terbaharu"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Tetapan penggunaan apl"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Papar Bar Tugas selalu"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Tukar mod navigasi"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Pembahagi Bar Tugas"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Limpahan Bar Tugas"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Alihkan ke atas/kiri"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Alihkan ke bawah/kanan"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{apl lagi}other{apl lagi}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Ketepikan semua"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"kembangkan <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"kuncupkan <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-my/strings.xml b/quickstep/res/values-my/strings.xml
index 6ea946a..ae0f66d 100644
--- a/quickstep/res/values-my/strings.xml
+++ b/quickstep/res/values-my/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"ပင်ထိုးရန်"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"အလွတ်ပုံစံ"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ဒက်စ်တော့"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"ဒက်စ်တော့"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"မကြာမီကဖွင့်ထားသည်များ မရှိပါ"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"အက်ပ်အသုံးပြုမှု ဆက်တင်များ"</string>
@@ -139,8 +141,7 @@
<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="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Taskbar မီနူးအပို"</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>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"အားလုံးကို ပယ်ရန်"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ကို ပိုပြပါ"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ကို လျှော့ပြပါ"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-nb/strings.xml b/quickstep/res/values-nb/strings.xml
index 33cde5a..1ce18c1 100644
--- a/quickstep/res/values-nb/strings.xml
+++ b/quickstep/res/values-nb/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Fest"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Fritt format"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Skrivebord"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Skrivebord"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Ingen nylige elementer"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Innstillinger for appbruk"</string>
@@ -139,8 +141,7 @@
<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="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Overflyt for oppgavelinjen"</string>
<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>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Lukk alle"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"vis <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"skjul <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-ne/strings.xml b/quickstep/res/values-ne/strings.xml
index 4c9747f..1921fed 100644
--- a/quickstep/res/values-ne/strings.xml
+++ b/quickstep/res/values-ne/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"पिन गर्नुहोस्"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"फ्रिफर्म"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"डेस्कटप"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"डेस्कटप"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"हालसालैको कुनै पनि वस्तु छैन"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"एपको उपयोगका सेटिङहरू"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"टास्कबार सधैँ देखाउनुहोस्"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"नेभिगेसन मोड बदल्नुहोस्"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"टास्कबार डिभाइडर"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"टास्कबार ओभरफ्लो"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"सिरान/बायाँतिर सार्नुहोस्"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"फेद/दायाँतिर सार्नुहोस्"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{थप एप}other{थप एपहरू}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"सबै हटाउनुहोस्"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> एक्स्पान्ड गर्नुहोस्"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> कोल्याप्स गर्नुहोस्"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-nl/strings.xml b/quickstep/res/values-nl/strings.xml
index a4949ac..995d8d4 100644
--- a/quickstep/res/values-nl/strings.xml
+++ b/quickstep/res/values-nl/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Vastzetten"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Vrije vorm"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Geen recente items"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Instellingen voor app-gebruik"</string>
@@ -139,8 +141,7 @@
<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="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Taakbalkoverloop"</string>
<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>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Alles sluiten"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> uitvouwen"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> samenvouwen"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-or/strings.xml b/quickstep/res/values-or/strings.xml
index 7753153..430058b 100644
--- a/quickstep/res/values-or/strings.xml
+++ b/quickstep/res/values-or/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"ପିନ୍"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"ଫ୍ରିଫର୍ମ"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ଡେସ୍କଟପ"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"ଡେସ୍କଟପ"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"ବର୍ତ୍ତମାନର କୌଣସି ଆଇଟମ ନାହିଁ"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ଆପ ବ୍ୟବହାର ସେଟିଂସ"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"ସର୍ବଦା ଟାସ୍କବାର ଦେଖାନ୍ତୁ"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"ନାଭିଗେସନ ମୋଡ ପରିବର୍ତ୍ତନ କରନ୍ତୁ"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"ଟାସ୍କବାର ଡିଭାଇଡର"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"ଟାସ୍କବାର ଓଭରଫ୍ଲୋ"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ଶୀର୍ଷ/ବାମକୁ ମୁଭ କରନ୍ତୁ"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ନିମ୍ନ/ଡାହାଣକୁ ମୁଭ କରନ୍ତୁ"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{ଅଧିକ ଆପ}other{ଅଧିକ ଆପ୍ସ}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"ସବୁ ଖାରଜ କରନ୍ତୁ"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ବିସ୍ତାର କରନ୍ତୁ"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ସଙ୍କୁଚିତ କରନ୍ତୁ"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-pa/strings.xml b/quickstep/res/values-pa/strings.xml
index 011fa6e..d8d0907 100644
--- a/quickstep/res/values-pa/strings.xml
+++ b/quickstep/res/values-pa/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"ਪਿੰਨ ਕਰੋ"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"ਫ੍ਰੀਫਾਰਮ"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ਡੈਸਕਟਾਪ"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"ਡੈਸਕਟਾਪ"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"ਕੋਈ ਹਾਲੀਆ ਆਈਟਮ ਨਹੀਂ"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ਐਪ ਵਰਤੋਂ ਦੀਆਂ ਸੈਟਿੰਗਾਂ"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"ਹਮੇਸ਼ਾਂ ਟਾਸਕਬਾਰ ਦਿਖਾਓ"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"ਨੈਵੀਗੇਸ਼ਨ ਮੋਡ ਬਦਲੋ"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"ਟਾਸਕਬਾਰ ਵਿਭਾਜਕ"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"ਟਾਸਕਬਾਰ ਓਵਰਫ਼ਲੋ"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ਸਿਖਰਲੇ/ਖੱਬੇ ਪਾਸੇ ਲੈ ਕੇ ਜਾਓ"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ਹੇਠਾਂ/ਸੱਜੇ ਪਾਸੇ ਲੈ ਕੇ ਜਾਓ"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{ਹੋਰ ਐਪ}one{ਹੋਰ ਐਪ}other{ਹੋਰ ਐਪਾਂ}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"ਸਭ ਖਾਰਜ ਕਰੋ"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ਦਾ ਵਿਸਤਾਰ ਕਰੋ"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ਨੂੰ ਸਮੇਟੋ"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-pl/strings.xml b/quickstep/res/values-pl/strings.xml
index df0f74c..957e5c4 100644
--- a/quickstep/res/values-pl/strings.xml
+++ b/quickstep/res/values-pl/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Przypnij"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Tryb dowolny"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Pulpit"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Pulpit"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Brak ostatnich elementów"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Ustawienia użycia aplikacji"</string>
@@ -139,8 +141,7 @@
<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="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Rozwijany pasek aplikacji"</string>
<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>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Zamknij wszystkie"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"rozwiń dymek: <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"zwiń dymek: <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-pt-rPT/strings.xml b/quickstep/res/values-pt-rPT/strings.xml
index 5ad32af..ca7cd58 100644
--- a/quickstep/res/values-pt-rPT/strings.xml
+++ b/quickstep/res/values-pt-rPT/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Fixar"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Forma livre"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Computador"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Computador"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nenhum item recente"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Definições de utilização de aplicações"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Ver sempre Barra de tarefas"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Alterar modo de navegação"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Divisor da Barra de tarefas"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Menu adicional da Barra de tarefas"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Mover para a parte superior esquerda"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mover para a part superior direita"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{outra app}other{outras apps}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Ignorar tudo"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"expandir <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"reduzir <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-pt/strings.xml b/quickstep/res/values-pt/strings.xml
index 28b3414..aee1c8d 100644
--- a/quickstep/res/values-pt/strings.xml
+++ b/quickstep/res/values-pt/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Fixar"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Forma livre"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Modo área de trabalho"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Computador"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nenhum item recente"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Configurações de uso do app"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Sempre mostrar a Barra de tarefas"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Mudar o modo de navegação"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Separador da Barra de tarefas"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Barra de tarefas flutuante"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Mover para cima/para a esquerda"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mover para baixo/para a direita"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{outro app}one{outro app}other{outros apps}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Dispensar todos"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"abrir <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"fechar <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-ro/strings.xml b/quickstep/res/values-ro/strings.xml
index 6bd3e0f..c05b85b 100644
--- a/quickstep/res/values-ro/strings.xml
+++ b/quickstep/res/values-ro/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Fixează"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Formă liberă"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Computer"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Niciun element recent"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Setări de utilizare a aplicației"</string>
@@ -139,8 +141,7 @@
<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="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Meniu suplimentar pentru bara de activități"</string>
<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>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Închide-le pe toate"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"extinde <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"restrânge <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-ru/strings.xml b/quickstep/res/values-ru/strings.xml
index 20f579d..fca0a05 100644
--- a/quickstep/res/values-ru/strings.xml
+++ b/quickstep/res/values-ru/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Закрепить"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Произвольная форма"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Мультиоконный режим"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Мультиоконный режим"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Здесь пока ничего нет."</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Настройки использования приложения"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Всегда показывать панель задач"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Изменить режим навигации"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Разделитель панели задач"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Дополнительное меню панели задач"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Переместить вверх или влево"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Переместить вниз или вправо"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{дополнительное приложение}one{дополнительное приложение}few{дополнительных приложения}many{дополнительных приложений}other{дополнительного приложения}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Закрыть все"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"Развернуто: <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"Свернуто: <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-si/strings.xml b/quickstep/res/values-si/strings.xml
index dd9739c..cfbf1dd 100644
--- a/quickstep/res/values-si/strings.xml
+++ b/quickstep/res/values-si/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"අමුණන්න"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ඩෙස්ක්ටොපය"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"ඩෙස්ක්ටොපය"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"මෑත අයිතම නැත"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"යෙදුම් භාවිත සැකසීම්"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"සෑම විටම කාර්ය තීරුව පෙන්වන්න"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"සංචාලන ප්රකාරය වෙනස් කරන්න"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"කාර්ය තීරු බෙදනය"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"කාර්ය තීරුව පිටාර යාම"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ඉහළ/වම වෙත ගෙන යන්න"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"පහළ/දකුණ වෙත ගෙන යන්න"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{තව යෙදුම}one{තවත් යෙදුම්}other{තවත් යෙදුම්}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"සියල්ල ඉවතලන්න"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> දිග හරින්න"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> හකුළන්න"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-sk/strings.xml b/quickstep/res/values-sk/strings.xml
index 6241ad2..b3145de 100644
--- a/quickstep/res/values-sk/strings.xml
+++ b/quickstep/res/values-sk/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Pripnúť"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Voľný režim"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Počítač"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Počítač"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Žiadne nedávne položky"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Nastavenia využívania aplikácie"</string>
@@ -139,8 +141,7 @@
<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="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Rozšírená ponuka panela aplikácií"</string>
<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>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Zavrieť všetko"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"rozbaliť <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"zbaliť <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-sl/strings.xml b/quickstep/res/values-sl/strings.xml
index 9394a5a..d72436a 100644
--- a/quickstep/res/values-sl/strings.xml
+++ b/quickstep/res/values-sl/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Pripni"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Prosta oblika"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Namizni računalnik"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Namizni način"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Ni nedavnih elementov"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Nastavitve uporabe aplikacij"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Stalen prikaz oprav. vrstice"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Spreminjanje načina navigacije"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Razdelilnik opravilne vrstice"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Oblaček opravilne vrstice z dodatnimi elementi"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Premakni na vrh/levo"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Premakni na dno/desno"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{dodatna aplikacija}one{dodatna aplikacija}two{dodatni aplikaciji}few{dodatne aplikacije}other{dodatnih aplikacij}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Opusti vse"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"razširitev oblačka <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"strnitev oblačka <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-sq/strings.xml b/quickstep/res/values-sq/strings.xml
index 12635c6..30ba61d 100644
--- a/quickstep/res/values-sq/strings.xml
+++ b/quickstep/res/values-sq/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Gozhdo"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Formë e lirë"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktopi"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktopi"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nuk ka asnjë artikull të fundit"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Cilësimet e përdorimit të aplikacionit"</string>
@@ -139,8 +141,7 @@
<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="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Tejkalimi i shiritit të detyrave"</string>
<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>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Hiqi të gjitha"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"zgjero <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"palos <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-sr/strings.xml b/quickstep/res/values-sr/strings.xml
index a7760b9..b83a4f2 100644
--- a/quickstep/res/values-sr/strings.xml
+++ b/quickstep/res/values-sr/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Закачи"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Слободни облик"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Рачунар"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Рачунари"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Нема недавних ставки"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Подешавања коришћења апликације"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Увек приказуј траку задатака"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Промени режим навигације"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Разделник траке задатака"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Преклопна трака задатака"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Премести горе лево"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Премести доле десно"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{додатна апликација}one{додатна апликација}few{додатне апликације}other{додатних апликација}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Одбаци све"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"проширите облачић <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"скупите облачић <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-sv/strings.xml b/quickstep/res/values-sv/strings.xml
index d4c3a69..5ab3866 100644
--- a/quickstep/res/values-sv/strings.xml
+++ b/quickstep/res/values-sv/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Fäst"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Fritt format"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Dator"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Dator"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Listan är tom"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Inställningar för appanvändning"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Visa alltid aktivitetsfältet"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Ändra navigeringsläge"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Avdelare för aktivitetsfältet"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Fler alternativ för aktivitetsfältet"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Flytta högst upp/till vänster"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Flytta längst ned/till höger"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{app till}other{appar till}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Stäng alla"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"utöka <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"komprimera <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-sw/strings.xml b/quickstep/res/values-sw/strings.xml
index 5b74600..9d13df8 100644
--- a/quickstep/res/values-sw/strings.xml
+++ b/quickstep/res/values-sw/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Bandika"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Muundo huru"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Kompyuta ya mezani"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Kompyuta ya Mezani"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Hakuna vipengee vya hivi karibuni"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Mipangilio ya matumizi ya programu"</string>
@@ -139,8 +141,7 @@
<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="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Upauzana wa Vipengele vya Ziada"</string>
<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>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Ondoa vyote"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"panua <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"kunja <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-ta/strings.xml b/quickstep/res/values-ta/strings.xml
index 39dc6bd..8ac0ff8 100644
--- a/quickstep/res/values-ta/strings.xml
+++ b/quickstep/res/values-ta/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"பின் செய்தல்"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"குறிப்பிட்ட வடிவமில்லாத பயன்முறை"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"டெஸ்க்டாப்"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"டெஸ்க்டாப்"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"சமீபத்தியவை எதுவுமில்லை"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ஆப்ஸ் உபயோக அமைப்புகள்"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"செயல் பட்டியை எப்போதும் காட்டு"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"வழிசெலுத்தல் பயன்முறையை மாற்று"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"செயல் பட்டிப் பிரிப்பான்"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"செயல் பட்டிக்கான கூடுதல் விருப்பங்கள்"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"மேலே/இடதுபுறம் நகர்த்தும்"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"கீழே/வலதுபுறம் நகர்த்தும்"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{கூடுதல் ஆப்ஸ்}other{கூடுதல் ஆப்ஸ்}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"அனைத்தையும் மூடும்"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ஐ விரிவாக்கும்"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ஐச் சுருக்கும்"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-te/strings.xml b/quickstep/res/values-te/strings.xml
index 86161b4..4ae7a58 100644
--- a/quickstep/res/values-te/strings.xml
+++ b/quickstep/res/values-te/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"పిన్ చేయండి"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"సంప్రదాయేతర"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"డెస్క్టాప్"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"డెస్క్టాప్"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"ఇటీవలి ఐటెమ్లు ఏవీ లేవు"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"యాప్ వినియోగ సెట్టింగ్లు"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"టాస్క్బార్ను నిరంతరం చూపండి"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"నావిగేషన్ మోడ్ను మార్చండి"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"టాస్క్బార్ డివైడర్"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"టాస్క్బార్ ఓవర్ఫ్లో"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ఎగువ/ఎడమ వైపునకు తరలించండి"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"దిగువ/కుడి వైపునకు తరలించండి"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{మరో యాప్}other{మరిన్ని యాప్లు}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"అన్నింటినీ విస్మరించండి"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>ను విస్తరించండి"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>ను కుదించండి"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-th/strings.xml b/quickstep/res/values-th/strings.xml
index c6fddfb..210e996 100644
--- a/quickstep/res/values-th/strings.xml
+++ b/quickstep/res/values-th/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"ปักหมุด"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"รูปแบบอิสระ"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"เดสก์ท็อป"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"เดสก์ท็อป"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"ไม่มีรายการล่าสุด"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"การตั้งค่าการใช้แอป"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"แสดงแถบงานเสมอ"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"เปลี่ยนโหมดการนําทาง"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"ตัวแบ่งแถบงาน"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"การดำเนินการเพิ่มเติมของแถบงาน"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ย้ายไปที่ด้านบนหรือด้านซ้าย"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ย้ายไปที่ด้านล่างหรือด้านขวา"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{แอปเพิ่มเติม}other{แอปเพิ่มเติม}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"ปิดทั้งหมด"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"ขยาย <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"ยุบ <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-tl/strings.xml b/quickstep/res/values-tl/strings.xml
index 91f9675..13d89a2 100644
--- a/quickstep/res/values-tl/strings.xml
+++ b/quickstep/res/values-tl/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"I-pin"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Walang kamakailang item"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Mga setting ng paggamit ng app"</string>
@@ -139,8 +141,7 @@
<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="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Taskbar Overflow"</string>
<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>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"I-dismiss lahat"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"i-expand ang <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"i-collapse ang <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-tr/strings.xml b/quickstep/res/values-tr/strings.xml
index f5f98bb..1cb1fa7 100644
--- a/quickstep/res/values-tr/strings.xml
+++ b/quickstep/res/values-tr/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Sabitle"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Serbest çalışma"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Masaüstü"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Masaüstü"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Yeni öğe yok"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Uygulama kullanım ayarları"</string>
@@ -139,8 +141,7 @@
<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="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Görev Çubuğu Taşması"</string>
<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>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Tümünü kapat"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"genişlet: <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"daralt: <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-uk/strings.xml b/quickstep/res/values-uk/strings.xml
index 93d974f..84ddd81 100644
--- a/quickstep/res/values-uk/strings.xml
+++ b/quickstep/res/values-uk/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Закріпити"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Довільна форма"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Робочий стіл"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Комп’ютер"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Немає нещодавніх додатків"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Налаштування використання додатка"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"Завжди показув. панель завдань"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Змінити режим навігації"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Розділювач панелі завдань"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Додаткове меню панелі завдань"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Перемістити вгору або вліво"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Перемістити вниз або вправо"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{інший додаток}one{інший додаток}few{інші додатки}many{інших додатків}other{іншого додатка}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Закрити все"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"розгорнути \"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>\""</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"згорнути \"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>\""</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-ur/strings.xml b/quickstep/res/values-ur/strings.xml
index 76a2c81..43e97c2 100644
--- a/quickstep/res/values-ur/strings.xml
+++ b/quickstep/res/values-ur/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"پن کریں"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"فری فارم"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ڈیسک ٹاپ"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"ڈیسک ٹاپ"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"کوئی حالیہ آئٹم نہیں"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ایپ کے استعمال کی ترتیبات"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"ہمیشہ ٹاسک بار دکھائیں"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"نیویگیشن موڈ تبدیل کریں"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"ٹاسک بار ڈیوائیڈر"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"ٹاسک بار اوورفلو"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"اوپر/بائیں طرف منتقل کریں"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"نیچے/دائیں طرف منتقل کریں"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{مزید ایپ}other{مزید ایپس}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"سبھی کو برخاست کریں"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> کو پھیلائیں"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> کو سکیڑیں"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-uz/strings.xml b/quickstep/res/values-uz/strings.xml
index 0d6c09d..090753f 100644
--- a/quickstep/res/values-uz/strings.xml
+++ b/quickstep/res/values-uz/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Qadash"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Erkin shakl"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Yaqinda ishlatilgan ilovalar yo‘q"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Ilovadan foydalanish sozlamalari"</string>
@@ -139,8 +141,7 @@
<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="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Vazifalar panelini kengaytirish"</string>
<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>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Hammasini yopish"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>ni yoyish"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>ni yigʻish"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-vi/strings.xml b/quickstep/res/values-vi/strings.xml
index cf38392..b0d31cb 100644
--- a/quickstep/res/values-vi/strings.xml
+++ b/quickstep/res/values-vi/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Ghim"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Dạng tự do"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Máy tính"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Máy tính"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Không có mục gần đây nào"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Cài đặt mức sử dụng ứng dụng"</string>
@@ -139,8 +141,7 @@
<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="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Trình đơn mục bổ sung trên thanh tác vụ"</string>
<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>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Đóng tất cả"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"mở rộng <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"thu gọn <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-zh-rCN/strings.xml b/quickstep/res/values-zh-rCN/strings.xml
index 9017482..73121b4 100644
--- a/quickstep/res/values-zh-rCN/strings.xml
+++ b/quickstep/res/values-zh-rCN/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"固定"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"自由窗口"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"桌面"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"桌面设备"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"近期没有任何内容"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"应用使用设置"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"始终显示任务栏"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"更改导航模式"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"任务栏分隔线"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"溢出式任务栏"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"移到顶部/左侧"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"移到底部/右侧"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{多个应用}other{多个应用}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"全部关闭"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"展开“<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>”"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"收起“<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>”"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-zh-rHK/strings.xml b/quickstep/res/values-zh-rHK/strings.xml
index 672a61c..b934dfc 100644
--- a/quickstep/res/values-zh-rHK/strings.xml
+++ b/quickstep/res/values-zh-rHK/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"固定"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"自由形式"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"桌面"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"桌面"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"最近沒有任何項目"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"應用程式使用情況設定"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"一律顯示工作列"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"變更導覽模式"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"工作列分隔線"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"工作列溢位"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"移至上方/左側"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"移至底部/右側"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{個其他應用程式}other{個其他應用程式}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"全部關閉"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"打開<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"收埋<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-zh-rTW/strings.xml b/quickstep/res/values-zh-rTW/strings.xml
index 257c996..1cba819 100644
--- a/quickstep/res/values-zh-rTW/strings.xml
+++ b/quickstep/res/values-zh-rTW/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"固定"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"自由形式"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"桌面"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"電腦"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"最近沒有任何項目"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"應用程式使用情況設定"</string>
@@ -139,8 +141,7 @@
<string name="always_show_taskbar" msgid="3608801276107751229">"一律顯示工作列"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"變更操作模式"</string>
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"工作列分隔線"</string>
- <!-- no translation found for taskbar_overflow_a11y_title (7960342079198820179) -->
- <skip />
+ <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"工作列溢位"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"移到上方/左側"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"移到底部/右側"</string>
<string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{個其他應用程式}other{個其他應用程式}}"</string>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"全部關閉"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"展開「<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>」"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"收合「<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>」"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values-zu/strings.xml b/quickstep/res/values-zu/strings.xml
index 9d7ecd7..ee6b3b7 100644
--- a/quickstep/res/values-zu/strings.xml
+++ b/quickstep/res/values-zu/strings.xml
@@ -22,6 +22,8 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Phina"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"I-Freeform"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Ideskithophu"</string>
+ <!-- no translation found for recent_task_option_external_display (4533840664313389484) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Ideskithophu"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Azikho izinto zakamuva"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Izilungiselelo zokusetshenziswa kohlelo lokusebenza"</string>
@@ -139,8 +141,7 @@
<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="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Ukuphuphuma Kwetaskbar"</string>
<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>
@@ -155,4 +156,6 @@
<string name="bubble_bar_action_dismiss_all" msgid="3290722022983403060">"Chitha konke"</string>
<string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"nweba <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
<string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"goqa <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
+ <!-- no translation found for search_gesture_feature_title (1294044108313175306) -->
+ <skip />
</resources>
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index e8cb5d5..41b2384 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -37,8 +37,8 @@
<string name="taskbar_edu_tooltip_controller_class" translatable="false">com.android.launcher3.taskbar.TaskbarEduTooltipController</string>
<string name="contextual_edu_manager_class" translatable="false">com.android.quickstep.contextualeducation.SystemContextualEduStatsManager</string>
<string name="nav_handle_long_press_handler_class" translatable="false"></string>
- <string name="assist_utils_class" translatable="false"></string>
- <string name="assist_state_manager_class" translatable="false"></string>
+ <string name="contextual_search_invoker_class" translatable="false"></string>
+ <string name="contextual_search_state_manager_class" translatable="false"></string>
<string name="api_wrapper_class" translatable="false">com.android.launcher3.uioverrides.SystemApiWrapper</string>
<!-- The number of thumbnails and icons to keep in the cache. The thumbnail cache size also
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 008766b..026e25c 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -28,6 +28,8 @@
<string name="recent_task_option_freeform">Freeform</string>
<!-- Title and content description for an option to enter desktop windowing mode for a given app -->
<string name="recent_task_option_desktop">Desktop</string>
+ <!-- Title and content description for an option to move app to external display. -->
+ <string name="recent_task_option_external_display">Move to external display</string>
<!-- Title and content description for Desktop tile in Recents screen that contains apps opened inside desktop windowing mode [CHAR LIMIT=NONE] -->
<string name="recent_task_desktop">Desktop</string>
@@ -360,4 +362,8 @@
<string name="bubble_bar_accessibility_announce_expand">expand <xliff:g id="bubble_description" example="some title from Messages">%1$s</xliff:g></string>
<!-- Accessibility announcement when the bubble bar collapses. [CHAR LIMIT=NONE]-->
<string name="bubble_bar_accessibility_announce_collapse">collapse <xliff:g id="bubble_description" example="some title from Messages">%1$s</xliff:g></string>
+
+ <!-- Name of Google's new feature to circle to search anything on your phone screen, without
+ switching apps. [CHAR_LIMIT=60] -->
+ <string name="search_gesture_feature_title">Circle to Search</string>
</resources>
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/desktop/DesktopRecentsTransitionController.kt b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
index f7da34a..ac1ffa6 100644
--- a/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
@@ -20,6 +20,7 @@
import android.os.RemoteException
import android.util.Log
import android.view.SurfaceControl
+import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.IRemoteTransitionFinishedCallback
import android.window.RemoteTransition
import android.window.RemoteTransitionStub
@@ -30,6 +31,7 @@
import com.android.quickstep.SystemUiProxy
import com.android.quickstep.TaskViewUtils
import com.android.quickstep.views.DesktopTaskView
+import com.android.window.flags.Flags
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
import java.util.function.Consumer
@@ -38,14 +40,14 @@
private val stateManager: StateManager<*, *>,
private val systemUiProxy: SystemUiProxy,
private val appThread: IApplicationThread,
- private val depthController: DepthController?
+ private val depthController: DepthController?,
) {
/** Launch desktop tasks from recents view */
fun launchDesktopFromRecents(
desktopTaskView: DesktopTaskView,
animated: Boolean,
- callback: Consumer<Boolean>? = null
+ callback: Consumer<Boolean>? = null,
) {
val animRunner =
RemoteDesktopLaunchTransitionRunner(
@@ -53,7 +55,7 @@
animated,
stateManager,
depthController,
- callback
+ callback,
)
val transition = RemoteTransition(animRunner, appThread, "RecentsToDesktop")
systemUiProxy.showDesktopApps(desktopTaskView.display.displayId, transition)
@@ -64,19 +66,24 @@
systemUiProxy.moveToDesktop(taskId, transitionSource)
}
+ /** Move task to external display from recents view */
+ fun moveToExternalDisplay(taskId: Int) {
+ systemUiProxy.moveToExternalDisplay(taskId)
+ }
+
private class RemoteDesktopLaunchTransitionRunner(
private val desktopTaskView: DesktopTaskView,
private val animated: Boolean,
private val stateManager: StateManager<*, *>,
private val depthController: DepthController?,
- private val successCallback: Consumer<Boolean>?
+ private val successCallback: Consumer<Boolean>?,
) : RemoteTransitionStub() {
override fun startAnimation(
token: IBinder,
info: TransitionInfo,
t: SurfaceControl.Transaction,
- finishCallback: IRemoteTransitionFinishedCallback
+ finishCallback: IRemoteTransitionFinishedCallback,
) {
val errorHandlingFinishCallback = Runnable {
try {
@@ -86,6 +93,9 @@
}
}
+ if (Flags.enableDesktopWindowingPersistence()) {
+ handleAnimationAfterReboot(info)
+ }
MAIN_EXECUTOR.execute {
val animator =
TaskViewUtils.composeRecentsDesktopLaunchAnimator(
@@ -93,7 +103,7 @@
stateManager,
depthController,
info,
- t
+ t,
) {
errorHandlingFinishCallback.run()
successCallback?.accept(true)
@@ -104,6 +114,26 @@
animator.start()
}
}
+
+ /**
+ * Upon reboot the start bounds of a task is set to fullscreen with the recents transition.
+ * Check this case and set the start bounds to the end bounds so that the window doesn't
+ * jump from start bounds to end bounds during the animation. Tasks in desktop cannot
+ * normally have top bound as 0 due to status bar so this is a good indicator to identify
+ * reboot case.
+ */
+ private fun handleAnimationAfterReboot(info: TransitionInfo) {
+ info.changes.forEach { change ->
+ if (
+ change.mode == TRANSIT_TO_FRONT &&
+ change.taskInfo?.isFreeform == true &&
+ change.startAbsBounds.top == 0 &&
+ change.startAbsBounds.left == 0
+ ) {
+ change.setStartAbsBounds(change.endAbsBounds)
+ }
+ }
+ }
}
companion object {
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index 6af5a30..29e1f4e 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -77,6 +77,7 @@
import com.android.launcher3.util.PersistedItemArray;
import com.android.quickstep.logging.SettingsChangeLogger;
import com.android.quickstep.logging.StatsLogCompatManager;
+import com.android.quickstep.util.ContextualSearchStateManager;
import com.android.systemui.shared.system.SysUiStatsLog;
import java.util.ArrayList;
@@ -209,6 +210,8 @@
@Override
public void workspaceLoadComplete() {
super.workspaceLoadComplete();
+ // Initialize ContextualSearchStateManager.
+ ContextualSearchStateManager.INSTANCE.get(mContext);
recreatePredictors();
}
@@ -237,7 +240,7 @@
InstanceId instanceId = new InstanceIdSequence().newInstanceId();
for (ItemInfo info : itemsIdMap) {
CollectionInfo parent = getContainer(info, itemsIdMap);
- StatsLogCompatManager.writeSnapshot(info.buildProto(parent), instanceId);
+ StatsLogCompatManager.writeSnapshot(info.buildProto(parent, mContext), instanceId);
}
additionalSnapshotEvents(instanceId);
prefs.put(LAST_SNAPSHOT_TIME_MILLIS, now);
@@ -274,7 +277,7 @@
for (ItemInfo info : itemsIdMap) {
CollectionInfo parent = getContainer(info, itemsIdMap);
- LauncherAtom.ItemInfo itemInfo = info.buildProto(parent);
+ LauncherAtom.ItemInfo itemInfo = info.buildProto(parent, mContext);
Log.d(TAG, itemInfo.toString());
StatsEvent statsEvent = StatsLogCompatManager.buildStatsEvent(itemInfo,
instanceId);
diff --git a/quickstep/src/com/android/launcher3/model/WellbeingModel.java b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
index fb17f15..94a1814 100644
--- a/quickstep/src/com/android/launcher3/model/WellbeingModel.java
+++ b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
@@ -44,23 +44,31 @@
import androidx.annotation.WorkerThread;
import com.android.launcher3.R;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppSingleton;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.popup.RemoteActionShortcut;
import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.util.DaggerSingletonObject;
+import com.android.launcher3.util.DaggerSingletonTracker;
+import com.android.launcher3.util.ExecutorUtil;
import com.android.launcher3.util.Executors;
-import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.launcher3.views.ActivityContext;
+import com.android.quickstep.dagger.QuickstepBaseAppComponent;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
+import javax.inject.Inject;
+
/**
* Data model for digital wellbeing status of apps.
*/
+@LauncherAppSingleton
public final class WellbeingModel implements SafeCloseable {
private static final String TAG = "WellbeingModel";
private static final int[] RETRY_TIMES_MS = {5000, 15000, 30000};
@@ -75,8 +83,8 @@
private static final String EXTRA_PACKAGES = "packages";
private static final String EXTRA_SUCCESS = "success";
- public static final MainThreadInitializedObject<WellbeingModel> INSTANCE =
- new MainThreadInitializedObject<>(WellbeingModel::new);
+ public static final DaggerSingletonObject<WellbeingModel> INSTANCE =
+ new DaggerSingletonObject<>(QuickstepBaseAppComponent::getWellbeingModel);
private final Context mContext;
private final String mWellbeingProviderPkg;
@@ -93,7 +101,9 @@
private boolean mIsInTest;
- private WellbeingModel(final Context context) {
+ @Inject
+ WellbeingModel(@ApplicationContext final Context context,
+ DaggerSingletonTracker tracker) {
mContext = context;
mWellbeingProviderPkg = mContext.getString(R.string.wellbeing_provider_pkg);
mWorkerHandler = new Handler(TextUtils.isEmpty(mWellbeingProviderPkg)
@@ -112,6 +122,7 @@
}
};
mWorkerHandler.post(this::initializeInBackground);
+ ExecutorUtil.executeSyncOnMainOrFail(() -> tracker.addCloseable(this));
}
@WorkerThread
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 3dcb2ac..2ac87ff 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
@@ -16,7 +16,7 @@
package com.android.launcher3.statehandlers;
import static android.view.View.VISIBLE;
-import static android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY;
+import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
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/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 477f90c..0b850bd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -15,8 +15,10 @@
*/
package com.android.launcher3.taskbar;
-import static android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY;
+import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY;
+import static com.android.launcher3.QuickstepTransitionManager.TASKBAR_TO_APP_DURATION;
+import static com.android.launcher3.QuickstepTransitionManager.getTaskbarToHomeDuration;
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;
@@ -32,9 +34,9 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Flags;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.QuickstepTransitionManager;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatedFloat;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.InstanceIdSequence;
import com.android.launcher3.model.data.ItemInfo;
@@ -50,6 +52,7 @@
import com.android.quickstep.util.TISBindHelper;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import java.io.PrintWriter;
@@ -66,14 +69,17 @@
public static final int ALL_APPS_PAGE_PROGRESS_INDEX = 1;
public static final int WIDGETS_PAGE_PROGRESS_INDEX = 2;
public static final int SYSUI_SURFACE_PROGRESS_INDEX = 3;
+ public static final int LAUNCHER_PAUSE_PROGRESS_INDEX = 4;
- public static final int DISPLAY_PROGRESS_COUNT = 4;
+ public static final int DISPLAY_PROGRESS_COUNT = 5;
private final AnimatedFloat mTaskbarInAppDisplayProgress = new AnimatedFloat(
this::onInAppDisplayProgressChanged);
private final MultiPropertyFactory<AnimatedFloat> mTaskbarInAppDisplayProgressMultiProp =
new MultiPropertyFactory<>(mTaskbarInAppDisplayProgress,
AnimatedFloat.VALUE, DISPLAY_PROGRESS_COUNT, Float::max);
+ private final AnimatedFloat mLauncherPauseProgress = new AnimatedFloat(
+ this::launcherPauseProgressUpdate);
private final QuickstepLauncher mLauncher;
private final HomeVisibilityState mHomeState;
@@ -148,12 +154,6 @@
}
}
- @Override
- protected boolean isTaskbarTouchable() {
- return !(mTaskbarLauncherStateController.isAnimatingToLauncher()
- && mTaskbarLauncherStateController.isTaskbarAlignedWithHotseat());
- }
-
public void setShouldDelayLauncherStateAnim(boolean shouldDelayLauncherStateAnim) {
mTaskbarLauncherStateController.setShouldDelayLauncherStateAnim(
shouldDelayLauncherStateAnim);
@@ -189,6 +189,33 @@
}
/**
+ * Called when Launcher Activity is paused/resumed.
+ * <p>
+ * To avoid UI clash between taskbar & bottom sheet, shift nav buttons down on launcher
+ * pause/resume at home.
+ * @param paused if launcher is currently paused.
+ */
+ public void onLauncherPausedOrResumed(boolean paused) {
+ if (!FeatureFlags.enableHomeTransitionListener()) {
+ onLauncherVisibilityChanged(mLauncher.hasBeenResumed());
+ return;
+ }
+
+ // Animate navbar iff pause/resume from home, NOT to/from app (avoid overriding existing
+ // animations).
+ boolean launcherPauseOrResumeFromHome = mHomeState.isHomeVisible() && mControllers
+ .taskbarAutohideSuspendController.isSuspendedForTransientTaskbarInLauncher();
+ if (launcherPauseOrResumeFromHome) {
+ mLauncherPauseProgress.animateToValue(paused ? 1.0f : 0.0f).start();
+ }
+ }
+
+ private void launcherPauseProgressUpdate() {
+ onTaskbarInAppDisplayProgressUpdate(
+ mLauncherPauseProgress.value, LAUNCHER_PAUSE_PROGRESS_INDEX);
+ }
+
+ /**
* Should be called from onResume() and onPause(), and animates the Taskbar accordingly.
*/
@Override
@@ -204,11 +231,17 @@
isVisible,
fromInitOrDestroy,
/* startAnimation= */ true,
- DisplayController.isTransientTaskbar(mLauncher)
- ? TRANSIENT_TASKBAR_TRANSITION_DURATION
- : (!isVisible
- ? QuickstepTransitionManager.TASKBAR_TO_APP_DURATION
- : QuickstepTransitionManager.getTaskbarToHomeDuration()));
+ getTaskbarAnimationDuration(isVisible));
+ }
+
+ private int getTaskbarAnimationDuration(boolean isVisible) {
+ if (isVisible && !mLauncher.getPredictiveBackToHomeInProgress()) {
+ return getTaskbarToHomeDuration();
+ } else {
+ return DisplayController.isTransientTaskbar(mLauncher)
+ ? TRANSIENT_TASKBAR_TRANSITION_DURATION
+ : TASKBAR_TO_APP_DURATION;
+ }
}
@Nullable
@@ -356,18 +389,20 @@
}
if (mControllers.uiController.isIconAlignedWithHotseat()
&& !mTaskbarLauncherStateController.isAnimatingToLauncher()) {
- // Only animate the nav buttons while home and not animating home, otherwise let
+ // Only animate nav button position while home and not animating home, otherwise let
// the TaskbarViewController handle it.
mControllers.navbarButtonsViewController
- .getTaskbarNavButtonTranslationYForInAppDisplay()
+ .getNavButtonTranslationYForInAppDisplay()
.updateValue(mLauncher.getDeviceProfile().getTaskbarOffsetY()
* mTaskbarInAppDisplayProgress.value);
- mControllers.navbarButtonsViewController
- .getOnTaskbarBackgroundNavButtonColorOverride().updateValue(progress);
+ if (!mLauncher.isPaused()) {
+ mControllers.navbarButtonsViewController
+ .getOnTaskbarBackgroundNavButtonColorOverride().updateValue(progress);
+ }
}
}
- /** Returns true iff any in-app display progress > 0. */
+ @Override
public boolean shouldUseInAppLayout() {
return mTaskbarInAppDisplayProgress.value > 0;
}
@@ -412,6 +447,16 @@
}
@Override
+ public boolean isHotseatVisibleForTaskBarAlignment() {
+ return mTaskbarLauncherStateController.isHotseatVisibleForTaskbarAlignment();
+ }
+
+ @Override
+ public boolean isAnimatingToLauncher() {
+ return mTaskbarLauncherStateController.isAnimatingToLauncher();
+ }
+
+ @Override
protected boolean isInOverviewUi() {
return mTaskbarLauncherStateController.isInOverviewUi();
}
@@ -469,6 +514,18 @@
}
@Override
+ public void onBubbleBarLocationAnimated(BubbleBarLocation location) {
+ mTaskbarLauncherStateController.onBubbleBarLocationChanged(location, /* animate = */ true);
+ mLauncher.setBubbleBarLocation(location);
+ }
+
+ @Override
+ public void onBubbleBarLocationUpdated(BubbleBarLocation location) {
+ mTaskbarLauncherStateController.onBubbleBarLocationChanged(location, /* animate = */ false);
+ mLauncher.setBubbleBarLocation(location);
+ }
+
+ @Override
public void onSwipeToUnstashTaskbar() {
// Once taskbar is unstashed, the user cannot return back to the overlay. We can
// clear it here to set the expected state once the user goes home.
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 2ac5793..cfcbd2f 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;
@@ -170,14 +173,17 @@
// Used for IME+A11Y buttons
private final ViewGroup mEndContextualContainer;
private final ViewGroup mStartContextualContainer;
- private final int mLightIconColorOnHome;
- private final int mDarkIconColorOnHome;
- /** Color to use for navigation bar buttons, if they are on on a Taskbar surface background. */
+ private final int mLightIconColorOnWorkspace;
+ private final int mDarkIconColorOnWorkspace;
+ /** Color to use for navbar 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(
+ private final AnimatedFloat mNavButtonTranslationYForInAppDisplay = new AnimatedFloat(
this::updateNavButtonTranslationY);
private final AnimatedFloat mTaskbarNavButtonTranslationYForIme = new AnimatedFloat(
this::updateNavButtonTranslationY);
@@ -185,7 +191,10 @@
// Used for System UI state updates that should translate the nav button for in-app display.
private final AnimatedFloat mNavButtonInAppDisplayProgressForSysui = new AnimatedFloat(
this::updateNavButtonInAppDisplayProgressForSysui);
- /** Expected nav button dark intensity communicated via the framework. */
+ /**
+ * Expected nav button dark intensity piped down from {@code LightBarController} in framework
+ * via {@code TaskbarDelegate}.
+ */
private final AnimatedFloat mTaskbarNavButtonDarkIntensity = new AnimatedFloat(
this::onDarkIntensityChanged);
/** {@code 1} if the Taskbar background color is fully opaque. */
@@ -240,8 +249,8 @@
mEndContextualContainer = mNavButtonsView.findViewById(R.id.end_contextual_buttons);
mStartContextualContainer = mNavButtonsView.findViewById(R.id.start_contextual_buttons);
- mLightIconColorOnHome = context.getColor(R.color.taskbar_nav_icon_light_color_on_home);
- mDarkIconColorOnHome = context.getColor(R.color.taskbar_nav_icon_dark_color_on_home);
+ mLightIconColorOnWorkspace = context.getColor(R.color.taskbar_nav_icon_light_color_on_home);
+ mDarkIconColorOnWorkspace = context.getColor(R.color.taskbar_nav_icon_dark_color_on_home);
mOnBackgroundIconColor = Utilities.isDarkTheme(context)
? context.getColor(R.color.taskbar_nav_icon_light_color)
: context.getColor(R.color.taskbar_nav_icon_dark_color);
@@ -400,6 +409,12 @@
}
};
mSeparateWindowParent.recreateControllers();
+ if (BubbleBarController.isBubbleBarEnabled()) {
+ mNavButtonsView.addOnLayoutChangeListener(
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
+ onLayoutsUpdated()
+ );
+ }
}
private void initButtons(ViewGroup navContainer, ViewGroup endContainer,
@@ -689,8 +704,8 @@
}
/** Use to set the translationY for the all nav+contextual buttons when in Launcher */
- public AnimatedFloat getTaskbarNavButtonTranslationYForInAppDisplay() {
- return mTaskbarNavButtonTranslationYForInAppDisplay;
+ public AnimatedFloat getNavButtonTranslationYForInAppDisplay() {
+ return mNavButtonTranslationYForInAppDisplay;
}
/** Use to set the dark intensity for the all nav+contextual buttons */
@@ -736,54 +751,84 @@
if (mContext.isPhoneButtonNavMode()) {
return;
}
- final float normalTranslationY = mTaskbarNavButtonTranslationY.value;
- final float imeAdjustmentTranslationY = mTaskbarNavButtonTranslationYForIme.value;
- TaskbarUIController uiController = mControllers.uiController;
- final float inAppDisplayAdjustmentTranslationY =
- (uiController instanceof LauncherTaskbarUIController
- && ((LauncherTaskbarUIController) uiController).shouldUseInAppLayout())
- ? mTaskbarNavButtonTranslationYForInAppDisplay.value : 0;
-
- mLastSetNavButtonTranslationY = normalTranslationY
- + imeAdjustmentTranslationY
- + inAppDisplayAdjustmentTranslationY;
+ mLastSetNavButtonTranslationY = calculateNavButtonTranslationY();
mNavButtonsView.setTranslationY(mLastSetNavButtonTranslationY);
}
+ /**
+ * Calculates the translationY of the nav buttons based on the current device state.
+ */
+ private float calculateNavButtonTranslationY() {
+ float translationY =
+ mTaskbarNavButtonTranslationY.value + mTaskbarNavButtonTranslationYForIme.value;
+ if (mControllers.uiController.shouldUseInAppLayout()) {
+ translationY += mNavButtonTranslationYForInAppDisplay.value;
+ }
+ return translationY;
+ }
+
+ /**
+ * Sets Taskbar 3-button mode icon colors based on the
+ * {@link #mTaskbarNavButtonDarkIntensity} value piped in from Framework. For certain cases
+ * in large screen taskbar where there may be opaque surfaces, the selected SystemUI button
+ * colors are intentionally overridden.
+ * <p>
+ * This method is also called when any of the AnimatedFloat instances change.
+ */
private void updateNavButtonColor() {
final ArgbEvaluator argbEvaluator = ArgbEvaluator.getInstance();
- final int sysUiNavButtonIconColorOnHome = (int) argbEvaluator.evaluate(
- mTaskbarNavButtonDarkIntensity.value,
- mLightIconColorOnHome,
- mDarkIconColorOnHome);
-
- final int iconColor;
- if (ENABLE_TASKBAR_NAVBAR_UNIFICATION && mContext.isPhoneMode()) {
- iconColor = sysUiNavButtonIconColorOnHome;
- } else {
- // Override the color from framework if nav buttons are over an opaque Taskbar surface.
- iconColor = (int) argbEvaluator.evaluate(
- mOnBackgroundNavButtonColorOverrideMultiplier.value * Math.max(
- mOnTaskbarBackgroundNavButtonColorOverride.value,
- mSlideInViewVisibleNavButtonColorOverride.value),
- sysUiNavButtonIconColorOnHome,
- mOnBackgroundIconColor);
+ int taskbarNavButtonColor = getSysUiIconColorOnHome(argbEvaluator);
+ // Only phone mode foldable button colors should be identical to SysUI navbar colors.
+ if (!(ENABLE_TASKBAR_NAVBAR_UNIFICATION && mContext.isPhoneMode())) {
+ taskbarNavButtonColor = getTaskbarButtonColor(argbEvaluator, taskbarNavButtonColor);
}
+ applyButtonColors(taskbarNavButtonColor);
+ }
+ /**
+ * Taskbar 3-button mode icon colors based on the
+ * {@link #mTaskbarNavButtonDarkIntensity} value piped in from Framework.
+ */
+ private int getSysUiIconColorOnHome(ArgbEvaluator argbEvaluator) {
+ return (int) argbEvaluator.evaluate(getTaskbarNavButtonDarkIntensity().value,
+ mLightIconColorOnWorkspace, mDarkIconColorOnWorkspace);
+ }
+
+ /**
+ * If Taskbar background is opaque or slide in overlay is visible, the selected SystemUI button
+ * colors are intentionally overridden. The override can be disabled when
+ * {@link #mOnBackgroundNavButtonColorOverrideMultiplier} is {@code 0}.
+ */
+ private int getTaskbarButtonColor(ArgbEvaluator argbEvaluator, int sysUiIconColorOnHome) {
+ final float sysUIColorOverride =
+ mOnBackgroundNavButtonColorOverrideMultiplier.value * Math.max(
+ mOnTaskbarBackgroundNavButtonColorOverride.value,
+ mSlideInViewVisibleNavButtonColorOverride.value);
+ return (int) argbEvaluator.evaluate(sysUIColorOverride, sysUiIconColorOnHome,
+ mOnBackgroundIconColor);
+ }
+
+ /**
+ * Iteratively sets button colors for each button in {@link #mAllButtons}.
+ */
+ private void applyButtonColors(int iconColor) {
for (ImageView button : mAllButtons) {
button.setImageTintList(ColorStateList.valueOf(iconColor));
Drawable background = button.getBackground();
if (background instanceof KeyButtonRipple) {
((KeyButtonRipple) background).setDarkIntensity(
- mTaskbarNavButtonDarkIntensity.value);
+ getTaskbarNavButtonDarkIntensity().value);
}
}
}
+ /**
+ * Updates Taskbar 3-Button icon colors as {@link #mTaskbarNavButtonDarkIntensity} changes.
+ */
private void onDarkIntensityChanged() {
updateNavButtonColor();
if (mContext.isPhoneMode()) {
- mTaskbarTransitions.onDarkIntensityChanged(mTaskbarNavButtonDarkIntensity.value);
+ mTaskbarTransitions.onDarkIntensityChanged(getTaskbarNavButtonDarkIntensity().value);
}
}
@@ -1119,7 +1164,7 @@
pw.println(prefix + "\t\tmTaskbarNavButtonTranslationY="
+ mTaskbarNavButtonTranslationY.value);
pw.println(prefix + "\t\tmTaskbarNavButtonTranslationYForInAppDisplay="
- + mTaskbarNavButtonTranslationYForInAppDisplay.value);
+ + mNavButtonTranslationYForInAppDisplay.value);
pw.println(prefix + "\t\tmTaskbarNavButtonTranslationYForIme="
+ mTaskbarNavButtonTranslationYForIme.value);
pw.println(prefix + "\t\tmTaskbarNavButtonDarkIntensity="
@@ -1174,14 +1219,42 @@
/** Adjusts navigation buttons layout accordingly to the bubble bar position. */
@Override
public void onBubbleBarLocationUpdated(BubbleBarLocation location) {
+ boolean locationUpdated = location != mBubbleBarTargetLocation;
+ if (locationUpdated) {
+ cancelExistingNavBarAnimation();
+ } else {
+ endExistingAnimation();
+ }
mNavButtonContainer.setTranslationX(getNavBarTranslationX(location));
+ mBubbleBarTargetLocation = location;
}
/** 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));
+ if (location == mBubbleBarTargetLocation) return;
+ 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 endExistingAnimation() {
+ if (mNavBarLocationAnimator != null) {
+ mNavBarLocationAnimator.end();
+ mNavBarLocationAnimator = null;
+ }
+ }
+
+ private void cancelExistingNavBarAnimation() {
+ if (mNavBarLocationAnimator != null) {
+ mNavBarLocationAnimator.cancel();
+ mNavBarLocationAnimator = null;
+ }
}
private int getNavBarTranslationX(BubbleBarLocation location) {
@@ -1218,12 +1291,22 @@
}
/** Adjusts the navigation buttons layout position according to the bubble bar location. */
- public void onTaskbarLayoutChange() {
- if (com.android.wm.shell.Flags.enableBubbleBarInPersistentTaskBar()
+ public void onLayoutsUpdated() {
+ // no need to do anything if on phone, or if taskbar or navbar views were not placed on
+ // screen.
+ if (mContext.getDeviceProfile().isPhone
+ || mControllers.taskbarViewController.getIconLayoutBounds().isEmpty()
+ || mNavButtonsView.getWidth() == 0) {
+ return;
+ }
+ if (enableBubbleBarInPersistentTaskBar()
&& mControllers.bubbleControllers.isPresent()) {
- BubbleBarLocation bubblesLocation = mControllers.bubbleControllers.get()
- .bubbleBarViewController.getBubbleBarLocation();
- onBubbleBarLocationUpdated(bubblesLocation);
+ if (mBubbleBarTargetLocation == null) {
+ // only set bubble bar location if it was not set before
+ mBubbleBarTargetLocation = mControllers.bubbleControllers.get()
+ .bubbleBarViewController.getBubbleBarLocation();
+ }
+ onBubbleBarLocationUpdated(mBubbleBarTargetLocation);
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index a59445b..3606615 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -71,6 +71,7 @@
import android.view.View;
import android.view.WindowInsets;
import android.view.WindowManager;
+import android.widget.FrameLayout;
import android.widget.Toast;
import android.window.RemoteTransition;
@@ -268,8 +269,10 @@
NearestTouchFrame navButtonsView = mDragLayer.findViewById(R.id.navbuttons_view);
StashedHandleView stashedHandleView = mDragLayer.findViewById(R.id.stashed_handle);
BubbleBarView bubbleBarView = null;
+ FrameLayout bubbleBarContainer = null;
if (isTransientTaskbar || Flags.enableBubbleBarInPersistentTaskBar()) {
bubbleBarView = mDragLayer.findViewById(R.id.taskbar_bubbles);
+ bubbleBarContainer = mDragLayer.findViewById(R.id.taskbar_bubbles_container);
}
StashedHandleView bubbleHandleView = mDragLayer.findViewById(R.id.stashed_bubble_handle);
@@ -278,7 +281,10 @@
// If Bubble bar is present, TaskbarControllers depends on it so build it first.
Optional<BubbleControllers> bubbleControllersOptional = Optional.empty();
BubbleBarController.onTaskbarRecreated();
- if (BubbleBarController.isBubbleBarEnabled() && bubbleBarView != null) {
+ if (BubbleBarController.isBubbleBarEnabled()
+ && !mDeviceProfile.isPhone
+ && bubbleBarView != null
+ ) {
Optional<BubbleStashedHandleViewController> bubbleHandleController = Optional.empty();
Optional<BubbleBarSwipeController> bubbleBarSwipeController = Optional.empty();
if (isTransientTaskbar) {
@@ -293,7 +299,7 @@
: new PersistentBubbleStashController(dimensionsProvider);
bubbleControllersOptional = Optional.of(new BubbleControllers(
new BubbleBarController(this, bubbleBarView),
- new BubbleBarViewController(this, bubbleBarView),
+ new BubbleBarViewController(this, bubbleBarView, bubbleBarContainer),
bubbleStashController,
bubbleHandleController,
new BubbleDragController(this),
@@ -943,15 +949,27 @@
}
/**
- * Hides the taskbar icons and background when the notication shade is expanded.
+ * Hides the taskbar icons and background when the notification shade is expanded.
*/
private void onNotificationShadeExpandChanged(boolean isExpanded, boolean skipAnim) {
+ // Close all floating views within the Taskbar window to make sure nothing is shown over
+ // the notification shade.
+ if (isExpanded) {
+ AbstractFloatingView.closeAllOpenViewsExcept(this, TYPE_TASKBAR_OVERLAY_PROXY);
+ }
+
float alpha = isExpanded ? 0 : 1;
AnimatorSet anim = new AnimatorSet();
anim.play(mControllers.taskbarViewController.getTaskbarIconAlpha().get(
TaskbarViewController.ALPHA_INDEX_NOTIFICATION_EXPANDED).animateToValue(alpha));
anim.play(mControllers.taskbarDragLayerController.getNotificationShadeBgTaskbar()
.animateToValue(alpha));
+
+ mControllers.bubbleControllers.ifPresent(controllers -> {
+ BubbleBarViewController bubbleBarViewController = controllers.bubbleBarViewController;
+ anim.play(bubbleBarViewController.getBubbleBarAlpha().get(0).animateToValue(alpha));
+ });
+
anim.start();
if (skipAnim) {
anim.end();
@@ -978,8 +996,8 @@
}
public void onNavButtonsDarkIntensityChanged(float darkIntensity) {
- mControllers.navbarButtonsViewController.getTaskbarNavButtonDarkIntensity()
- .updateValue(darkIntensity);
+ mControllers.navbarButtonsViewController.getTaskbarNavButtonDarkIntensity().updateValue(
+ darkIntensity);
}
public void onNavigationBarLumaSamplingEnabled(int displayId, boolean enable) {
@@ -1535,6 +1553,7 @@
/**
* Called when we want to unstash taskbar when user performs swipes up gesture.
+ *
* @param delayTaskbarBackground whether we will delay the taskbar background animation
*/
public void onSwipeToUnstashTaskbar(boolean delayTaskbarBackground) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index 56fd2bb..4a85acc 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;
@@ -194,11 +195,12 @@
};
if (taskbarDesktopModeController.getAreDesktopTasksVisible()) {
- mCornerRoundness.updateValue(taskbarDesktopModeController.getTaskbarCornerRoundness(
- mSharedState.showCornerRadiusInDesktopMode));
+ mCornerRoundness.value = taskbarDesktopModeController.getTaskbarCornerRoundness(
+ mSharedState.showCornerRadiusInDesktopMode);
} else {
- mCornerRoundness.updateValue(TaskbarBackgroundRenderer.MAX_ROUNDNESS);
+ mCornerRoundness.value = TaskbarBackgroundRenderer.MAX_ROUNDNESS;
}
+ updateCornerRoundness();
onPostInit();
}
@@ -219,7 +221,12 @@
uiController = newUiController;
uiController.init(this);
uiController.updateStateForSysuiFlags(mSharedState.sysuiStateFlags);
-
+ // if bubble controllers are present take bubble bar location, else set it to null
+ bubbleControllers.ifPresentOrElse(bubbleControllers -> {
+ BubbleBarLocation location =
+ bubbleControllers.bubbleBarViewController.getBubbleBarLocation();
+ uiController.onBubbleBarLocationUpdated(location);
+ }, () -> uiController.onBubbleBarLocationUpdated(null));
// Notify that the ui controller has changed
navbarButtonsViewController.onUiControllerChanged();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
index 06376d3..ade8f8c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
@@ -49,6 +49,7 @@
import com.android.launcher3.util.ResourceBasedOverride
import com.android.launcher3.views.ActivityContext
import com.android.launcher3.views.BaseDragLayer
+import com.android.quickstep.util.ContextualSearchInvoker
import com.android.quickstep.util.LottieAnimationColorUtils
import java.io.PrintWriter
@@ -80,7 +81,11 @@
ResourceBasedOverride, LoggableTaskbarController {
protected val activityContext: TaskbarActivityContext = ActivityContext.lookupContext(context)
- open val shouldShowSearchEdu = false
+ open val shouldShowSearchEdu: Boolean
+ get() =
+ ContextualSearchInvoker.newInstance(activityContext)
+ .runContextualSearchInvocationChecksAndLogFailures()
+
private val isTooltipEnabled: Boolean
get() {
return !Utilities.isRunningInTestHarness() &&
@@ -351,19 +356,19 @@
overlayContext.layoutInflater.inflate(
R.layout.taskbar_edu_tooltip,
overlayContext.dragLayer,
- false
+ false,
) as TaskbarEduTooltip
controllers.taskbarAutohideSuspendController.updateFlag(
FLAG_AUTOHIDE_SUSPEND_EDU_OPEN,
- true
+ true,
)
tooltip.onCloseCallback = {
this.tooltip = null
controllers.taskbarAutohideSuspendController.updateFlag(
FLAG_AUTOHIDE_SUSPEND_EDU_OPEN,
- false
+ false,
)
controllers.taskbarStashController.updateAndAnimateTransientTaskbar(true)
}
@@ -378,7 +383,7 @@
override fun performAccessibilityAction(
host: View,
action: Int,
- args: Bundle?
+ args: Bundle?,
): Boolean {
if (action == R.id.close) {
hide()
@@ -396,13 +401,13 @@
override fun onInitializeAccessibilityNodeInfo(
host: View,
- info: AccessibilityNodeInfo
+ info: AccessibilityNodeInfo,
) {
super.onInitializeAccessibilityNodeInfo(host, info)
info.addAction(
AccessibilityNodeInfo.AccessibilityAction(
R.id.close,
- host.context?.getText(R.string.taskbar_edu_close)
+ host.context?.getText(R.string.taskbar_edu_close),
)
)
}
@@ -421,7 +426,7 @@
return ResourceBasedOverride.Overrides.getObject(
TaskbarEduTooltipController::class.java,
context,
- R.string.taskbar_edu_tooltip_controller_class
+ R.string.taskbar_edu_tooltip_controller_class,
)
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index 685c109..1141a01 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -47,6 +47,7 @@
import com.android.internal.policy.GestureNavigationSettingsObserver
import com.android.launcher3.DeviceProfile
import com.android.launcher3.R
+import com.android.launcher3.Utilities
import com.android.launcher3.anim.AlphaUpdateListener
import com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION
import com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate
@@ -82,7 +83,7 @@
context.mainThreadHandler,
Executors.UI_HELPER_EXECUTOR.handler,
context,
- this::onTaskbarOrBubblebarWindowHeightOrInsetsChanged
+ this::onTaskbarOrBubblebarWindowHeightOrInsetsChanged,
)
private val debugTouchableRegion = DebugTouchableRegion()
@@ -120,7 +121,7 @@
if (enableTaskbarNoRecreate() && controllers.sharedState != null) {
getProvidedInsets(
controllers.sharedState!!.insetsFrameProviders,
- insetsRoundedCornerFlag
+ insetsRoundedCornerFlag,
)
} else {
getProvidedInsets(insetsRoundedCornerFlag)
@@ -133,9 +134,6 @@
}
val bubbleControllers = controllers.bubbleControllers.getOrNull()
- val taskbarTouchableHeight = taskbarStashController.touchableHeight
- val bubblesTouchableHeight =
- bubbleControllers?.bubbleStashController?.getTouchableHeight() ?: 0
// reset touch bounds
defaultTouchableRegion.setEmpty()
if (bubbleControllers != null) {
@@ -147,16 +145,45 @@
defaultTouchableRegion.addBoundsToRegion(bubbleBarViewController.bubbleBarBounds)
}
}
+ val uiController = controllers.uiController
if (
- taskbarStashController.isInApp ||
- taskbarStashController.isInOverview ||
+ !uiController.isHotseatVisibleForTaskBarAlignment ||
DisplayController.showLockedTaskbarOnHome(context)
) {
- // only add the taskbar touch region if not on home
+ // adding the taskbar touch region
+ val touchableHeight: Int
+ var left = 0
+ var right = context.deviceProfile.widthPx
+ var bubbleBarAdjustment = 0
+ if (uiController.isAnimatingToLauncher) {
+ val dp = controllers.taskbarActivityContext.deviceProfile
+ touchableHeight = windowLayoutParams.height
+ if (dp.isQsbInline) {
+ // if Qsb is inline need to exclude search icon from touch region
+ val isRtl = Utilities.isRtl(context.resources)
+ bubbleControllers?.bubbleBarViewController?.let {
+ if (dp.shouldAdjustHotseatOnBubblesLocationUpdate(context)) {
+ val isBubblesOnLeft = it.bubbleBarLocation.isOnLeft(isRtl)
+ bubbleBarAdjustment =
+ dp.getHotseatTranslationXForBubbleBar(isBubblesOnLeft, isRtl)
+ }
+ }
+ val hotseatPadding: Rect = dp.getHotseatLayoutPadding(context)
+ val borderSpacing: Int = dp.hotseatBorderSpace
+ if (isRtl) {
+ right =
+ dp.widthPx - hotseatPadding.right + borderSpacing + bubbleBarAdjustment
+ } else {
+ left = hotseatPadding.left - borderSpacing + bubbleBarAdjustment
+ }
+ }
+ } else {
+ // if not animating to launcher use the taskbar touchanle height
+ touchableHeight = taskbarStashController.touchableHeight
+ }
val bottom = windowLayoutParams.height
- val top = bottom - taskbarTouchableHeight
- val right = context.deviceProfile.widthPx
- defaultTouchableRegion.addBoundsToRegion(Rect(/* left= */ 0, top, right, bottom))
+ val top = bottom - touchableHeight
+ defaultTouchableRegion.addBoundsToRegion(Rect(left, top, right, bottom))
}
// Pre-calculate insets for different providers across different rotations for this gravity
@@ -181,7 +208,7 @@
*/
private fun getProvidedInsets(
providedInsets: Array<InsetsFrameProvider>,
- insetsRoundedCornerFlag: Int
+ insetsRoundedCornerFlag: Int,
): Array<InsetsFrameProvider> {
val navBarsFlag =
(if (context.isGestureNav) FLAG_SUPPRESS_SCRIM else 0) or insetsRoundedCornerFlag
@@ -207,14 +234,14 @@
InsetsFrameProvider(insetsOwner, 0, navigationBars())
.setFlags(
navBarsFlag,
- FLAG_SUPPRESS_SCRIM or FLAG_ANIMATE_RESIZING or FLAG_INSETS_ROUNDED_CORNER
+ FLAG_SUPPRESS_SCRIM or FLAG_ANIMATE_RESIZING or FLAG_INSETS_ROUNDED_CORNER,
),
InsetsFrameProvider(insetsOwner, 0, tappableElement()),
InsetsFrameProvider(insetsOwner, 0, mandatorySystemGestures()),
InsetsFrameProvider(insetsOwner, INDEX_LEFT, systemGestures())
.setSource(SOURCE_DISPLAY),
InsetsFrameProvider(insetsOwner, INDEX_RIGHT, systemGestures())
- .setSource(SOURCE_DISPLAY)
+ .setSource(SOURCE_DISPLAY),
)
}
@@ -232,7 +259,7 @@
val gestureHeight =
ResourceUtils.getNavbarSize(
ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE,
- context.resources
+ context.resources,
)
val isPinnedTaskbar =
context.deviceProfile.isTaskbarPresent &&
@@ -272,8 +299,8 @@
// override below (insetsSizeOverrides must have the same length and
// types after the window is added according to
// WindowManagerService#relayoutWindow)
- provider.insetsSize
- )
+ provider.insetsSize,
+ ),
)
// Use 0 tappableElement insets for the VoiceInteractionWindow when gesture nav is enabled.
val visInsetsSizeForTappableElement =
@@ -284,7 +311,7 @@
InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
InsetsFrameProvider.InsetsSizeOverride(
TYPE_VOICE_INTERACTION,
- visInsetsSizeForTappableElement
+ visInsetsSizeForTappableElement,
),
)
if (
@@ -368,10 +395,6 @@
// Let touches pass through us.
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
debugTouchableRegion.lastSetTouchableReason = "Stashed over IME"
- } else if (!controllers.uiController.isTaskbarTouchable) {
- // Let touches pass through us.
- insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
- debugTouchableRegion.lastSetTouchableReason = "Taskbar is not touchable"
} else if (controllers.taskbarDragController.isSystemDragInProgress) {
// Let touches pass through us.
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
@@ -427,7 +450,7 @@
// Always have nav buttons be touchable
controllers.navbarButtonsViewController.addVisibleButtonsRegion(
context.dragLayer,
- insetsInfo.touchableRegion
+ insetsInfo.touchableRegion,
)
debugTouchableRegion.lastSetTouchableBounds.set(insetsInfo.touchableRegion.bounds)
context.excludeFromMagnificationRegion(insetsIsTouchableRegion)
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 876221b..39aef28 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -16,14 +16,19 @@
package com.android.launcher3.taskbar;
import static com.android.app.animation.Interpolators.EMPHASIZED;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
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;
@@ -38,12 +43,15 @@
import android.animation.ObjectAnimator;
import android.os.SystemClock;
import android.util.Log;
+import android.view.animation.Interpolator;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.app.animation.Interpolators;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Hotseat;
import com.android.launcher3.Hotseat.HotseatQsbAlphaId;
import com.android.launcher3.LauncherState;
import com.android.launcher3.QuickstepTransitionManager;
@@ -52,16 +60,19 @@
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.BubbleLauncherState;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
import com.android.quickstep.RecentsAnimationCallbacks;
import com.android.quickstep.RecentsAnimationController;
+import com.android.quickstep.util.ScalingWorkspaceRevealAnim;
import com.android.quickstep.util.SystemUiFlagUtils;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.animation.ViewRootSync;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
import java.io.PrintWriter;
import java.util.HashMap;
@@ -151,6 +162,7 @@
private AnimatedFloat mTaskbarAlpha;
private AnimatedFloat mTaskbarCornerRoundness;
private MultiProperty mTaskbarAlphaForHome;
+ private @Nullable Animator mHotseatTranslationXAnimation;
private QuickstepLauncher mLauncher;
private boolean mIsDestroyed = false;
@@ -160,7 +172,12 @@
private boolean mSkipNextRecentsAnimEnd;
// Time when FLAG_TASKBAR_HIDDEN was last cleared, SystemClock.elapsedRealtime (milliseconds).
- private long mLastUnlockTimeMs = 0;
+ private long mLastRemoveTaskbarHiddenTimeMs = 0;
+ /**
+ * Time when FLAG_DEVICE_LOCKED was last cleared, plus
+ * {@link TaskbarStashController#UNLOCK_TRANSITION_MEMOIZATION_MS}
+ */
+ private long mLastUnlockTransitionTimeout;
private @Nullable TaskBarRecentsAnimationListener mTaskBarRecentsAnimationListener;
@@ -168,11 +185,15 @@
private boolean mShouldDelayLauncherStateAnim;
+ private @Nullable BubbleBarLocation mBubbleBarLocation;
+
// We skip any view synchronizations during init/destroy.
private boolean mCanSyncViews;
private boolean mIsQsbInline;
+ private boolean mIsHotseatVisibleForTaskbarAlignment;
+
private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener =
new DeviceProfile.OnDeviceProfileChangeListener() {
@Override
@@ -185,6 +206,8 @@
mIsQsbInline = dp.isQsbInline;
TaskbarLauncherStateController.this.updateIconAlphaForHome(
mTaskbarAlphaForHome.getValue(), ALPHA_CHANNEL_TASKBAR_ALIGNMENT);
+ TaskbarLauncherStateController.this.onBubbleBarLocationChanged(
+ mBubbleBarLocation, /* animate = */ false);
}
};
@@ -462,8 +485,12 @@
boolean onOverview = mLauncherState == LauncherState.OVERVIEW;
boolean hotseatIconsVisible = isInLauncher && mLauncherState.areElementsVisible(
mLauncher, HOTSEAT_ICONS);
- controllers.bubbleStashController.setBubblesShowingOnHome(hotseatIconsVisible);
- controllers.bubbleStashController.setBubblesShowingOnOverview(onOverview);
+ BubbleLauncherState state = onOverview
+ ? BubbleLauncherState.OVERVIEW
+ : hotseatIconsVisible
+ ? BubbleLauncherState.HOME
+ : BubbleLauncherState.IN_APP;
+ controllers.bubbleStashController.setLauncherState(state);
});
TaskbarStashController stashController = mControllers.taskbarStashController;
@@ -524,7 +551,7 @@
if (hasAnyFlag(changedFlags, FLAG_TASKBAR_HIDDEN) && !hasAnyFlag(FLAG_TASKBAR_HIDDEN)) {
// Take note of the current time, as the taskbar is made visible again.
- mLastUnlockTimeMs = SystemClock.elapsedRealtime();
+ mLastRemoveTaskbarHiddenTimeMs = SystemClock.elapsedRealtime();
}
boolean isHidden = hasAnyFlag(FLAG_TASKBAR_HIDDEN);
@@ -550,7 +577,8 @@
// with a fingerprint reader. This should only be done when the device was woken
// up via fingerprint reader, however since this information is currently not
// available, opting to always delay the fade-in a bit.
- long durationSinceLastUnlockMs = SystemClock.elapsedRealtime() - mLastUnlockTimeMs;
+ long durationSinceLastUnlockMs = SystemClock.elapsedRealtime()
+ - mLastRemoveTaskbarHiddenTimeMs;
taskbarVisibility.setStartDelay(
Math.max(0, TASKBAR_SHOW_DELAY_MS - durationSinceLastUnlockMs));
}
@@ -620,6 +648,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();
@@ -650,7 +687,9 @@
animatorSet.play(iconAlignAnim);
}
- animatorSet.setInterpolator(EMPHASIZED);
+ Interpolator interpolator = enableScalingRevealHomeAnimation()
+ ? ScalingWorkspaceRevealAnim.SCALE_INTERPOLATOR : EMPHASIZED;
+ animatorSet.setInterpolator(interpolator);
if (start) {
animatorSet.start();
@@ -701,6 +740,7 @@
boolean committed) {
boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher);
TaskbarStashController stashController = mControllers.taskbarStashController;
+ TaskbarInsetsController insetsController = mControllers.taskbarInsetsController;
stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, isInStashedState);
Animator stashAnimator = stashController.createApplyStateAnimator(duration);
if (stashAnimator != null) {
@@ -709,7 +749,11 @@
public void onAnimationEnd(Animator animation) {
if (isInStashedState && committed) {
// Reset hotseat alpha to default
- mLauncher.getHotseat().setIconsAlpha(1, ALPHA_CHANNEL_TASKBAR_ALIGNMENT);
+ updateIconAlphaForHome(
+ /* taskbarAlpha = */ 0,
+ ALPHA_CHANNEL_TASKBAR_ALIGNMENT,
+ /* updateTaskbarAlpha = */ false
+ );
}
}
@@ -749,6 +793,9 @@
}
protected void stashHotseat(boolean stash) {
+ // align taskbar with the hotseat icons before performing any animation
+ mControllers.taskbarViewController.setLauncherIconAlignment(/* alignmentRatio = */ 1,
+ mLauncher.getDeviceProfile());
TaskbarStashController stashController = mControllers.taskbarStashController;
stashController.updateStateForFlag(FLAG_STASHED_FOR_BUBBLES, stash);
Runnable swapHotseatWithTaskbar = new Runnable() {
@@ -831,8 +878,82 @@
if (mIsQsbInline) {
mLauncher.getHotseat().setQsbAlpha(targetAlpha, alphaChannel);
}
+ if (alphaChannel == ALPHA_CHANNEL_TASKBAR_ALIGNMENT) {
+ boolean isHotseatVisibleForTaskbarAlignment = isHotseatVisibleForTaskbarAlignment();
+ if (mIsHotseatVisibleForTaskbarAlignment != isHotseatVisibleForTaskbarAlignment) {
+ mIsHotseatVisibleForTaskbarAlignment = isHotseatVisibleForTaskbarAlignment;
+ mControllers.taskbarInsetsController
+ .onTaskbarOrBubblebarWindowHeightOrInsetsChanged();
+ }
+ }
}
+ /** Updates launcher home screen appearance accordingly to the bubble bar location. */
+ public void onBubbleBarLocationChanged(@Nullable BubbleBarLocation location, boolean animate) {
+ mBubbleBarLocation = location;
+ if (location == null) {
+ // bubble bar is not present, hence no location, resetting the hotseat
+ updateHotseatAndQsbTranslationX(0, animate);
+ mBubbleBarLocation = null;
+ return;
+ }
+ DeviceProfile deviceProfile = mLauncher.getDeviceProfile();
+ if (!deviceProfile.shouldAdjustHotseatOnBubblesLocationUpdate(
+ mControllers.taskbarActivityContext)) {
+ return;
+ }
+ boolean isRtl = isRtl(mLauncher.getResources());
+ boolean isBubblesOnLeft = location.isOnLeft(isRtl);
+ int targetX = deviceProfile
+ .getHotseatTranslationXForBubbleBar(isBubblesOnLeft, isRtl);
+ updateHotseatAndQsbTranslationX(targetX, animate);
+ }
+
+ /** Used to translate hotseat and QSB to make room for bubbles. */
+ private void updateHotseatAndQsbTranslationX(float targetValue, boolean animate) {
+ // cancel existing animation
+ if (mHotseatTranslationXAnimation != null) {
+ mHotseatTranslationXAnimation.cancel();
+ mHotseatTranslationXAnimation = null;
+ }
+ Hotseat hotseat = mLauncher.getHotseat();
+ AnimatorSet translationXAnimation = new AnimatorSet();
+ MultiProperty iconsTranslationX = mLauncher.getHotseat()
+ .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) {
+ return;
+ }
+ mHotseatTranslationXAnimation = translationXAnimation;
+ translationXAnimation.setStartDelay(FADE_OUT_ANIM_POSITION_DURATION_MS);
+ translationXAnimation.setDuration(FADE_IN_ANIM_ALPHA_DURATION_MS);
+ translationXAnimation.setInterpolator(Interpolators.EMPHASIZED);
+ translationXAnimation.start();
+ }
+
+ /** Returns true if hotseat icons visible for the taskbar alignment */
+ public boolean isHotseatVisibleForTaskbarAlignment() {
+ return mLauncher.getHotseat()
+ .getIconsAlpha(ALPHA_CHANNEL_TASKBAR_ALIGNMENT).getValue() == 1;
+ }
+
+
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 78e7b47..c18cf28 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -16,7 +16,6 @@
package com.android.launcher3.taskbar;
import static android.content.Context.RECEIVER_NOT_EXPORTED;
-import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
@@ -72,7 +71,7 @@
import com.android.quickstep.AllAppsActionManager;
import com.android.quickstep.RecentsActivity;
import com.android.quickstep.SystemUiProxy;
-import com.android.quickstep.util.AssistUtils;
+import com.android.quickstep.util.ContextualSearchInvoker;
import com.android.systemui.shared.statusbar.phone.BarTransitions;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
@@ -220,7 +219,7 @@
TaskbarNavButtonCallbacks navCallbacks,
@NonNull DesktopVisibilityController desktopVisibilityController) {
Display display =
- context.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
+ context.getSystemService(DisplayManager.class).getDisplay(context.getDisplayId());
mContext = context.createWindowContext(display,
ENABLE_TASKBAR_NAVBAR_UNIFICATION ? TYPE_NAVIGATION_BAR : TYPE_NAVIGATION_BAR_PANEL,
null);
@@ -250,7 +249,7 @@
SystemUiProxy.INSTANCE.get(mContext),
ContextualEduStatsManager.INSTANCE.get(mContext),
new Handler(),
- AssistUtils.newInstance(mContext));
+ ContextualSearchInvoker.newInstance(mContext));
mComponentCallbacks = new ComponentCallbacks() {
private Configuration mOldConfig = mContext.getResources().getConfiguration();
@@ -672,11 +671,6 @@
@VisibleForTesting
public void setSuspended(boolean isSuspended) {
mIsSuspended = isSuspended;
- if (mIsSuspended) {
- removeTaskbarRootViewFromWindow();
- } else {
- addTaskbarRootViewToWindow();
- }
}
private void addTaskbarRootViewToWindow() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
index 15c35b6..8947914 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -51,7 +51,7 @@
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.quickstep.util.ContextualSearchInvoker;
import com.android.systemui.contextualeducation.GestureType;
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
@@ -113,7 +113,7 @@
private final SystemUiProxy mSystemUiProxy;
private final ContextualEduStatsManager mContextualEduStatsManager;
private final Handler mHandler;
- private final AssistUtils mAssistUtils;
+ private final ContextualSearchInvoker mContextualSearchInvoker;
@Nullable private StatsLogManager mStatsLogManager;
private final Runnable mResetLongPress = this::resetScreenUnpin;
@@ -124,13 +124,13 @@
SystemUiProxy systemUiProxy,
ContextualEduStatsManager contextualEduStatsManager,
Handler handler,
- AssistUtils assistUtils) {
+ ContextualSearchInvoker contextualSearchInvoker) {
mContext = context;
mCallbacks = callbacks;
mSystemUiProxy = systemUiProxy;
mContextualEduStatsManager = contextualEduStatsManager;
mHandler = handler;
- mAssistUtils = assistUtils;
+ mContextualSearchInvoker = contextualSearchInvoker;
}
public void onButtonClick(@TaskbarButton int buttonType, View view) {
@@ -344,8 +344,9 @@
if (mScreenPinned || !mAssistantLongPressEnabled) {
return;
}
- // Attempt to start Assist with AssistUtils, otherwise fall back to SysUi's implementation.
- if (!mAssistUtils.tryStartAssistOverride(INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS)) {
+ // Attempt to start Contextual Search, otherwise fall back to SysUi's implementation.
+ if (!mContextualSearchInvoker.tryStartAssistOverride(
+ INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS)) {
Bundle args = new Bundle();
args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS);
mSystemUiProxy.startAssistant(args);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
index 57d4dbb..3e8b615 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
@@ -16,7 +16,7 @@
package com.android.launcher3.taskbar
import android.content.Context
-import android.window.flags.DesktopModeFlags
+import android.window.DesktopModeFlags
import androidx.annotation.VisibleForTesting
import com.android.launcher3.Flags.enableRecentsInTaskbar
import com.android.launcher3.model.data.ItemInfo
@@ -251,7 +251,7 @@
// Remove any newly-missing Tasks, and actual group-tasks
val newShownTasks =
shownTasks
- .filter { !it.hasMultipleTasks() }
+ .filter { !it.supportsMultipleTasks() }
.filter { it.task1.key.id in desktopTaskIds }
.toMutableList()
// Add any new Tasks, maintaining the order from previous shownTasks.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index e39e904..8991965 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -191,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.
@@ -450,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);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 9c8c2a9..db69e8f 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.
@@ -73,8 +75,14 @@
mControllers = null;
}
- protected boolean isTaskbarTouchable() {
- return true;
+ /** Returns true if transition animation to launcher home is being played. */
+ public boolean isAnimatingToLauncher() {
+ return false;
+ }
+
+ /** Returns true if hotseat icons visible for the taskbar alignment. */
+ public boolean isHotseatVisibleForTaskBarAlignment() {
+ return false;
}
/**
@@ -89,6 +97,14 @@
protected void onStashedInAppChanged() { }
/**
+ * Whether the Taskbar should use in-app layout.
+ * @return {@code true} iff in-app display progress > 0 or Launcher Activity paused.
+ */
+ public boolean shouldUseInAppLayout() {
+ return false;
+ }
+
+ /**
* Called when taskbar icon layout bounds change.
*/
protected void onIconLayoutBoundsChanged() { }
@@ -433,6 +449,14 @@
public void stashHotseat(boolean stash) {
}
+ @Override
+ public void onBubbleBarLocationAnimated(BubbleBarLocation location) {
+ }
+
+ @Override
+ public void onBubbleBarLocationUpdated(BubbleBarLocation location) {
+ }
+
/** Un-stash the hotseat instantly */
public void unStashHotseatInstantly() {
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 8763509..d757180 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -65,8 +65,8 @@
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.quickstep.views.TaskViewType;
import com.android.systemui.shared.recents.model.Task;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
@@ -185,20 +185,50 @@
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.
+ * @return the maximum number of 'icons' that can fit in the taskbar.
*/
private int calculateMaxNumIcons() {
- int availableWidth = mActivityContext.getDeviceProfile().widthPx
- - (mActivityContext.getDeviceProfile().edgeMarginPx * 2);
- return Math.floorDiv(availableWidth, mIconTouchSize);
+ DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
+ int availableWidth = deviceProfile.widthPx;
+
+ // Reserve space required for edge margins, or for navbar if shown. If task bar needs to be
+ // center aligned with nav bar shown, reserve space on both sides.
+ availableWidth -= Math.max(deviceProfile.edgeMarginPx, deviceProfile.hotseatBarEndOffset);
+ availableWidth -= Math.max(deviceProfile.edgeMarginPx,
+ mShouldTryStartAlign ? 0 : deviceProfile.hotseatBarEndOffset);
+
+ // The space taken by an item icon used during layout.
+ int iconSize = 2 * mItemMarginLeftRight + mIconTouchSize;
+
+ int additionalIcons = 0;
+
+ if (mTaskbarDividerContainer != null) {
+ // Space for divider icon is reduced during layout compared to normal icon size, reserve
+ // space for the divider separately.
+ availableWidth -= iconSize - 4 * mItemMarginLeftRight;
+ ++additionalIcons;
+ }
+
+ // All apps icon takes less space compared to normal icon size, reserve space for the icon
+ // separately.
+ if (mAllAppsButtonContainer != null) {
+ boolean forceTransientTaskbarSize =
+ enableTaskbarPinning() && !mActivityContext.isThreeButtonNav();
+ availableWidth -= iconSize - (int) getResources().getDimension(
+ mAllAppsButtonContainer.getAllAppsButtonTranslationXOffset(
+ forceTransientTaskbarSize || (
+ DisplayController.isTransientTaskbar(mActivityContext)
+ && !mActivityContext.isPhoneMode())));
+ ++additionalIcons;
+ }
+
+ return Math.floorDiv(availableWidth, iconSize) + additionalIcons;
}
@Override
@@ -412,18 +442,38 @@
if (mTaskbarDividerContainer != null && !recentTasks.isEmpty()) {
addView(mTaskbarDividerContainer, nextViewIndex++);
mAddedDividerForRecents = true;
- if (mTaskbarOverflowView != null) {
+ }
+
+ // At this point, the all apps button has not been added as a child view, but needs to be
+ // accounted for when comparing current icon count to max number of icons.
+ int nonTaskIconsToBeAdded = 1;
+
+ boolean supportsOverflow = Flags.taskbarOverflow();
+ if (supportsOverflow) {
+ int numberOfSupportedRecents = 0;
+ for (GroupTask task : recentTasks) {
+ // TODO(b/343289567 and b/316004172): support app pairs and desktop mode.
+ if (!task.hasMultipleTasks()) {
+ ++numberOfSupportedRecents;
+ }
+ }
+ if (nextViewIndex + numberOfSupportedRecents + nonTaskIconsToBeAdded > mMaxNumIcons
+ && mTaskbarOverflowView != null) {
addView(mTaskbarOverflowView, nextViewIndex++);
}
}
// Add Recent/Running icons.
for (GroupTask task : recentTasks) {
+ if (supportsOverflow && nextViewIndex + nonTaskIconsToBeAdded >= mMaxNumIcons) {
+ break;
+ }
+
// Replace any Recent views with the appropriate type if it's not already that type.
final int expectedLayoutResId;
boolean isCollection = false;
- if (task.hasMultipleTasks()) {
- if (task instanceof DesktopTask) {
+ if (task.supportsMultipleTasks()) {
+ if (task.taskViewType == TaskViewType.DESKTOP) {
// TODO(b/316004172): use Desktop tile layout.
expectedLayoutResId = -1;
} else {
@@ -488,8 +538,6 @@
}
}
- updateRecentAppsToFit();
-
if (mActivityContext.getDeviceProfile().isQsbInline) {
addView(mQsb, mIsRtl ? getChildCount() : 0);
// Always set QSB to invisible after re-adding.
@@ -497,45 +545,6 @@
}
}
- /**
- * Updates the recent apps portion of the taskbar by:
- * - Removing overflow affordance if overflow is not needed.
- * - Removing any recent apps that do not fit.
- */
- private void updateRecentAppsToFit() {
- if (!Flags.taskbarOverflow()) {
- return;
- }
- int indexOfFirstRecentApp = -1;
- int size = getChildCount();
- boolean removeOverflowView = true;
-
- for (int i = 0; i < size; ++i) {
- if (getChildAt(i).getTag() instanceof GroupTask) {
- indexOfFirstRecentApp = i;
- removeOverflowView = false;
- break;
- }
- }
-
- if (indexOfFirstRecentApp != -1) {
- // We pre-maturely added the overflow icon, so we can take it out of the count.
- int numRecentAppsToRemove = Math.max(0, getChildCount() - mMaxNumIcons + 1);
- if (numRecentAppsToRemove <= 1) {
- // We can fit all of the recent apps if we remove the overflow icon.
- removeOverflowView = true;
- } else {
- for (int i = 0; i < numRecentAppsToRemove; ++i) {
- removeAndRecycle(getChildAt(indexOfFirstRecentApp));
- }
- }
- }
-
- if (removeOverflowView) {
- removeView(mTaskbarOverflowView);
- }
- }
-
/** Binds the GroupTask to the BubbleTextView to be ready to present to the user. */
public void applyGroupTaskToBubbleTextView(BubbleTextView btv, GroupTask groupTask) {
// TODO(b/343289567): support app pairs.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
index 176be1c..8bc1e12 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
@@ -19,6 +19,7 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_ALLAPPS_BUTTON_LONG_PRESS;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP;
+import android.content.Context;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.View;
@@ -64,7 +65,8 @@
mActivity.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_ALLAPPS_BUTTON_LONG_PRESS);
}
- public boolean isAllAppsButtonHapticFeedbackEnabled() {
+ /** @return true if haptic feedback should occur when long pressing the all apps button. */
+ public boolean isAllAppsButtonHapticFeedbackEnabled(Context context) {
return false;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacksFactory.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacksFactory.kt
index ba0f5a0..704d6cf 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacksFactory.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacksFactory.kt
@@ -16,10 +16,14 @@
package com.android.launcher3.taskbar
+import android.app.contextualsearch.ContextualSearchManager.ENTRYPOINT_LONG_PRESS_META
import android.content.Context
import com.android.launcher3.R
+import com.android.launcher3.logging.StatsLogManager
import com.android.launcher3.util.ResourceBasedOverride
import com.android.launcher3.util.ResourceBasedOverride.Overrides
+import com.android.quickstep.TopTaskTracker
+import com.android.quickstep.util.ContextualSearchInvoker
/** Creates [TaskbarViewCallbacks] instances. */
open class TaskbarViewCallbacksFactory : ResourceBasedOverride {
@@ -28,7 +32,35 @@
activity: TaskbarActivityContext,
controllers: TaskbarControllers,
taskbarView: TaskbarView,
- ): TaskbarViewCallbacks = TaskbarViewCallbacks(activity, controllers, taskbarView)
+ ): TaskbarViewCallbacks {
+ return object : TaskbarViewCallbacks(activity, controllers, taskbarView) {
+ override fun triggerAllAppsButtonLongClick() {
+ super.triggerAllAppsButtonLongClick()
+
+ val contextualSearchInvoked =
+ ContextualSearchInvoker.newInstance(activity).show(ENTRYPOINT_LONG_PRESS_META)
+ if (contextualSearchInvoked) {
+ val runningPackage =
+ TopTaskTracker.INSTANCE[activity].getCachedTopTask(
+ /* filterOnlyVisibleRecents */ true
+ )
+ .getPackageName()
+ activity.statsLogManager
+ .logger()
+ .withPackageName(runningPackage)
+ .log(StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_OMNI_SUCCESSFUL_META)
+ }
+ }
+
+ override fun isAllAppsButtonHapticFeedbackEnabled(context: Context): Boolean {
+ return longPressAllAppsToStartContextualSearch(context)
+ }
+ }
+ }
+
+ open fun longPressAllAppsToStartContextualSearch(context: Context): Boolean =
+ ContextualSearchInvoker.newInstance(context)
+ .runContextualSearchInvocationChecksAndLogFailures()
companion object {
@JvmStatic
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 83527ab..c275536 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -84,7 +84,6 @@
import java.io.PrintWriter;
import java.util.Collections;
import java.util.HashSet;
-import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
@@ -160,7 +159,9 @@
private final View.OnLayoutChangeListener mTaskbarViewLayoutChangeListener =
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
updateTaskbarIconTranslationXForPinning();
- mControllers.navbarButtonsViewController.onTaskbarLayoutChange();
+ if (BubbleBarController.isBubbleBarEnabled()) {
+ mControllers.navbarButtonsViewController.onLayoutsUpdated();
+ }
};
// Animation to align icons with Launcher, created lazily. This allows the controller to be
@@ -232,7 +233,7 @@
mTaskbarNavButtonTranslationY =
controllers.navbarButtonsViewController.getTaskbarNavButtonTranslationY();
mTaskbarNavButtonTranslationYForInAppDisplay = controllers.navbarButtonsViewController
- .getTaskbarNavButtonTranslationYForInAppDisplay();
+ .getNavButtonTranslationYForInAppDisplay();
mActivity.addOnDeviceProfileChangeListener(mDeviceProfileChangeListener);
@@ -330,7 +331,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();
}
/**
@@ -834,6 +836,14 @@
: mPersistentTaskbarDp.taskbarBottomMargin;
int firstRecentTaskIndex = -1;
+ int hotseatNavBarTranslationX = 0;
+ if (mCurrentBubbleBarLocation != null
+ && taskbarDp.shouldAdjustHotseatOnBubblesLocationUpdate(mActivity)) {
+ boolean isRtl = mTaskbarView.isLayoutRtl();
+ boolean isBubblesOnLeft = mCurrentBubbleBarLocation.isOnLeft(isRtl);
+ hotseatNavBarTranslationX = taskbarDp
+ .getHotseatTranslationXForBubbleBar(isBubblesOnLeft, isRtl);
+ }
for (int i = 0; i < mTaskbarView.getChildCount(); i++) {
View child = mTaskbarView.getChildAt(i);
boolean isAllAppsButton = child == mTaskbarView.getAllAppsButtonContainer();
@@ -869,16 +879,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)
@@ -887,8 +901,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);
@@ -923,10 +937,12 @@
mTaskbarView.isDividerForRecents(), recentTaskIndex);
if (positionInHotseat == ERROR_POSITION_IN_HOTSEAT_NOT_FOUND) continue;
- float hotseatAdjustedBorderSpace =
- launcherDp.getHotseatAdjustedBorderSpaceForBubbleBar(child.getContext());
+
float hotseatIconCenter;
- if (bubbleBarHasBubbles() && hotseatAdjustedBorderSpace != 0) {
+ if (launcherDp.shouldAdjustHotseatForBubbleBar(child.getContext(),
+ bubbleBarHasBubbles())) {
+ float hotseatAdjustedBorderSpace =
+ launcherDp.getHotseatAdjustedBorderSpaceForBubbleBar(child.getContext());
hotseatIconCenter = hotseatPadding.left + hotseatCellSize
+ (hotseatCellSize + hotseatAdjustedBorderSpace) * positionInHotseat
+ hotseatCellSize / 2f;
@@ -935,6 +951,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/BubbleBarBackground.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
index f08318e..249773d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
@@ -43,9 +43,10 @@
private val arrowTipRadius: Float
private val arrowVisibleHeight: Float
- private val shadowAlpha: Float
- private var shadowBlur = 0f
- private var keyShadowDistance = 0f
+ private val strokeAlpha: Int
+ private val shadowAlpha: Int
+ private val shadowBlur: Float
+ private val keyShadowDistance: Float
private var arrowHeightFraction = 1f
var arrowPositionX: Float = 0f
@@ -105,13 +106,13 @@
strokePaint.strokeWidth = res.getDimension(R.dimen.transient_taskbar_stroke_width)
// apply theme alpha attributes
if (Utilities.isDarkTheme(context)) {
- strokePaint.alpha = DARK_THEME_STROKE_ALPHA
+ strokeAlpha = DARK_THEME_STROKE_ALPHA
shadowAlpha = DARK_THEME_SHADOW_ALPHA
} else {
- strokePaint.alpha = LIGHT_THEME_STROKE_ALPHA
+ strokeAlpha = LIGHT_THEME_STROKE_ALPHA
shadowAlpha = LIGHT_THEME_SHADOW_ALPHA
}
-
+ strokePaint.alpha = strokeAlpha
shadowBlur = res.getDimension(R.dimen.transient_taskbar_shadow_blur)
keyShadowDistance = res.getDimension(R.dimen.transient_taskbar_key_shadow_distance)
arrowWidth = res.getDimension(R.dimen.bubblebar_pointer_width)
@@ -132,15 +133,14 @@
override fun draw(canvas: Canvas) {
canvas.save()
- // TODO (b/277359345): Should animate the alpha similar to taskbar (see TaskbarDragLayer)
// Draw shadows.
val newShadowAlpha =
- mapToRange(fillPaint.alpha.toFloat(), 0f, 255f, 0f, shadowAlpha, Interpolators.LINEAR)
+ mapToRange(fillPaint.alpha, 0, 255, 0, shadowAlpha, Interpolators.LINEAR)
fillPaint.setShadowLayer(
shadowBlur,
0f,
keyShadowDistance,
- setColorAlphaBound(Color.BLACK, Math.round(newShadowAlpha))
+ setColorAlphaBound(Color.BLACK, newShadowAlpha),
)
// Create background path
val backgroundPath = Path()
@@ -172,7 +172,7 @@
arrowWidth,
scaledHeight,
arrowTipRadius,
- arrowPath
+ arrowPath,
)
// flip it horizontally
val pathTransform = Matrix()
@@ -196,6 +196,7 @@
override fun setAlpha(alpha: Int) {
fillPaint.alpha = alpha
+ strokePaint.alpha = mapToRange(alpha, 0, 255, 0, strokeAlpha, Interpolators.LINEAR)
invalidateSelf()
}
@@ -237,7 +238,7 @@
companion object {
private const val DARK_THEME_STROKE_ALPHA = 51
private const val LIGHT_THEME_STROKE_ALPHA = 41
- private const val DARK_THEME_SHADOW_ALPHA = 51f
- private const val LIGHT_THEME_SHADOW_ALPHA = 25f
+ private const val DARK_THEME_SHADOW_ALPHA = 51
+ private const val LIGHT_THEME_SHADOW_ALPHA = 25
}
}
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/BubbleBarItem.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarItem.kt
index 7a32ef1..680ffca 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarItem.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarItem.kt
@@ -17,10 +17,11 @@
import android.graphics.Bitmap
import android.graphics.Path
+import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutMessage
import com.android.wm.shell.shared.bubbles.BubbleInfo
/** An entity in the bubble bar. */
-sealed class BubbleBarItem(open var key: String, open var view: BubbleView)
+sealed class BubbleBarItem(open val key: String, open var view: BubbleView)
/** Contains state info about a bubble in the bubble bar as well as presentation information. */
data class BubbleBarBubble(
@@ -30,7 +31,8 @@
var icon: Bitmap,
var dotColor: Int,
var dotPath: Path,
- var appName: String
+ var appName: String,
+ var flyoutMessage: BubbleBarFlyoutMessage?,
) : BubbleBarItem(info.key, view)
/** Represents the overflow bubble in the bubble bar. */
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarSwipeController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarSwipeController.kt
index a831fd7..2d3642b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarSwipeController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarSwipeController.kt
@@ -25,6 +25,10 @@
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.BarState.COLLAPSED
+import com.android.launcher3.taskbar.bubbles.BubbleBarSwipeController.BarState.EXPANDED
+import com.android.launcher3.taskbar.bubbles.BubbleBarSwipeController.BarState.STASHED
+import com.android.launcher3.taskbar.bubbles.BubbleBarSwipeController.BarState.UNKNOWN
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController
import com.android.launcher3.touch.OverScroll
@@ -34,17 +38,16 @@
private val context: Context
private var bubbleStashedHandleViewController: BubbleStashedHandleViewController? = null
- private var bubbleBarViewController: BubbleBarViewController? = null
- private var bubbleStashController: BubbleStashController? = null
+ private lateinit var bubbleBarViewController: BubbleBarViewController
+ private lateinit var bubbleStashController: BubbleStashController
private var springAnimation: ValueAnimator? = null
private val animatedSwipeTranslation = AnimatedFloat(this::onSwipeUpdate)
private val unstashThreshold: Int
- private val expandThreshold: Int
private val maxOverscroll: Int
- private var swipeState: SwipeState = SwipeState()
+ private var swipeState: SwipeState = SwipeState(startState = UNKNOWN)
constructor(tac: TaskbarActivityContext) : this(tac, DefaultDimensionProvider(tac))
@@ -52,7 +55,6 @@
constructor(context: Context, dimensionProvider: DimensionProvider) {
this.context = context
unstashThreshold = dimensionProvider.unstashThreshold
- expandThreshold = dimensionProvider.expandThreshold
maxOverscroll = dimensionProvider.maxOverscroll
}
@@ -66,70 +68,83 @@
/** Start tracking a new swipe gesture */
fun start() {
if (springAnimation != null) reset()
- val stashed = bubbleStashController?.isStashed ?: false
- val barVisible = bubbleStashController?.isBubbleBarVisible() ?: false
- val expanded = bubbleBarViewController?.isExpanded ?: false
-
- swipeState =
- SwipeState(
- stashedOnStart = stashed,
- collapsedOnStart = !stashed && barVisible && !expanded,
- expandedOnStart = expanded,
- )
+ val startState =
+ when {
+ bubbleStashController.isStashed -> STASHED
+ bubbleBarViewController.isExpanded -> EXPANDED
+ bubbleStashController.isBubbleBarVisible() -> COLLAPSED
+ else -> UNKNOWN
+ }
+ swipeState = SwipeState(startState = startState, currentState = startState)
}
/** Update swipe distance to [dy] */
fun swipeTo(dy: Float) {
- // Only handle swipe up and stashed or collapsed bar
- if (dy > 0 || swipeState.expandedOnStart) return
-
+ if (!canHandleSwipe(dy)) {
+ return
+ }
animatedSwipeTranslation.updateValue(dy)
- val prevState = swipeState
- // 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)
-
- if (
- passedUnstashThreshold != prevState.passedUnstashThreshold ||
- passedExpandThreshold != prevState.passedExpandThreshold
- ) {
- swipeState =
- swipeState.copy(
- passedUnstashThreshold = passedUnstashThreshold,
- passedExpandThreshold = passedExpandThreshold,
- )
- }
-
- if (
- swipeState.stashedOnStart &&
- swipeState.passedUnstashThreshold &&
- !prevState.passedUnstashThreshold
- ) {
- bubbleStashController?.showBubbleBar(expandBubbles = false)
+ swipeState.passedUnstash = isUnstash(dy)
+ // Tracking swipe gesture if we pass unstash threshold at least once during gesture
+ swipeState.isSwipe = swipeState.isSwipe || swipeState.passedUnstash
+ when {
+ canUnstash() && swipeState.passedUnstash -> {
+ swipeState.currentState = COLLAPSED
+ bubbleStashController.showBubbleBar(expandBubbles = false)
+ }
+ canStash() && !swipeState.passedUnstash -> {
+ swipeState.currentState = STASHED
+ bubbleStashController.stashBubbleBar()
+ }
}
}
/** Finish tracking swipe gesture. Animate views back to resting state */
fun finish() {
- if (swipeState.passedExpandThreshold) {
- bubbleStashController?.showBubbleBar(expandBubbles = true)
+ if (swipeState.passedUnstash && swipeState.startState in setOf(STASHED, COLLAPSED)) {
+ bubbleStashController.showBubbleBar(expandBubbles = true)
}
- springToRest()
+ if (animatedSwipeTranslation.value == 0f) {
+ reset()
+ } else {
+ springToRest()
+ }
}
/** Returns `true` if we are tracking a swipe gesture */
fun isSwipeGesture(): Boolean {
- return swipeState.passedUnstashThreshold || swipeState.passedExpandThreshold
+ return swipeState.isSwipe
+ }
+
+ private fun canHandleSwipe(dy: Float): Boolean {
+ return when (swipeState.startState) {
+ STASHED -> {
+ if (swipeState.currentState == COLLAPSED) {
+ // if we have unstashed the bar, allow swipe in both directions
+ true
+ } else {
+ // otherwise, only allow swipe up on stash handle
+ dy < 0
+ }
+ }
+ COLLAPSED -> dy < 0 // collapsed bar can only be swiped up
+ 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 canStash(): Boolean {
+ // Only allow stashing if we started from stashed state
+ return swipeState.startState == STASHED && swipeState.currentState == COLLAPSED
+ }
+
+ private fun canUnstash(): Boolean {
+ return swipeState.currentState == STASHED
}
private fun reset() {
@@ -141,13 +156,13 @@
}
}
springAnimation = null
- swipeState = SwipeState()
+ swipeState = SwipeState(startState = UNKNOWN)
}
private fun onSwipeUpdate(value: Float) {
val dampedSwipe = -OverScroll.dampedScroll(-value, maxOverscroll).toFloat()
bubbleStashedHandleViewController?.setTranslationYForSwipe(dampedSwipe)
- bubbleBarViewController?.setTranslationYForSwipe(dampedSwipe)
+ bubbleBarViewController.setTranslationYForSwipe(dampedSwipe)
}
private fun springToRest() {
@@ -163,25 +178,29 @@
}
internal data class SwipeState(
- val stashedOnStart: Boolean = false,
- val collapsedOnStart: Boolean = false,
- val expandedOnStart: Boolean = false,
- val passedUnstashThreshold: Boolean = false,
- val passedExpandThreshold: Boolean = false,
+ val startState: BarState,
+ var currentState: BarState = UNKNOWN,
+ var passedUnstash: Boolean = false,
+ var isSwipe: Boolean = false,
)
+ internal enum class BarState {
+ UNKNOWN,
+ STASHED,
+ COLLAPSED,
+ EXPANDED,
+ }
+
/** Allows overriding the dimension provider for testing */
@VisibleForTesting
interface DimensionProvider {
val unstashThreshold: Int
- val expandThreshold: Int
val maxOverscroll: Int
}
private class DefaultDimensionProvider(taskbarActivityContext: TaskbarActivityContext) :
DimensionProvider {
override val unstashThreshold: Int
- override val expandThreshold: Int
override val maxOverscroll: Int
init {
@@ -191,12 +210,6 @@
resources,
taskbarActivityContext.deviceProfile,
)
- // TODO(325673340): review threshold with ux
- expandThreshold =
- TaskbarThresholdUtils.getAppWindowThreshold(
- resources,
- taskbarActivityContext.deviceProfile,
- )
maxOverscroll = taskbarActivityContext.deviceProfile.heightPx - unstashThreshold
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index d454fd7..c5d649e 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}.
@@ -1555,6 +1492,38 @@
return bubbles;
}
+ /**
+ * Returns the distance between the top left corner of the bubble bar to the center of the dot
+ * of the selected bubble.
+ */
+ PointF getSelectedBubbleDotDistanceFromTopLeft() {
+ if (mSelectedBubbleView == null) {
+ return new PointF(0, 0);
+ }
+ final int indexOfSelectedBubble = indexOfChild(mSelectedBubbleView);
+ final boolean onLeft = mBubbleBarLocation.isOnLeft(isLayoutRtl());
+ final float selectedBubbleTx = isExpanded()
+ ? getExpandedBubbleTranslationX(indexOfSelectedBubble, getChildCount(), onLeft)
+ : getCollapsedBubbleTranslationX(indexOfSelectedBubble, getChildCount(), onLeft);
+ PointF selectedBubbleDotCenter = mSelectedBubbleView.getDotCenter();
+
+ return new PointF(
+ selectedBubbleTx + selectedBubbleDotCenter.x,
+ mBubbleBarPadding + mPointerSize + selectedBubbleDotCenter.y);
+ }
+
+ int getSelectedBubbleDotColor() {
+ return mSelectedBubbleView == null ? 0 : mSelectedBubbleView.getDotColor();
+ }
+
+ int getPointerSize() {
+ return mPointerSize;
+ }
+
+ float getBubbleElevation() {
+ return mBubbleElevation;
+ }
+
/** Interface for BubbleBarView to communicate with its controller. */
interface Controller {
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 025c038..63f101f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -29,6 +29,7 @@
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
+import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -42,6 +43,8 @@
import com.android.launcher3.taskbar.TaskbarInsetsController;
import com.android.launcher3.taskbar.TaskbarStashController;
import com.android.launcher3.taskbar.bubbles.animation.BubbleBarViewAnimator;
+import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutController;
+import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutPositioner;
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.MultiValueAlpha;
@@ -63,6 +66,8 @@
private static final float APP_ICON_SMALL_DP = 44f;
private static final float APP_ICON_MEDIUM_DP = 48f;
private static final float APP_ICON_LARGE_DP = 52f;
+ /** The dot size is defined as a percentage of the icon size. */
+ private static final float DOT_TO_BUBBLE_SIZE_RATIO = 0.228f;
private final SystemUiProxy mSystemUiProxy;
private final TaskbarActivityContext mActivity;
private final BubbleBarView mBarView;
@@ -106,20 +111,26 @@
private boolean mHiddenForSysui;
// Whether the bar is hidden because there are no bubbles.
private boolean mHiddenForNoBubbles = true;
+ // Whether the bar is hidden when stashed
+ private boolean mHiddenForStashed;
private boolean mShouldShowEducation;
public boolean mOverflowAdded;
private BubbleBarViewAnimator mBubbleBarViewAnimator;
+ private final FrameLayout mBubbleBarContainer;
+ private BubbleBarFlyoutController mBubbleBarFlyoutController;
private final TimeSource mTimeSource = System::currentTimeMillis;
@Nullable
private BubbleBarBoundsChangeListener mBoundsChangeListener;
- public BubbleBarViewController(TaskbarActivityContext activity, BubbleBarView barView) {
+ public BubbleBarViewController(TaskbarActivityContext activity, BubbleBarView barView,
+ FrameLayout bubbleBarContainer) {
mActivity = activity;
mBarView = barView;
+ mBubbleBarContainer = bubbleBarContainer;
mSystemUiProxy = SystemUiProxy.INSTANCE.get(mActivity);
mBubbleBarAlpha = new MultiValueAlpha(mBarView, 1 /* num alpha channels */);
mIconSize = activity.getResources().getDimensionPixelSize(
@@ -134,6 +145,8 @@
mBubbleDragController = bubbleControllers.bubbleDragController;
mTaskbarStashController = controllers.taskbarStashController;
mTaskbarInsetsController = controllers.taskbarInsetsController;
+ mBubbleBarFlyoutController = new BubbleBarFlyoutController(
+ mBubbleBarContainer, createFlyoutPositioner(), createFlyoutTopBoundaryListener());
mBubbleBarViewAnimator = new BubbleBarViewAnimator(
mBarView, mBubbleStashController, mBubbleBarController::showExpandedView);
mTaskbarViewPropertiesProvider = taskbarViewPropertiesProvider;
@@ -206,6 +219,74 @@
};
}
+ private BubbleBarFlyoutPositioner createFlyoutPositioner() {
+ return new BubbleBarFlyoutPositioner() {
+
+ @Override
+ public boolean isOnLeft() {
+ return mBarView.getBubbleBarLocation().isOnLeft(mBarView.isLayoutRtl());
+ }
+
+ @Override
+ public float getTargetTy() {
+ return mBarView.getTranslationY() - mBarView.getHeight();
+ }
+
+ @Override
+ @NonNull
+ public PointF getDistanceToCollapsedPosition() {
+ // the flyout animates from the selected bubble dot. calculate the distance it needs
+ // to translate itself to its starting position.
+ PointF distanceToDotCenter = mBarView.getSelectedBubbleDotDistanceFromTopLeft();
+
+ // if we're gravitating left, return the distance between the top left corner of the
+ // bubble bar and the bottom left corner of the dot.
+ // if we're gravitating right, return the distance between the top right corner of
+ // the bubble bar and the bottom right corner of the dot.
+ float distanceX = isOnLeft()
+ ? distanceToDotCenter.x - getCollapsedSize() / 2
+ : mBarView.getWidth() - distanceToDotCenter.x - getCollapsedSize() / 2;
+ float distanceY = distanceToDotCenter.y + getCollapsedSize() / 2;
+ return new PointF(distanceX, distanceY);
+ }
+
+ @Override
+ public float getCollapsedSize() {
+ return mIconSize * DOT_TO_BUBBLE_SIZE_RATIO;
+ }
+
+ @Override
+ public int getCollapsedColor() {
+ return mBarView.getSelectedBubbleDotColor();
+ }
+
+ @Override
+ public float getCollapsedElevation() {
+ return mBarView.getBubbleElevation();
+ }
+
+ @Override
+ public float getDistanceToRevealTriangle() {
+ return getDistanceToCollapsedPosition().y - mBarView.getPointerSize();
+ }
+ };
+ }
+
+ private BubbleBarFlyoutController.TopBoundaryListener createFlyoutTopBoundaryListener() {
+ return new BubbleBarFlyoutController.TopBoundaryListener() {
+ @Override
+ public void extendTopBoundary(int space) {
+ int defaultSize = mActivity.getDefaultTaskbarWindowSize();
+ mActivity.setTaskbarWindowSize(defaultSize + space);
+ }
+
+ @Override
+ public void resetTopBoundary() {
+ mActivity.setTaskbarWindowSize(mActivity.getDefaultTaskbarWindowSize());
+ }
+ };
+ }
+
private void onBubbleClicked(BubbleView bubbleView) {
bubbleView.markSeen();
BubbleBarItem bubble = bubbleView.getBubble();
@@ -467,9 +548,17 @@
}
}
+ /** Sets whether the bubble bar should be hidden due to stashed state */
+ public void setHiddenForStashed(boolean hidden) {
+ if (mHiddenForStashed != hidden) {
+ mHiddenForStashed = hidden;
+ updateVisibilityForStateChange();
+ }
+ }
+
// TODO: (b/273592694) animate it
private void updateVisibilityForStateChange() {
- if (!mHiddenForSysui && !mHiddenForNoBubbles) {
+ if (!mHiddenForSysui && !mHiddenForNoBubbles && !mHiddenForStashed) {
mBarView.setVisibility(VISIBLE);
} else {
mBarView.setVisibility(INVISIBLE);
@@ -675,7 +764,8 @@
// if the animation is suppressed, immediately stash or show the bubble bar to
// ensure they've been initialized.
if (mTaskbarStashController.isInApp()
- && mBubbleStashController.isTransientTaskBar()) {
+ && mBubbleStashController.isTransientTaskBar()
+ && mTaskbarStashController.isStashed()) {
mBubbleStashController.stashBubbleBarImmediate();
} else {
mBubbleStashController.showBubbleBarImmediate();
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
index 8230f42..b5d94bd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
@@ -22,6 +22,7 @@
import com.android.launcher3.taskbar.TaskbarControllers;
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController.TaskbarViewPropertiesProvider;
+import com.android.launcher3.taskbar.bubbles.stashing.BubbleBarLocationOnDemandListener;
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.RunnableList;
@@ -79,11 +80,11 @@
* in constructors for now, as some controllers may still be waiting for init().
*/
public void init(TaskbarControllers taskbarControllers) {
- // TODO(b/346381754) add TaskbarLauncherStateController implementation to adjust the hotseat
BubbleBarLocationCompositeListener bubbleBarLocationListeners =
new BubbleBarLocationCompositeListener(
taskbarControllers.navbarButtonsViewController,
- taskbarControllers.taskbarViewController
+ taskbarControllers.taskbarViewController,
+ new BubbleBarLocationOnDemandListener(() -> taskbarControllers.uiController)
);
bubbleBarController.init(this,
bubbleBarLocationListeners,
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleCreator.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleCreator.java
index 340a120..c5efe2f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleCreator.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleCreator.java
@@ -22,6 +22,7 @@
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER;
import static com.android.launcher3.icons.FastBitmapDrawable.WHITE_SCRIM_ALPHA;
+import static com.android.wm.shell.shared.bubbles.FlyoutDrawableLoader.loadFlyoutDrawable;
import android.annotation.Nullable;
import android.content.Context;
@@ -49,7 +50,9 @@
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.BubbleIconFactory;
import com.android.launcher3.shortcuts.ShortcutRequest;
+import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutMessage;
import com.android.wm.shell.shared.bubbles.BubbleInfo;
+import com.android.wm.shell.shared.bubbles.ParcelableFlyoutMessage;
/**
* Loads the necessary info to populate / present a bubble (name, icon, shortcut).
@@ -157,13 +160,16 @@
dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color,
Color.WHITE, WHITE_SCRIM_ALPHA / 255f);
+ final BubbleBarFlyoutMessage flyoutMessage =
+ getFlyoutMessage(info.getParcelableFlyoutMessage());
+
if (existingBubble == null) {
LayoutInflater inflater = LayoutInflater.from(context);
BubbleView bubbleView = (BubbleView) inflater.inflate(
R.layout.bubblebar_item_view, barView, false /* attachToRoot */);
BubbleBarBubble bubble = new BubbleBarBubble(info, bubbleView,
- badgeBitmap, bubbleBitmap, dotColor, dotPath, appName);
+ badgeBitmap, bubbleBitmap, dotColor, dotPath, appName, flyoutMessage);
bubbleView.setBubble(bubble);
return bubble;
} else {
@@ -174,10 +180,25 @@
existingBubble.setDotColor(dotColor);
existingBubble.setDotPath(dotPath);
existingBubble.setAppName(appName);
+ existingBubble.setFlyoutMessage(flyoutMessage);
return existingBubble;
}
}
+ @Nullable
+ private BubbleBarFlyoutMessage getFlyoutMessage(
+ @Nullable ParcelableFlyoutMessage parcelableFlyoutMessage) {
+ if (parcelableFlyoutMessage == null) {
+ return null;
+ }
+ String title = parcelableFlyoutMessage.getTitle();
+ String message = parcelableFlyoutMessage.getMessage();
+ return new BubbleBarFlyoutMessage(
+ loadFlyoutDrawable(parcelableFlyoutMessage.getIcon(), mContext),
+ title == null ? "" : title,
+ message == null ? "" : message);
+ }
+
/**
* Creates the overflow view shown in the bubble bar.
*
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
index 561df5c..707655c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
@@ -22,6 +22,7 @@
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Path;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
@@ -67,8 +68,7 @@
private float mAnimatingToDotScale;
// The current scale value of the dot
private float mDotScale;
-
- private boolean mProvideShadowOutline = true;
+ private boolean mDotSuppressedForBubbleUpdate = false;
// TODO: (b/273310265) handle RTL
// Whether the bubbles are positioned on the left or right side of the screen
@@ -300,6 +300,10 @@
}
void updateDotVisibility(boolean animate) {
+ if (mDotSuppressedForBubbleUpdate) {
+ // if the dot is suppressed for
+ return;
+ }
final float targetScale = hasUnseenContent() ? 1f : 0f;
if (animate) {
animateDotScale(targetScale);
@@ -317,6 +321,20 @@
}
}
+ /**
+ * Suppresses or un-suppresses drawing the dot due to an update for this bubble.
+ *
+ * <p>If the dot is being suppressed and is already visible, it remains visible because it is
+ * used as a starting point for the animation. If the dot is being unsuppressed, it is
+ * redrawn if needed.
+ */
+ public void suppressDotForBubbleUpdate(boolean suppress) {
+ mDotSuppressedForBubbleUpdate = suppress;
+ if (!suppress) {
+ showDotIfNeeded(/* animate= */ false);
+ }
+ }
+
boolean hasUnseenContent() {
return mBubble != null
&& mBubble instanceof BubbleBarBubble
@@ -353,8 +371,8 @@
}
void showDotIfNeeded(boolean animate) {
- // only show the dot if we have unseen content
- if (!hasUnseenContent()) {
+ // only show the dot if we have unseen content and it's not suppressed
+ if (!hasUnseenContent() || mDotSuppressedForBubbleUpdate) {
return;
}
if (animate) {
@@ -406,6 +424,23 @@
}).start();
}
+ /**
+ * Returns the distance from the top left corner of this bubble view to the center of its dot.
+ */
+ public PointF getDotCenter() {
+ float[] dotPosition =
+ mOnLeft ? mDotRenderer.getLeftDotPosition() : mDotRenderer.getRightDotPosition();
+ getDrawingRect(mTempBounds);
+ float dotCenterX = mTempBounds.width() * dotPosition[0];
+ float dotCenterY = mTempBounds.height() * dotPosition[1];
+ return new PointF(dotCenterX, dotCenterY);
+ }
+
+ /** Returns the dot color. */
+ public int getDotColor() {
+ return mDotColor;
+ }
+
@Override
public String toString() {
String toString = mBubble != null ? mBubble.getKey() : "null";
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 49760ff..c431deb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutController.kt
@@ -21,20 +21,30 @@
import android.widget.FrameLayout
import androidx.core.animation.ValueAnimator
import com.android.launcher3.R
+import com.android.systemui.util.doOnEnd
+import com.android.systemui.util.doOnStart
/** Creates and manages the visibility of the [BubbleBarFlyoutView]. */
-class BubbleBarFlyoutController(
+class BubbleBarFlyoutController
+@JvmOverloads
+constructor(
private val container: FrameLayout,
private val positioner: BubbleBarFlyoutPositioner,
+ private val topBoundaryListener: TopBoundaryListener,
+ private val flyoutScheduler: FlyoutScheduler = HandlerScheduler(container),
) {
+ private companion object {
+ const val EXPAND_COLLAPSE_ANIMATION_DURATION_MS = 250L
+ }
+
private var flyout: BubbleBarFlyoutView? = null
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, positioner)
+ val flyout = BubbleBarFlyoutView(container.context, positioner, flyoutScheduler)
flyout.translationY = positioner.targetTy
@@ -48,17 +58,43 @@
lp.marginEnd = horizontalMargin
container.addView(flyout, lp)
- val animator = ValueAnimator.ofFloat(0f, 1f)
+ val animator =
+ ValueAnimator.ofFloat(0f, 1f).setDuration(EXPAND_COLLAPSE_ANIMATION_DURATION_MS)
animator.addUpdateListener { _ ->
flyout.updateExpansionProgress(animator.animatedValue as Float)
}
+ animator.doOnStart {
+ val flyoutTop = flyout.top + flyout.translationY
+ // If the top position of the flyout is negative, then it's bleeding over the
+ // top boundary of its parent view
+ if (flyoutTop < 0) topBoundaryListener.extendTopBoundary(space = -flyoutTop.toInt())
+ }
flyout.showFromCollapsed(message) { animator.start() }
this.flyout = flyout
}
- fun hideFlyout() {
+ fun hideFlyout(endAction: () -> Unit) {
val flyout = this.flyout ?: return
- container.removeView(flyout)
- this.flyout = null
+ val animator =
+ ValueAnimator.ofFloat(1f, 0f).setDuration(EXPAND_COLLAPSE_ANIMATION_DURATION_MS)
+ animator.addUpdateListener { _ ->
+ flyout.updateExpansionProgress(animator.animatedValue as Float)
+ }
+ animator.doOnEnd {
+ container.removeView(flyout)
+ this@BubbleBarFlyoutController.flyout = null
+ topBoundaryListener.resetTopBoundary()
+ endAction()
+ }
+ animator.start()
+ }
+
+ /** Notifies when the top boundary of the flyout view changes. */
+ interface TopBoundaryListener {
+ /** Requests to extend the top boundary of the parent to fully include the flyout. */
+ fun extendTopBoundary(space: Int)
+
+ /** Resets the top boundary of the parent. */
+ fun resetTopBoundary()
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutMessage.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutMessage.kt
index 7298297..14b456c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutMessage.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutMessage.kt
@@ -18,9 +18,4 @@
import android.graphics.drawable.Drawable
-data class BubbleBarFlyoutMessage(
- val senderAvatar: Drawable?,
- val senderName: CharSequence,
- val message: CharSequence,
- val isGroupChat: Boolean,
-)
+data class BubbleBarFlyoutMessage(val icon: Drawable?, val title: String, val message: String)
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 2b77dec..aa2555e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutPositioner.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutPositioner.kt
@@ -36,4 +36,16 @@
/** 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 8884b64..c60fba2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutView.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutView.kt
@@ -20,30 +20,39 @@
import android.content.res.Configuration
import android.graphics.Canvas
import android.graphics.Color
+import android.graphics.Outline
import android.graphics.Paint
import android.graphics.Path
import android.graphics.PointF
+import android.graphics.RectF
import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewOutlineProvider
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.animation.ArgbEvaluator
import com.android.launcher3.R
import com.android.launcher3.popup.RoundedArrowDrawable
/** The flyout view used to notify the user of a new bubble notification. */
-class BubbleBarFlyoutView(context: Context, private val positioner: BubbleBarFlyoutPositioner) :
- ConstraintLayout(context) {
+class BubbleBarFlyoutView(
+ context: Context,
+ private val positioner: BubbleBarFlyoutPositioner,
+ scheduler: FlyoutScheduler? = null,
+) : ConstraintLayout(context) {
private companion object {
- // the minimum progress of the expansion animation before the triangle is made visible.
- const val MIN_EXPANSION_PROGRESS_FOR_TRIANGLE = 0.1f
+ // 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) }
+ private val scheduler: FlyoutScheduler = scheduler ?: HandlerScheduler(this)
+ private val title: TextView by
+ lazy(LazyThreadSafetyMode.NONE) { findViewById(R.id.bubble_flyout_title) }
- private val avatar: ImageView by
- lazy(LazyThreadSafetyMode.NONE) { findViewById(R.id.bubble_flyout_avatar) }
+ private val icon: ImageView by
+ lazy(LazyThreadSafetyMode.NONE) { findViewById(R.id.bubble_flyout_icon) }
private val message: TextView by
lazy(LazyThreadSafetyMode.NONE) { findViewById(R.id.bubble_flyout_text) }
@@ -85,8 +94,16 @@
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
@@ -96,6 +113,24 @@
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
@@ -111,14 +146,13 @@
ta.recycle()
setWillNotDraw(false)
- clipChildren = false
+ clipChildren = true
clipToPadding = false
val padding = context.resources.getDimensionPixelSize(R.dimen.bubblebar_flyout_padding)
// add extra padding to the bottom of the view to include the triangle
setPadding(padding, padding, padding, padding + triangleHeight - triangleOverlap)
- translationZ =
- context.resources.getDimensionPixelSize(R.dimen.bubblebar_flyout_elevation).toFloat()
+ translationZ = flyoutElevation
RoundedArrowDrawable.addDownPointingRoundedTriangleToPath(
triangleWidth.toFloat(),
@@ -126,12 +160,24 @@
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)
}
/** Sets the data for the flyout and starts playing the expand animation. */
fun showFromCollapsed(flyoutMessage: BubbleBarFlyoutMessage, expandAnimation: () -> Unit) {
+ icon.alpha = 0f
+ title.alpha = 0f
+ message.alpha = 0f
setData(flyoutMessage)
val txToCollapsedPosition =
if (positioner.isOnLeft) {
@@ -145,26 +191,32 @@
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)
+ scheduler.runAfterLayout(expandAnimation)
}
private fun setData(flyoutMessage: BubbleBarFlyoutMessage) {
- // the avatar is only displayed in group chat messages
- if (flyoutMessage.senderAvatar != null && flyoutMessage.isGroupChat) {
- avatar.visibility = VISIBLE
- avatar.setImageDrawable(flyoutMessage.senderAvatar)
+ if (flyoutMessage.icon != null) {
+ icon.visibility = VISIBLE
+ icon.setImageDrawable(flyoutMessage.icon)
} else {
- avatar.visibility = GONE
+ icon.visibility = GONE
}
val minTextViewWidth: Int
val maxTextViewWidth: Int
- if (avatar.visibility == VISIBLE) {
- minTextViewWidth = minFlyoutWidth - avatar.width - flyoutPadding * 2
- maxTextViewWidth = maxFlyoutWidth - avatar.width - flyoutPadding * 2
+ if (icon.visibility == VISIBLE) {
+ minTextViewWidth = minFlyoutWidth - icon.width - flyoutPadding * 2
+ maxTextViewWidth = maxFlyoutWidth - icon.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
@@ -172,13 +224,13 @@
maxTextViewWidth = minTextViewWidth
}
- if (flyoutMessage.senderName.isEmpty()) {
- sender.visibility = GONE
+ if (flyoutMessage.title.isEmpty()) {
+ title.visibility = GONE
} else {
- sender.minWidth = minTextViewWidth
- sender.maxWidth = maxTextViewWidth
- sender.text = flyoutMessage.senderName
- sender.visibility = VISIBLE
+ title.minWidth = minTextViewWidth
+ title.maxWidth = maxTextViewWidth
+ title.text = flyoutMessage.title
+ title.visibility = VISIBLE
}
message.minWidth = minTextViewWidth
@@ -189,42 +241,67 @@
/** Updates the flyout view with the progress of the animation. */
fun updateExpansionProgress(fraction: Float) {
expansionProgress = fraction
+
+ updateTranslationForAnimation(message)
+ updateTranslationForAnimation(title)
+ updateTranslationForAnimation(icon)
+
+ // 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)
+ title.alpha = alpha
+ message.alpha = alpha
+ icon.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
+ // 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
- val currentCornerRadius =
- collapsedCornerRadius + (cornerRadius - collapsedCornerRadius) * expansionProgress
- val tx = translationToCollapsedPosition.x * (1 - expansionProgress)
- val ty = translationToCollapsedPosition.y * (1 - expansionProgress)
- canvas.save()
- canvas.translate(tx, ty)
- // draw the background starting from the bottom left if we're positioned left, or the bottom
- // right if we're positioned right.
- canvas.drawRoundRect(
+ backgroundRect.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(
+ backgroundRect,
currentCornerRadius,
currentCornerRadius,
backgroundPaint,
)
- if (expansionProgress >= MIN_EXPANSION_PROGRESS_FOR_TRIANGLE) {
- drawTriangle(canvas, currentCornerRadius)
+ if (expansionProgress >= minExpansionProgressForTriangle) {
+ drawTriangle(canvas)
}
canvas.restore()
+ invalidateOutline()
super.onDraw(canvas)
}
- private fun drawTriangle(canvas: Canvas, currentCornerRadius: Float) {
+ private fun drawTriangle(canvas: Canvas) {
canvas.save()
val triangleX =
if (positioner.isOnLeft) {
@@ -232,14 +309,54 @@
} else {
width - currentCornerRadius - triangleWidth
}
- // instead of scaling the triangle, increasingly reveal it from the background, starting
- // with half the size. this has the effect of the triangle scaling.
- val triangleY = height - triangleHeight - 0.5f * triangleHeight * (1 - expansionProgress)
+ // 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
@@ -254,7 +371,7 @@
)
)
backgroundColor = ta.getColor(0, defaultBackgroundColor)
- sender.setTextColor(ta.getColor(1, defaultTextColor))
+ title.setTextColor(ta.getColor(1, defaultTextColor))
message.setTextColor(ta.getColor(2, defaultTextColor))
ta.recycle()
backgroundPaint.color = backgroundColor
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/FlyoutScheduler.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/FlyoutScheduler.kt
new file mode 100644
index 0000000..6f5d700
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/FlyoutScheduler.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.flyout
+
+import android.view.View
+
+/** Interface for scheduling jobs by flyout. */
+fun interface FlyoutScheduler {
+ /** Runs the given [block] after layout. */
+ fun runAfterLayout(block: () -> Unit)
+}
+
+/** A [FlyoutScheduler] that uses a Handler to schedule jobs. */
+class HandlerScheduler(val view: View) : FlyoutScheduler {
+ override fun runAfterLayout(block: () -> Unit) {
+ view.post(block)
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleBarLocationOnDemandListener.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleBarLocationOnDemandListener.kt
new file mode 100644
index 0000000..ffe7c44
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleBarLocationOnDemandListener.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.bubbles.stashing
+
+import com.android.launcher3.taskbar.bubbles.BubbleBarController.BubbleBarLocationListener
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation
+
+/** On demand implementation of [BubbleBarLocationListener]. */
+class BubbleBarLocationOnDemandListener(
+ private val listenerProvider: () -> BubbleBarLocationListener
+) : BubbleBarLocationListener {
+
+ override fun onBubbleBarLocationAnimated(location: BubbleBarLocation) {
+ listenerProvider().onBubbleBarLocationAnimated(location)
+ }
+
+ override fun onBubbleBarLocationUpdated(location: BubbleBarLocation) {
+ listenerProvider().onBubbleBarLocationUpdated(location)
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt
index 9721792..a78890b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt
@@ -56,14 +56,29 @@
fun runAfterInit(action: Runnable)
}
+ /** Launcher states bubbles cares about */
+ enum class BubbleLauncherState {
+ /* When launcher is in overview */
+ OVERVIEW,
+ /* When launcher is on home */
+ HOME,
+ /* We're in an app */
+ IN_APP,
+ }
+
+ /** The current launcher state */
+ var launcherState: BubbleLauncherState
+
/** Whether bubble bar is currently stashed */
val isStashed: Boolean
/** Whether launcher enters or exits the home page. */
- var isBubblesShowingOnHome: Boolean
+ val isBubblesShowingOnHome: Boolean
+ get() = launcherState == BubbleLauncherState.HOME
/** Whether launcher enters or exits the overview page. */
- var isBubblesShowingOnOverview: Boolean
+ val isBubblesShowingOnOverview: Boolean
+ get() = launcherState == BubbleLauncherState.OVERVIEW
/** Updated when sysui locked state changes, when locked, bubble bar is not shown. */
var isSysuiLocked: Boolean
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashController.kt
index 7d6f7ad..722dfe7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashController.kt
@@ -26,6 +26,7 @@
import com.android.launcher3.taskbar.TaskbarInsetsController
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController
import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController
+import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.BubbleLauncherState
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.BAR_STASH_DURATION
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.BAR_TRANSLATION_DURATION
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.ControllersAfterInitAction
@@ -45,29 +46,21 @@
private lateinit var bubbleBarScaleAnimator: AnimatedFloat
private lateinit var controllersAfterInitAction: ControllersAfterInitAction
- override var isBubblesShowingOnHome: Boolean = false
- set(onHome) {
- if (field == onHome) return
- field = onHome
+ override var launcherState: BubbleLauncherState = BubbleLauncherState.IN_APP
+ set(state) {
+ if (field == state) return
+ val transitionFromHome = field == BubbleLauncherState.HOME
+ field = state
if (!bubbleBarViewController.hasBubbles()) {
// if there are no bubbles, there's nothing to show, so just return.
return
}
- if (onHome) {
- // When transition to home we should show collapse the bubble bar
- updateExpandedState(expand = false)
- }
- animateBubbleBarY()
- bubbleBarViewController.onBubbleBarConfigurationChanged(/* animate= */ true)
- }
-
- override var isBubblesShowingOnOverview: Boolean = false
- set(onOverview) {
- if (field == onOverview) return
- field = onOverview
- if (!onOverview) {
- // When transition from overview we should show collapse the bubble bar
- updateExpandedState(expand = false)
+ // If we're transitioning anywhere, bubble bar should be collapsed
+ updateExpandedState(expand = false)
+ if (transitionFromHome || field == BubbleLauncherState.HOME) {
+ // If we're transitioning to or from home, animate the Y because we're in hotseat
+ // on home but in persistent taskbar elsewhere so the position is different.
+ animateBubbleBarY()
}
bubbleBarViewController.onBubbleBarConfigurationChanged(/* animate= */ true)
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
index 74e3c00..9e7d1c4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
@@ -36,6 +36,7 @@
import com.android.launcher3.taskbar.TaskbarStashController.TRANSIENT_TASKBAR_STASH_ALPHA_DURATION
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController
import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController
+import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.BubbleLauncherState
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.BAR_STASH_DURATION
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.BAR_TRANSLATION_DURATION
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.ControllersAfterInitAction
@@ -81,36 +82,26 @@
override var isStashed: Boolean = false
@VisibleForTesting set
- override var isBubblesShowingOnHome: Boolean = false
- set(onHome) {
- if (field == onHome) return
- field = onHome
+ override var launcherState: BubbleLauncherState = BubbleLauncherState.IN_APP
+ set(state) {
+ if (field == state) return
+ field = state
if (!bubbleBarViewController.hasBubbles()) {
// if there are no bubbles, there's nothing to show, so just return.
return
}
- if (onHome) {
- updateStashedAndExpandedState(stash = false, expand = false)
- // When transitioning from app to home we need to animate the bubble bar
+ if (field == BubbleLauncherState.HOME) {
+ // When to home we need to animate the bubble bar
// here to align with hotseat center.
animateBubbleBarYToHotseat()
- } else if (!bubbleBarViewController.isExpanded) {
- updateStashedAndExpandedState(stash = true, expand = false)
- }
- bubbleBarViewController.onBubbleBarConfigurationChanged(/* animate= */ true)
- }
-
- override var isBubblesShowingOnOverview: Boolean = false
- set(onOverview) {
- if (field == onOverview) return
- field = onOverview
- if (onOverview) {
+ } else if (field == BubbleLauncherState.OVERVIEW) {
// When transitioning to overview we need to animate the bubble bar to align with
// the taskbar bottom.
animateBubbleBarYToTaskbar()
- } else {
- updateStashedAndExpandedState(stash = true, expand = false)
}
+ // Only stash if we're in an app, otherwise we're in home or overview where we should
+ // be un-stashed
+ updateStashedAndExpandedState(field == BubbleLauncherState.IN_APP, expand = false)
bubbleBarViewController.onBubbleBarConfigurationChanged(/* animate= */ true)
}
@@ -178,7 +169,11 @@
isStashed = true
stashHandleViewAlpha?.let { animatorSet.playTogether(it.animateToValue(1f)) }
}
- animatorSet.updateTouchRegionOnAnimationEnd().setDuration(BAR_STASH_DURATION).start()
+ animatorSet
+ .updateBarVisibility(isStashed)
+ .updateTouchRegionOnAnimationEnd()
+ .setDuration(BAR_STASH_DURATION)
+ .start()
}
override fun showBubbleBarImmediate() {
@@ -195,6 +190,7 @@
bubbleBarBackgroundScaleX.updateValue(1f)
bubbleBarBackgroundScaleY.updateValue(1f)
isStashed = false
+ bubbleBarViewController.setHiddenForStashed(false)
onIsStashedChanged()
}
@@ -209,6 +205,7 @@
bubbleBarBackgroundScaleX.updateValue(getStashScaleX())
bubbleBarBackgroundScaleY.updateValue(getStashScaleY())
isStashed = true
+ bubbleBarViewController.setHiddenForStashed(true)
onIsStashedChanged()
}
@@ -490,6 +487,7 @@
animator?.cancel()
animator =
createStashAnimator(isStashed, BAR_STASH_DURATION).apply {
+ updateBarVisibility(isStashed)
updateTouchRegionOnAnimationEnd()
start()
}
@@ -504,6 +502,15 @@
return this
}
+ private fun <T : Animator> T.updateBarVisibility(stashed: Boolean): T {
+ if (stashed) {
+ doOnEnd { bubbleBarViewController.setHiddenForStashed(true) }
+ } else {
+ doOnStart { bubbleBarViewController.setHiddenForStashed(false) }
+ }
+ return this
+ }
+
private fun Animator.setBubbleBarPivotDuringAnim(pivotX: Float, pivotY: Float): Animator {
var initialPivotX = Float.NaN
var initialPivotY = Float.NaN
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarAllAppsButtonContainer.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarAllAppsButtonContainer.kt
index e6c0b2f..c5f8aa0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarAllAppsButtonContainer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarAllAppsButtonContainer.kt
@@ -37,7 +37,7 @@
import com.android.launcher3.views.ActivityContext
import com.android.launcher3.views.IconButtonView
import com.android.quickstep.DeviceConfigWrapper
-import com.android.quickstep.util.AssistStateManager
+import com.android.quickstep.util.ContextualSearchStateManager
/** Taskbar all apps button container for customizable taskbar. */
class TaskbarAllAppsButtonContainer
@@ -79,17 +79,18 @@
setOnClickListener(this::onAllAppsButtonClick)
setOnLongClickListener(this::onAllAppsButtonLongClick)
setOnTouchListener(this::onAllAppsButtonTouch)
- isHapticFeedbackEnabled = taskbarViewCallbacks.isAllAppsButtonHapticFeedbackEnabled()
+ isHapticFeedbackEnabled =
+ taskbarViewCallbacks.isAllAppsButtonHapticFeedbackEnabled(mContext)
allAppsTouchRunnable = Runnable {
taskbarViewCallbacks.triggerAllAppsButtonLongClick()
allAppsTouchTriggered = true
}
- val assistStateManager = AssistStateManager.INSTANCE[mContext]
+ val contextualSearchStateManager = ContextualSearchStateManager.INSTANCE[mContext]
if (
DeviceConfigWrapper.get().customLpaaThresholds &&
- assistStateManager.lpnhDurationMillis.isPresent
+ contextualSearchStateManager.lpnhDurationMillis.isPresent
) {
- allAppsButtonTouchDelayMs = assistStateManager.lpnhDurationMillis.get()
+ allAppsButtonTouchDelayMs = contextualSearchStateManager.lpnhDurationMillis.get()
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index bcad478..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
@@ -142,6 +148,12 @@
setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS,
toState.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()) ? 1f : 0f,
getOverviewInterpolator(fromState, toState));
+
+ if (enableLargeDesktopWindowingTile()) {
+ setter.setFloat(mRecentsView, DESKTOP_CAROUSEL_DETACH_PROGRESS,
+ toState.detachDesktopCarousel() ? 1f : 0f,
+ getOverviewInterpolator(fromState, toState));
+ }
}
private Interpolator getOverviewInterpolator(LauncherState fromState, LauncherState toState) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 9d9f096..acdff71 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -19,7 +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 android.window.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;
@@ -37,6 +37,7 @@
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
+import static com.android.launcher3.Utilities.isRtl;
import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
import static com.android.launcher3.config.FeatureFlags.enableSplitContextually;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
@@ -63,7 +64,9 @@
import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback;
import static com.android.quickstep.util.SplitAnimationTimings.TABLET_HOME_TO_SPLIT;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50;
+import static com.android.wm.shell.Flags.enableBubbleAnything;
+import static com.android.wm.shell.Flags.enableBubbleBarInPersistentTaskBar;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -196,8 +199,11 @@
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;
@@ -210,8 +216,6 @@
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 =
@@ -237,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
@@ -414,10 +419,8 @@
mDepthController.setActivityStarted(isStarted());
}
- if ((changeBits & ACTIVITY_STATE_RESUMED) != 0) {
- if (!FeatureFlags.enableHomeTransitionListener() && mTaskbarUIController != null) {
- mTaskbarUIController.onLauncherVisibilityChanged(hasBeenResumed());
- }
+ if ((changeBits & ACTIVITY_STATE_RESUMED) != 0 && mTaskbarUIController != null) {
+ mTaskbarUIController.onLauncherPausedOrResumed(isPaused());
}
super.onActivityFlagsChanged(changeBits);
@@ -461,7 +464,7 @@
if (Flags.enablePrivateSpace()) {
shortcuts.add(UNINSTALL_APP);
}
- if (com.android.wm.shell.Flags.enableBubbleAnything()) {
+ if (enableBubbleAnything()) {
shortcuts.add(BUBBLE_SHORTCUT);
}
return shortcuts.stream();
@@ -1092,6 +1095,25 @@
return mTaskbarUIController;
}
+ /** Provides the translation X for the hotseat item. */
+ public int getHotseatItemTranslationX(ItemInfo itemInfo) {
+ int translationX = 0;
+ if (isBubbleBarEnabled()
+ && enableBubbleBarInPersistentTaskBar()
+ && mBubbleBarLocation != null) {
+ boolean isRtl = isRtl(getResources());
+ boolean isBubblesOnLeft = mBubbleBarLocation.isOnLeft(isRtl);
+ translationX += mDeviceProfile
+ .getHotseatTranslationXForBubbleBar(isBubblesOnLeft, isRtl);
+ }
+ if (isBubbleBarEnabled()
+ && mDeviceProfile.shouldAdjustHotseatForBubbleBar(getContext(), hasBubbles())) {
+ translationX += (int) mDeviceProfile
+ .getHotseatAdjustedTranslation(getContext(), itemInfo.cellX);
+ }
+ return translationX;
+ }
+
public SplitToWorkspaceController getSplitToWorkspaceController() {
return mSplitToWorkspaceController;
}
@@ -1287,6 +1309,10 @@
mTISBindHelper.setPredictiveBackToHomeInProgress(isInProgress);
}
+ public boolean getPredictiveBackToHomeInProgress() {
+ return mIsPredictiveBackToHomeInProgress;
+ }
+
@Override
public boolean areDesktopTasksVisible() {
DesktopVisibilityController desktopVisibilityController = getDesktopVisibilityController();
@@ -1332,7 +1358,7 @@
/* callback= */ success -> mSplitSelectStateController.resetState(),
/* freezeTaskList= */ false,
groupTask.mSplitBounds == null
- ? SNAP_TO_50_50
+ ? SNAP_TO_2_50_50
: groupTask.mSplitBounds.snapPosition,
remoteTransition);
}
@@ -1397,6 +1423,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/SystemApiWrapper.kt b/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
index 0469636..f542b8c 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
@@ -23,6 +23,7 @@
import android.content.IIntentSender
import android.content.Intent
import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
import android.content.pm.LauncherActivityInfo
import android.content.pm.LauncherApps
import android.content.pm.ShortcutInfo
@@ -76,7 +77,7 @@
UserManager.USER_TYPE_PROFILE_PRIVATE -> UserIconInfo.TYPE_PRIVATE
else -> UserIconInfo.TYPE_MAIN
},
- userSerialNumber.toLong()
+ userSerialNumber.toLong(),
)
}
}
@@ -110,7 +111,7 @@
)
.toBundle()
requireActivityResult = false
- }
+ },
)
else super.getAppMarketActivityIntent(packageName, user)
@@ -131,7 +132,7 @@
)
.toBundle()
requireActivityResult = false
- }
+ },
)
else null
@@ -160,7 +161,7 @@
allowlistToken: IBinder?,
finishedReceiver: IIntentReceiver?,
requiredPermission: String?,
- options: Bundle?
+ options: Bundle?,
) {
if (code != -1) {
Executors.MAIN_EXECUTOR.execute {
@@ -168,9 +169,9 @@
context,
context.getString(
R.string.set_default_home_app,
- context.getString(R.string.derived_app_name)
+ context.getString(R.string.derived_app_name),
),
- Toast.LENGTH_LONG
+ Toast.LENGTH_LONG,
)
.show()
}
@@ -183,4 +184,7 @@
context.startActivity(ProxyActivityStarter.getLaunchIntent(context, params))
}
}
+
+ override fun getApplicationInfoHash(appInfo: ApplicationInfo): String =
+ (appInfo.sourceDir?.hashCode() ?: 0).toString() + " " + appInfo.longVersionCode
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DevOptionsUiHelper.kt b/quickstep/src/com/android/launcher3/uioverrides/flags/DevOptionsUiHelper.kt
index 181cba0..417bb74 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/DevOptionsUiHelper.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/DevOptionsUiHelper.kt
@@ -35,9 +35,6 @@
import android.provider.Settings.Secure
import android.text.Html
import android.util.AttributeSet
-import android.util.Base64
-import android.util.Base64.NO_PADDING
-import android.util.Base64.NO_WRAP
import android.view.inputmethod.EditorInfo
import android.widget.TextView
import android.widget.Toast
@@ -57,9 +54,10 @@
import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER
-import com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_KEY
import com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_LABEL
import com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_TAG
+import com.android.launcher3.LauncherSettings.Settings.LAYOUT_PROVIDER_KEY
+import com.android.launcher3.LauncherSettings.Settings.createBlobProviderKey
import com.android.launcher3.R
import com.android.launcher3.model.data.FolderInfo
import com.android.launcher3.model.data.ItemInfo
@@ -241,7 +239,7 @@
private fun DebugInfo<Boolean>.getBoolValue() =
DeviceConfigHelper.prefs.getBoolean(
this.key,
- DeviceConfig.getBoolean(NAMESPACE_LAUNCHER, this.key, this.valueInCode)
+ DeviceConfig.getBoolean(NAMESPACE_LAUNCHER, this.key, this.valueInCode),
)
private fun DebugInfo<Int>.getIntValueAsString() =
@@ -265,7 +263,7 @@
val pluginPermissionApps =
pm.getPackagesHoldingPermissions(
arrayOf(PLUGIN_PERMISSION),
- PackageManager.MATCH_DISABLED_COMPONENTS
+ PackageManager.MATCH_DISABLED_COMPONENTS,
)
.map { it.packageName }
@@ -274,7 +272,7 @@
pm.queryIntentServices(
Intent(action),
PackageManager.MATCH_DISABLED_COMPONENTS or
- PackageManager.GET_RESOLVED_FILTER
+ PackageManager.GET_RESOLVED_FILTER,
)
.filter { pluginPermissionApps.contains(it.serviceInfo.packageName) }
}
@@ -316,7 +314,7 @@
infoList.forEach {
manager.pluginEnabler.setDisabled(
it.serviceInfo.componentName,
- disabledState
+ disabledState,
)
}
manager.notifyChange(Intent(Intent.ACTION_PACKAGE_CHANGED, pluginUri))
@@ -387,12 +385,12 @@
addOnboardPref(
"All Apps Bounce",
HOME_BOUNCE_SEEN.sharedPrefKey,
- HOME_BOUNCE_COUNT.sharedPrefKey
+ HOME_BOUNCE_COUNT.sharedPrefKey,
)
addOnboardPref(
"Hybrid Hotseat Education",
HOTSEAT_DISCOVERY_TIP_COUNT.sharedPrefKey,
- HOTSEAT_LONGPRESS_TIP_SEEN.sharedPrefKey
+ HOTSEAT_LONGPRESS_TIP_SEEN.sharedPrefKey,
)
addOnboardPref("Taskbar Education", TASKBAR_EDU_TOOLTIP_STEP.sharedPrefKey)
addOnboardPref("Taskbar Search Education", TASKBAR_SEARCH_EDU_SEEN.sharedPrefKey)
@@ -470,13 +468,16 @@
session.allowPublicAccess()
session.commit(ORDERED_BG_EXECUTOR) {
- val key = Base64.encodeToString(digest, NO_WRAP or NO_PADDING)
- Secure.putString(resolver, LAYOUT_DIGEST_KEY, key)
+ Secure.putString(
+ resolver,
+ LAYOUT_PROVIDER_KEY,
+ createBlobProviderKey(digest),
+ )
MODEL_EXECUTOR.submit { model.modelDbController.createEmptyDB() }.get()
MAIN_EXECUTOR.submit { model.forceReload() }.get()
MODEL_EXECUTOR.submit {}.get()
- Secure.putString(resolver, LAYOUT_DIGEST_KEY, null)
+ Secure.putString(resolver, LAYOUT_PROVIDER_KEY, null)
}
}
}
@@ -512,7 +513,7 @@
info.providerName.className,
info.spanX,
info.spanY,
- userType
+ userType,
)
}
}
@@ -520,7 +521,7 @@
private fun createUriPickerIntent(
action: String,
executor: Executor,
- callback: (uri: Uri) -> Unit
+ callback: (uri: Uri) -> Unit,
): Intent {
val pendingIntent =
PendingIntent(
@@ -532,7 +533,7 @@
allowlistToken: IBinder?,
finishedReceiver: IIntentReceiver?,
requiredPermission: String?,
- options: Bundle?
+ options: Bundle?,
) {
intent.data?.let { uri -> executor.execute { callback(uri) } }
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index 18d717f..e87ac2f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -90,6 +90,11 @@
}
@Override
+ public boolean detachDesktopCarousel() {
+ return true;
+ }
+
+ @Override
protected float getDepthUnchecked(Context context) {
if (Launcher.getLauncher(context).areDesktopTasksVisible()) {
// Don't blur the background while desktop tasks are visible
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index b165cdd..c48ba4f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -166,6 +166,11 @@
}
@Override
+ public boolean detachDesktopCarousel() {
+ return false;
+ }
+
+ @Override
public boolean disallowTaskbarGlobalDrag() {
// Disable global drag in overview
return true;
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 864328f..fbb2c06 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -88,8 +88,8 @@
import android.view.WindowInsets;
import android.view.animation.Interpolator;
import android.widget.Toast;
+import android.window.DesktopModeFlags;
import android.window.PictureInPictureSurfaceTransaction;
-import android.window.flags.DesktopModeFlags;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -156,6 +156,8 @@
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;
@@ -165,8 +167,6 @@
import java.util.OptionalInt;
import java.util.function.Consumer;
-import kotlin.Unit;
-
/**
* Handles the navigation gestures when Launcher is the default home activity.
*/
@@ -237,6 +237,8 @@
getNextStateFlag("STATE_SCALED_CONTROLLER_HOME");
private static final int STATE_SCALED_CONTROLLER_RECENTS =
getNextStateFlag("STATE_SCALED_CONTROLLER_RECENTS");
+ private static final int STATE_PARALLEL_ANIM_FINISHED =
+ getNextStateFlag("STATE_PARALLEL_ANIM_FINISHED");
protected static final int STATE_HANDLER_INVALIDATED =
getNextStateFlag("STATE_HANDLER_INVALIDATED");
@@ -453,7 +455,8 @@
mStateCallback.runOnceAtState(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
| STATE_SCALED_CONTROLLER_HOME,
this::finishCurrentTransitionToHome);
- mStateCallback.runOnceAtState(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED,
+ mStateCallback.runOnceAtState(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED
+ | STATE_PARALLEL_ANIM_FINISHED,
this::reset);
mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
@@ -1149,12 +1152,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) {
@@ -1201,11 +1210,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;
}
@@ -1540,9 +1547,12 @@
@Override
public void onAnimationEnd(Animator animation) {
mParallelRunningAnim = null;
+ mStateCallback.setStateOnUiThread(STATE_PARALLEL_ANIM_FINISHED);
}
});
mParallelRunningAnim.start();
+ } else {
+ mStateCallback.setStateOnUiThread(STATE_PARALLEL_ANIM_FINISHED);
}
}
@@ -2274,18 +2284,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(
@@ -2294,7 +2301,7 @@
if (!hasTaskPreviouslyAppeared) {
ActiveGestureLog.INSTANCE.trackEvent(EXPECTING_TASK_APPEARED);
}
- ActiveGestureProtoLogProxy.logDynamicString(nextTaskLog.toString());
+ ActiveGestureProtoLogProxy.logStartNewTask(nextTaskLog);
nextTask.launchWithoutAnimation(true, success -> {
resultCallback.accept(success);
if (success) {
@@ -2375,8 +2382,8 @@
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
@@ -2390,10 +2397,10 @@
return;
}
ActiveGestureLog.CompoundString handleTaskFailureReason =
- new ActiveGestureLog.CompoundString("handleTaskAppeared check failed: ");
+ ActiveGestureLog.CompoundString.newEmptyString();
if (!handleTaskAppeared(appearedTaskTargets, handleTaskFailureReason)) {
forceFinishReason.append(handleTaskFailureReason);
- ActiveGestureProtoLogProxy.logDynamicString(forceFinishReason.toString());
+ ActiveGestureProtoLogProxy.logHandleTaskAppearedFailed(forceFinishReason);
finishRecentsAnimationOnTasksAppeared(onFinishComplete);
return;
}
@@ -2402,7 +2409,7 @@
.toArray(RemoteAnimationTarget[]::new);
if (taskTargets.length == 0) {
forceFinishReason.append("No appeared task matching started task id");
- ActiveGestureProtoLogProxy.logDynamicString(forceFinishReason.toString());
+ ActiveGestureProtoLogProxy.logHandleTaskAppearedFailed(forceFinishReason);
finishRecentsAnimationOnTasksAppeared(onFinishComplete);
return;
}
@@ -2412,13 +2419,13 @@
if (taskView == null || taskView.getTaskContainers().stream().noneMatch(
TaskContainer::getShouldShowSplashView)) {
forceFinishReason.append("Splash not needed");
- ActiveGestureProtoLogProxy.logDynamicString(forceFinishReason.toString());
+ ActiveGestureProtoLogProxy.logHandleTaskAppearedFailed(forceFinishReason);
finishRecentsAnimationOnTasksAppeared(onFinishComplete);
return;
}
if (mContainer == null) {
forceFinishReason.append("Activity destroyed");
- ActiveGestureProtoLogProxy.logDynamicString(forceFinishReason.toString());
+ ActiveGestureProtoLogProxy.logHandleTaskAppearedFailed(forceFinishReason);
finishRecentsAnimationOnTasksAppeared(onFinishComplete);
return;
}
diff --git a/quickstep/src/com/android/quickstep/BaseContainerInterface.java b/quickstep/src/com/android/quickstep/BaseContainerInterface.java
index 14c2cc4..143ef12 100644
--- a/quickstep/src/com/android/quickstep/BaseContainerInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseContainerInterface.java
@@ -46,7 +46,6 @@
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;
@@ -379,9 +378,6 @@
public static void getTaskDimension(Context context, DeviceProfile dp, PointF out) {
out.x = dp.widthPx;
out.y = dp.heightPx;
- if (dp.isTablet && !DisplayController.isTransientTaskbar(context)) {
- out.y -= dp.taskbarHeight;
- }
}
/**
diff --git a/quickstep/src/com/android/quickstep/ExternalDisplaySystemShortcut.kt b/quickstep/src/com/android/quickstep/ExternalDisplaySystemShortcut.kt
new file mode 100644
index 0000000..46c4f36
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/ExternalDisplaySystemShortcut.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.view.View
+import com.android.launcher3.AbstractFloatingViewHelper
+import com.android.launcher3.R
+import com.android.launcher3.logging.StatsLogManager.LauncherEvent
+import com.android.launcher3.popup.SystemShortcut
+import com.android.quickstep.views.RecentsView
+import com.android.quickstep.views.RecentsViewContainer
+import com.android.quickstep.views.TaskContainer
+import com.android.window.flags.Flags
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+
+/** A menu item that allows the user to move the current app into external display. */
+class ExternalDisplaySystemShortcut(
+ container: RecentsViewContainer,
+ abstractFloatingViewHelper: AbstractFloatingViewHelper,
+ private val taskContainer: TaskContainer,
+) :
+ SystemShortcut<RecentsViewContainer>(
+ R.drawable.ic_external_display,
+ R.string.recent_task_option_external_display,
+ container,
+ taskContainer.itemInfo,
+ taskContainer.taskView,
+ abstractFloatingViewHelper,
+ ) {
+ override fun onClick(view: View) {
+ dismissTaskMenuView()
+ val recentsView = mTarget.getOverviewPanel<RecentsView<*, *>>()
+ recentsView.moveTaskToExternalDisplay(taskContainer) {
+ mTarget.statsLogManager
+ .logger()
+ .withItemInfo(taskContainer.itemInfo)
+ .log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_EXTERNAL_DISPLAY_TAP)
+ }
+ }
+
+ companion object {
+ @JvmOverloads
+ /**
+ * Creates a factory for creating move task to external display system shortcuts in
+ * [com.android.quickstep.TaskOverlayFactory].
+ */
+ fun createFactory(
+ abstractFloatingViewHelper: AbstractFloatingViewHelper = AbstractFloatingViewHelper()
+ ): TaskShortcutFactory =
+ object : TaskShortcutFactory {
+ override fun getShortcuts(
+ container: RecentsViewContainer,
+ taskContainer: TaskContainer,
+ ): List<ExternalDisplaySystemShortcut>? {
+ return if (
+ DesktopModeStatus.canEnterDesktopMode(container.asContext()) &&
+ Flags.moveToExternalDisplayShortcut()
+ )
+ listOf(
+ ExternalDisplaySystemShortcut(
+ container,
+ abstractFloatingViewHelper,
+ taskContainer,
+ )
+ )
+ else null
+ }
+
+ override fun showForGroupedTask() = true
+ }
+ }
+}
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 2892d2c..015a449 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -36,6 +36,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.launcher3.Flags;
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StatefulContainer;
import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
@@ -302,6 +303,16 @@
}
/**
+ * Requests that handling for this gesture should use a synthetic transition, as in that it
+ * will need to start a recents transition that is not backed by a system transition. This is
+ * generally only needed in scenarios where a system transition can not be created due to no
+ * changes in the WM hierarchy (ie. starting recents transition when you are already over home).
+ */
+ public boolean useSyntheticRecentsTransition() {
+ return mRunningTask.isHomeTask() && Flags.enableFallbackOverviewInWindow();
+ }
+
+ /**
* @return the running task for this gesture.
*/
@Nullable
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
index a33e5c0..e23947b 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
@@ -27,6 +27,7 @@
import androidx.annotation.UiThread
import androidx.annotation.VisibleForTesting
import com.android.internal.jank.Cuj
+import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
import com.android.launcher3.Flags.enableOverviewCommandHelperTimeout
import com.android.launcher3.PagedView
import com.android.launcher3.logger.LauncherAtom
@@ -52,6 +53,7 @@
import com.android.systemui.shared.system.InteractionJankMonitorWrapper
import java.io.PrintWriter
import java.util.concurrent.ConcurrentLinkedDeque
+import java.util.concurrent.Executor
import kotlin.coroutines.resume
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
@@ -68,6 +70,7 @@
private val overviewComponentObserver: OverviewComponentObserver,
private val taskAnimationManager: TaskAnimationManager,
private val dispatcherProvider: DispatcherProvider = ProductionDispatchers,
+ private val uiExecutor: Executor = Executors.MAIN_EXECUTOR,
) {
private val coroutineScope = CoroutineScope(SupervisorJob() + dispatcherProvider.default)
@@ -84,7 +87,7 @@
get() = overviewComponentObserver.containerInterface
private val visibleRecentsView: RecentsView<*, *>?
- get() = containerInterface.getVisibleRecentsView<RecentsView<*, *>>()
+ get() = containerInterface.getVisibleRecentsView()
/**
* Adds a command to be executed next, after all pending tasks are completed. Max commands that
@@ -104,11 +107,7 @@
if (commandQueue.size == 1) {
Log.d(TAG, "execute: $command - queue size: ${commandQueue.size}")
- if (enableOverviewCommandHelperTimeout()) {
- coroutineScope.launch(dispatcherProvider.main) { processNextCommand() }
- } else {
- Executors.MAIN_EXECUTOR.execute { processNextCommand() }
- }
+ uiExecutor.execute { processNextCommand() }
} else {
Log.d(TAG, "not executed: $command - queue size: ${commandQueue.size}")
}
@@ -215,13 +214,12 @@
}
}
TOGGLE -> {
- val taskView =
- if (recentsView.runningTaskView == null) {
- recentsView.getTaskViewAt(0)
- } else {
- recentsView.nextTaskView ?: recentsView.runningTaskView
- }
- launchTask(recentsView, taskView, command, onCallbackResult)
+ launchTask(
+ recentsView,
+ getNextToggledTaskView(recentsView),
+ command,
+ onCallbackResult,
+ )
}
HOME -> {
recentsView.startHome()
@@ -229,6 +227,27 @@
}
}
+ private fun getNextToggledTaskView(recentsView: RecentsView<*, *>): TaskView? {
+ // When running task view is null we return last large taskView - typically focusView when
+ // grid only is not enabled else last desktop task view.
+ return if (recentsView.runningTaskView == null) {
+ recentsView.lastLargeTaskView ?: recentsView.getTaskViewAt(0)
+ } else {
+ if (
+ enableLargeDesktopWindowingTile() &&
+ recentsView.getTaskViewCount() == recentsView.largeTilesCount &&
+ recentsView.runningTaskView === recentsView.lastLargeTaskView
+ ) {
+ // Enables the toggle when only large tiles are in recents view.
+ // We return previous because unlike small tiles, large tiles are always
+ // on the right hand side.
+ recentsView.previousTaskView ?: recentsView.runningTaskView
+ } else {
+ recentsView.nextTaskView ?: recentsView.runningTaskView
+ }
+ }
+ }
+
private fun launchTask(
recents: RecentsView<*, *>,
taskView: TaskView?,
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index fc11812..7d5bd37 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -15,7 +15,9 @@
*/
package com.android.quickstep;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -29,6 +31,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.UiThread;
+import com.android.launcher3.Flags;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.Preconditions;
import com.android.quickstep.util.ActiveGestureProtoLogProxy;
@@ -102,7 +105,11 @@
long appCount = Arrays.stream(appTargets)
.filter(app -> app.mode == MODE_CLOSING)
.count();
- if (appCount == 0) {
+
+ boolean isOpeningHome = Arrays.stream(appTargets).filter(app -> app.mode == MODE_OPENING
+ && app.windowConfiguration.getActivityType() == ACTIVITY_TYPE_HOME)
+ .count() > 0;
+ if (appCount == 0 && (!Flags.enableFallbackOverviewInWindow() || isOpeningHome)) {
ActiveGestureProtoLogProxy.logOnRecentsAnimationStartCancelled();
// Edge case, if there are no closing app targets, then Launcher has nothing to handle
notifyAnimationCanceled();
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 5131774..de8be50 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -19,6 +19,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.launcher3.MotionEventsUtils.isTrackpadScroll;
import static com.android.launcher3.util.DisplayController.CHANGE_ALL;
import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
import static com.android.launcher3.util.DisplayController.CHANGE_ROTATION;
@@ -70,7 +71,7 @@
import com.android.launcher3.util.SettingsCache;
import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
import com.android.quickstep.util.ActiveGestureLog;
-import com.android.quickstep.util.AssistStateManager;
+import com.android.quickstep.util.ContextualSearchStateManager;
import com.android.quickstep.util.GestureExclusionManager;
import com.android.quickstep.util.GestureExclusionManager.ExclusionListener;
import com.android.quickstep.util.NavBarPosition;
@@ -101,7 +102,7 @@
private final DisplayController mDisplayController;
private final GestureExclusionManager mExclusionManager;
- private final AssistStateManager mAssistStateManager;
+ private final ContextualSearchStateManager mContextualSearchStateManager;
private final RotationTouchHelper mRotationTouchHelper;
private final TaskStackChangeListener mPipListener;
@@ -152,7 +153,7 @@
mContext = context;
mDisplayController = DisplayController.INSTANCE.get(context);
mExclusionManager = exclusionManager;
- mAssistStateManager = AssistStateManager.INSTANCE.get(context);
+ mContextualSearchStateManager = ContextualSearchStateManager.INSTANCE.get(context);
mIsOneHandedModeSupported = SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false);
mRotationTouchHelper = RotationTouchHelper.INSTANCE.get(context);
if (isInstanceForTouches) {
@@ -563,6 +564,7 @@
return mAssistantAvailable
&& !QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags)
&& mRotationTouchHelper.touchInAssistantRegion(ev)
+ && !isTrackpadScroll(ev)
&& !isLockToAppActive();
}
@@ -617,8 +619,9 @@
: QUICKSTEP_TOUCH_SLOP_RATIO_TWO_BUTTON;
float touchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
- if (mAssistStateManager.getLPNHCustomSlopMultiplier().isPresent()) {
- float customSlopMultiplier = mAssistStateManager.getLPNHCustomSlopMultiplier().get();
+ if (mContextualSearchStateManager.getLPNHCustomSlopMultiplier().isPresent()) {
+ float customSlopMultiplier =
+ mContextualSearchStateManager.getLPNHCustomSlopMultiplier().get();
return customSlopMultiplier * slopMultiplier * touchSlop;
} else {
return slopMultiplier * touchSlop;
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 34435d5..14b4d60 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -46,11 +46,11 @@
import android.view.MotionEvent;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
+import android.window.DesktopModeFlags;
import android.window.IOnBackInvokedCallback;
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;
@@ -64,7 +64,7 @@
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.SafeCloseable;
import com.android.quickstep.util.ActiveGestureProtoLogProxy;
-import com.android.quickstep.util.AssistUtils;
+import com.android.quickstep.util.ContextualSearchInvoker;
import com.android.quickstep.util.unfold.ProxyUnfoldTransitionProvider;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -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;
@@ -300,6 +301,7 @@
registerSplitScreenListener(mSplitScreenListener);
registerSplitSelectListener(mSplitSelectListener);
mHomeVisibilityState.init(mShellTransitions);
+ mFocusState.init(mShellTransitions);
setStartingWindowListener(mStartingWindowListener);
setLauncherUnlockAnimationController(
mLauncherActivityClass, mLauncherUnlockAnimationController);
@@ -309,8 +311,8 @@
setBackToLauncherCallback(mBackToLauncherCallback, mBackToLauncherRunner);
setUnfoldAnimationListener(mUnfoldAnimationListener);
setDesktopTaskListener(mDesktopTaskListener);
- setAssistantOverridesRequested(
- AssistUtils.newInstance(mContext).getSysUiAssistOverrideInvocationTypes());
+ setAssistantOverridesRequested(ContextualSearchInvoker.newInstance(mContext)
+ .getSysUiAssistOverrideInvocationTypes());
mStateChangeCallbacks.forEach(Runnable::run);
if (mUnfoldTransitionProvider != null) {
@@ -1077,16 +1079,6 @@
}
}
- public void removeFromSideStage(int taskId) {
- if (mSplitScreen != null) {
- try {
- mSplitScreen.removeFromSideStage(taskId);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call removeFromSideStage");
- }
- }
- }
-
//
// One handed
//
@@ -1144,6 +1136,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
@@ -1486,6 +1482,28 @@
}
}
+ /** Call shell to remove the desktop that is on given `displayId` */
+ public void removeDesktop(int displayId) {
+ if (mDesktopMode != null) {
+ try {
+ mDesktopMode.removeDesktop(displayId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call removeDesktop", e);
+ }
+ }
+ }
+
+ /** Call shell to move a task with given `taskId` to external display. */
+ public void moveToExternalDisplay(int taskId) {
+ if (mDesktopMode != null) {
+ try {
+ mDesktopMode.moveToExternalDisplay(taskId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call moveToExternalDisplay", e);
+ }
+ }
+ }
+
//
// Unfold transition
//
@@ -1517,7 +1535,7 @@
* Starts the recents activity. The caller should manage the thread on which this is called.
*/
public boolean startRecentsActivity(Intent intent, ActivityOptions options,
- RecentsAnimationListener listener) {
+ RecentsAnimationListener listener, boolean useSyntheticRecentsTransition) {
if (mRecentTasks == null) {
ActiveGestureProtoLogProxy.logRecentTasksMissing();
return false;
@@ -1548,6 +1566,9 @@
}
};
final Bundle optsBundle = options.toBundle();
+ if (useSyntheticRecentsTransition) {
+ optsBundle.putBoolean("is_synthetic_recents_transition", true);
+ }
try {
mRecentTasks.startRecentsTransition(mRecentsPendingIntent, intent, optsBundle,
mContext.getIApplicationThread(), runner);
@@ -1592,6 +1613,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 98d7628..56c978a 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -296,9 +296,9 @@
// 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();
+ mRecentsAnimationStartPending = getSystemUiProxy().startRecentsActivity(intent, options,
+ mCallbacks, gestureState.useSyntheticRecentsTransition());
+ mRecentsWindowsManager.startRecentsWindow(mCallbacks);
} else {
options.setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
@@ -326,9 +326,10 @@
});
}
- mRecentsAnimationStartPending = getSystemUiProxy()
- .startRecentsActivity(intent, options, mCallbacks);
+ mRecentsAnimationStartPending = getSystemUiProxy().startRecentsActivity(intent,
+ options, mCallbacks, false /* useSyntheticRecentsTransition */);
}
+
if (enableHandleDelayedGestureCallbacks()) {
ActiveGestureProtoLogProxy.logSettingRecentsAnimationStartPending(
mRecentsAnimationStartPending);
@@ -485,10 +486,6 @@
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 8e45767..0dbdcb7 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -116,6 +116,7 @@
TaskShortcutFactory.INSTALL,
TaskShortcutFactory.FREE_FORM,
DesktopSystemShortcut.Companion.createFactory(),
+ ExternalDisplaySystemShortcut.Companion.createFactory(),
TaskShortcutFactory.WELLBEING,
TaskShortcutFactory.SAVE_APP_PAIR,
TaskShortcutFactory.SCREENSHOT,
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 18b7678..41a8a31 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -122,8 +122,8 @@
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.util.ContextualSearchInvoker;
+import com.android.quickstep.util.ContextualSearchStateManager;
import com.android.quickstep.views.RecentsViewContainer;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
@@ -297,7 +297,8 @@
@Override
public void onAssistantOverrideInvoked(int invocationType) {
executeForTouchInteractionService(tis -> {
- if (!AssistUtils.newInstance(tis).tryStartAssistOverride(invocationType)) {
+ if (!ContextualSearchInvoker.newInstance(tis)
+ .tryStartAssistOverride(invocationType)) {
Log.w(TAG, "Failed to invoke Assist override");
}
});
@@ -911,7 +912,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 +930,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 +958,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,7 +987,7 @@
if (mUncheckedConsumer != InputConsumer.NO_OP) {
switch (action) {
case ACTION_DOWN:
- ActiveGestureProtoLogProxy.logDynamicString(reasonString.toString());
+ ActiveGestureProtoLogProxy.logOnInputEventActionDown(reasonString);
// fall through
case ACTION_UP:
ActiveGestureProtoLogProxy.logOnInputEventActionUp(
@@ -1059,11 +1060,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);
}
@@ -1132,12 +1133,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;
@@ -1149,13 +1149,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()) {
@@ -1165,11 +1164,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);
}
@@ -1180,11 +1179,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);
}
@@ -1193,9 +1192,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);
}
@@ -1206,10 +1205,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, ");
}
@@ -1221,74 +1220,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);
}
@@ -1298,7 +1297,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) {
@@ -1325,14 +1324,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.
@@ -1373,11 +1370,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) {
@@ -1386,21 +1384,22 @@
gestureState,
event,
forceOverviewInputConsumer,
- reasonString.append(SUBSTRING_PREFIX)
- .append(previousGestureAnimatedToLauncher
- ? "previous gesture animated to launcher"
+ reasonString.append(previousGestureAnimatedToLauncher
+ ? "%sprevious gesture animated to launcher, "
+ + "trying to use overview input consumer"
: (launcherResumedThroughShellTransition
- ? "launcher resumed through a shell transition"
- : "forceOverviewInputConsumer == true"))
- .append(", trying to use overview input consumer"));
+ ? "%slauncher resumed through a shell transition, "
+ + "trying to use overview input consumer"
+ : "%sforceOverviewInputConsumer == true, "
+ + "trying to use overview input consumer"),
+ SUBSTRING_PREFIX));
} else if (mDeviceState.isGestureBlockedTask(runningTask) || launcherChildActivityResumed) {
- return getDefaultInputConsumer(reasonString.append(SUBSTRING_PREFIX)
- .append(launcherChildActivityResumed
- ? "is launcher child-task, trying to use default input consumer"
- : "is gesture-blocked task, trying to use default input consumer"));
+ return getDefaultInputConsumer(reasonString.append(launcherChildActivityResumed
+ ? "%sis launcher child-task, trying to use default input consumer"
+ : "%sis gesture-blocked task, trying to use default input consumer",
+ SUBSTRING_PREFIX));
} else {
- reasonString.append(SUBSTRING_PREFIX)
- .append("using OtherActivityInputConsumer");
+ reasonString.append("%susing OtherActivityInputConsumer", SUBSTRING_PREFIX);
return createOtherActivityInputConsumer(gestureState, event);
}
}
@@ -1427,20 +1426,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));
}
}
@@ -1452,9 +1449,8 @@
CompoundString reasonString) {
RecentsViewContainer container = gestureState.getContainerInterface().getCreatedContainer();
if (container == null) {
- return getDefaultInputConsumer(
- reasonString.append(SUBSTRING_PREFIX)
- .append("activity == null, trying to use default input consumer"));
+ return getDefaultInputConsumer(reasonString.append(
+ "%sactivity == null, trying to use default input consumer", SUBSTRING_PREFIX));
}
View rootview = container.getRootView();
@@ -1464,24 +1460,24 @@
|| mDeviceState.isPredictiveBackToHomeInProgress();
boolean isInLiveTileMode = gestureState.getContainerInterface().isInLiveTileMode();
- reasonString.append(SUBSTRING_PREFIX)
- .append(hasWindowFocus
- ? "activity has window focus"
- : (isPreviousGestureAnimatingToLauncher
- ? "previous gesture is still animating to launcher"
- : isInLiveTileMode
- ? "device is in live mode"
- : "all overview focus conditions failed"));
+ reasonString.append(hasWindowFocus
+ ? "%sactivity has window focus"
+ : (isPreviousGestureAnimatingToLauncher
+ ? "%sprevious gesture is still animating to launcher"
+ : isInLiveTileMode
+ ? "%sdevice is in live mode"
+ : "%sall overview focus conditions failed"), SUBSTRING_PREFIX);
if (hasWindowFocus
|| isPreviousGestureAnimatingToLauncher
|| isInLiveTileMode) {
- reasonString.append(SUBSTRING_PREFIX)
- .append("overview should have focus, using OverviewInputConsumer");
+ reasonString.append(
+ "%soverview should have focus, using OverviewInputConsumer", SUBSTRING_PREFIX);
return new OverviewInputConsumer(gestureState, container, mInputMonitorCompat,
false /* startingInActivityBounds */);
} else {
- reasonString.append(SUBSTRING_PREFIX).append(
- "overview shouldn't have focus, using OverviewWithoutFocusInputConsumer");
+ reasonString.append(
+ "%soverview shouldn't have focus, using OverviewWithoutFocusInputConsumer",
+ SUBSTRING_PREFIX);
final boolean disableHorizontalSwipe = mDeviceState.isInExclusionRegion(event);
return new OverviewWithoutFocusInputConsumer(container.asContext(), mDeviceState,
gestureState, mInputMonitorCompat, disableHorizontalSwipe);
@@ -1518,12 +1514,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;
@@ -1643,8 +1641,8 @@
}
mTaskbarManager.dumpLogs("", pw);
mDesktopVisibilityController.dumpLogs("", pw);
- pw.println("AssistStateManager:");
- AssistStateManager.INSTANCE.get(this).dump("\t", pw);
+ pw.println("ContextualSearchStateManager:");
+ ContextualSearchStateManager.INSTANCE.get(this).dump("\t", pw);
SystemUiProxy.INSTANCE.get(this).dump(pw);
DeviceConfigWrapper.get().dump(" ", pw);
}
diff --git a/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java b/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
index f2d5715..335161b 100644
--- a/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
+++ b/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
@@ -18,7 +18,10 @@
import com.android.launcher3.dagger.LauncherAppComponent;
import com.android.launcher3.dagger.LauncherBaseAppComponent;
+import com.android.launcher3.model.WellbeingModel;
import com.android.quickstep.logging.SettingsChangeLogger;
+import com.android.quickstep.util.AsyncClockEventDelegate;
+import com.android.quickstep.util.ContextualSearchHapticManager;
/**
* Launcher Quickstep base component for Dagger injection.
@@ -30,4 +33,10 @@
*/
public interface QuickstepBaseAppComponent extends LauncherBaseAppComponent {
SettingsChangeLogger getSettingsChangeLogger();
+
+ WellbeingModel getWellbeingModel();
+
+ AsyncClockEventDelegate getAsyncClockEventDelegate();
+
+ ContextualSearchHapticManager getContextualSearchHapticManager();
}
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index 83794fb..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;
@@ -116,6 +118,11 @@
getOverviewInterpolator(state));
setter.setFloat(mRecentsView, TASK_THUMBNAIL_SPLASH_ALPHA,
state.showTaskThumbnailSplash() ? 1f : 0f, getOverviewInterpolator(state));
+ if (enableLargeDesktopWindowingTile()) {
+ setter.setFloat(mRecentsView, DESKTOP_CAROUSEL_DETACH_PROGRESS,
+ state.detachDesktopCarousel() ? 1f : 0f,
+ getOverviewInterpolator(state));
+ }
setter.setViewBackgroundColor(mRecentsViewContainer.getScrimView(),
state.getScrimColor(mRecentsViewContainer.asContext()),
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 7f88090..5a4c769 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -323,7 +323,7 @@
}
@Override
- protected boolean canLaunchFullscreenTask() {
+ public boolean canLaunchFullscreenTask() {
return !mContainer.isInState(OVERVIEW_SPLIT_SELECT);
}
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsState.java b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
index ca9753f..082b96c 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsState.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
@@ -42,6 +42,7 @@
private static final int FLAG_LIVE_TILE = BaseState.getFlag(6);
private static final int FLAG_RECENTS_VIEW_VISIBLE = BaseState.getFlag(7);
private static final int FLAG_TASK_THUMBNAIL_SPLASH = BaseState.getFlag(8);
+ private static final int FLAG_DETACH_DESKTOP_CAROUSEL = BaseState.getFlag(9);
public static final RecentsState DEFAULT = new RecentsState(0,
FLAG_DISABLE_RESTORE | FLAG_CLEAR_ALL_BUTTON | FLAG_OVERVIEW_ACTIONS | FLAG_SHOW_AS_GRID
@@ -51,8 +52,8 @@
| FLAG_SHOW_AS_GRID | FLAG_SCRIM | FLAG_LIVE_TILE | FLAG_RECENTS_VIEW_VISIBLE);
public static final RecentsState BACKGROUND_APP = new BackgroundAppState(2,
FLAG_DISABLE_RESTORE | FLAG_NON_INTERACTIVE | FLAG_FULL_SCREEN
- | FLAG_RECENTS_VIEW_VISIBLE
- | FLAG_TASK_THUMBNAIL_SPLASH);
+ | FLAG_RECENTS_VIEW_VISIBLE | FLAG_TASK_THUMBNAIL_SPLASH
+ | FLAG_DETACH_DESKTOP_CAROUSEL);
public static final RecentsState HOME = new RecentsState(3, 0);
public static final RecentsState BG_LAUNCHER = new LauncherState(4, 0);
public static final RecentsState OVERVIEW_SPLIT_SELECT = new RecentsState(5,
@@ -149,6 +150,11 @@
return hasFlag(FLAG_TASK_THUMBNAIL_SPLASH);
}
+ @Override
+ public boolean detachDesktopCarousel() {
+ return hasFlag(FLAG_DETACH_DESKTOP_CAROUSEL);
+ }
+
/**
* True if the state has overview panel visible.
*/
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
index 8ce61f5..fbf671f 100644
--- a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
@@ -18,11 +18,9 @@
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
@@ -30,7 +28,6 @@
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
@@ -49,6 +46,9 @@
import com.android.launcher3.views.ScrimView
import com.android.quickstep.FallbackWindowInterface
import com.android.quickstep.OverviewComponentObserver
+import com.android.quickstep.RecentsAnimationCallbacks
+import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener
+import com.android.quickstep.RecentsAnimationController
import com.android.quickstep.RecentsModel
import com.android.quickstep.RemoteAnimationTargets
import com.android.quickstep.SystemUiProxy
@@ -63,17 +63,25 @@
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.RecentsWindowProtoLogProxy
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 com.android.systemui.shared.recents.model.ThumbnailData
+import com.android.systemui.shared.system.TaskStackChangeListener
+import com.android.systemui.shared.system.TaskStackChangeListeners
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 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
+ *
+ * To add new protologs, see [RecentsWindowProtoLogProxy]. To enable logging to logcat, see
+ * [QuickstepProtoLogGroup.Constants.DEBUG_RECENTS_WINDOW]
*/
class RecentsWindowManager(context: Context) :
RecentsWindowContext(context), RecentsViewContainer, StatefulContainer<RecentsState> {
@@ -81,12 +89,12 @@
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 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)
@@ -97,22 +105,48 @@
private var actionsView: OverviewActionsView<*>? = null
private var scrimView: ScrimView? = null
- private var isShown = false
+ private var callbacks: RecentsAnimationCallbacks? = null
private var tisBindHelper: TISBindHelper = TISBindHelper(this) {}
// Callback array that corresponds to events defined in @ActivityEvent
private val mEventCallbacks =
- arrayOf(RunnableList(), RunnableList(), RunnableList(), RunnableList())
+ listOf(RunnableList(), RunnableList(), RunnableList(), RunnableList())
private var onInitListener: Predicate<Boolean>? = null
+ private val taskStackChangeListener =
+ object : TaskStackChangeListener {
+ override fun onTaskMovedToFront(taskId: Int) {
+ if ((isShowing() && isInState(DEFAULT))) {
+ // handling state where we end recents animation by swiping livetile away
+ // TODO: animate this switch.
+ cleanupRecentsWindow()
+ }
+ }
+ }
+
+ private val recentsAnimationListener =
+ object : RecentsAnimationListener {
+ override fun onRecentsAnimationCanceled(thumbnailDatas: HashMap<Int, ThumbnailData>) {
+ recentAnimationStopped()
+ }
+
+ override fun onRecentsAnimationFinished(controller: RecentsAnimationController) {
+ recentAnimationStopped()
+ }
+ }
+
init {
FallbackWindowInterface.init(this)
+ TaskStackChangeListeners.getInstance().registerTaskStackListener(taskStackChangeListener)
}
override fun destroy() {
super.destroy()
+ cleanupRecentsWindow()
FallbackWindowInterface.getInstance()?.destroy()
+ TaskStackChangeListeners.getInstance().unregisterTaskStackListener(taskStackChangeListener)
+ callbacks?.removeListener(recentsAnimationListener)
}
override fun startHome() {
@@ -134,15 +168,16 @@
),
)
OverviewComponentObserver.startHomeIntentSafely(this, options.toBundle(), TAG)
+ stateManager.moveToRestState()
}
private val mAnimationToHomeFactory =
RemoteAnimationFactory {
- _: Int,
- appTargets: Array<RemoteAnimationTarget>?,
- wallpaperTargets: Array<RemoteAnimationTarget>?,
- nonAppTargets: Array<RemoteAnimationTarget>?,
- result: LauncherAnimationRunner.AnimationResult? ->
+ _: Int,
+ appTargets: Array<RemoteAnimationTarget>?,
+ wallpaperTargets: Array<RemoteAnimationTarget>?,
+ nonAppTargets: Array<RemoteAnimationTarget>?,
+ result: LauncherAnimationRunner.AnimationResult? ->
val controller =
getStateManager().createAnimationToNewWorkspace(BG_LAUNCHER, HOME_APPEAR_DURATION)
controller.dispatchOnStart()
@@ -163,27 +198,35 @@
anim,
this@RecentsWindowManager,
{
- getStateManager().goToState(HOME, false)
- cleanup()
+ getStateManager().goToState(BG_LAUNCHER, false)
+ cleanupRecentsWindow()
},
true, /* skipFirstFrame */
)
}
- fun cleanup() {
- if (isShown) {
+ private fun cleanupRecentsWindow() {
+ RecentsWindowProtoLogProxy.logCleanup(isShowing())
+ if (isShowing()) {
windowManager.removeViewImmediate(windowView)
- isShown = false
}
+ stateManager.moveToRestState()
+ callbacks?.removeListener(recentsAnimationListener)
}
- fun startRecentsWindow() {
- if (isShown) return
+ private fun isShowing(): Boolean {
+ return windowView?.parent != null
+ }
+
+ fun startRecentsWindow(callbacks: RecentsAnimationCallbacks? = null) {
+ RecentsWindowProtoLogProxy.logStartRecentsWindow(isShowing(), windowView == null)
+ if (isShowing()) {
+ 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
@@ -212,6 +255,15 @@
mSystemUiController = SystemUiController(windowView)
onInitListener?.test(true)
+
+ this.callbacks = callbacks
+ callbacks?.addListener(recentsAnimationListener)
+ }
+
+ private fun recentAnimationStopped() {
+ if (isInState(BACKGROUND_APP)) {
+ cleanupRecentsWindow()
+ }
}
override fun canStartHomeSafely(): Boolean {
@@ -243,39 +295,30 @@
return stateManager.state == state
}
- override fun onStateSetStart(state: RecentsState?) {
+ override fun onStateSetStart(state: RecentsState) {
super.onStateSetStart(state)
- logState(state, "state started:")
+ RecentsWindowProtoLogProxy.logOnStateSetStart(getStateName(state))
}
- override fun onStateSetEnd(state: RecentsState?) {
+ override fun onStateSetEnd(state: RecentsState) {
super.onStateSetEnd(state)
- logState(state, "state ended:")
+ RecentsWindowProtoLogProxy.logOnStateSetEnd(getStateName(state))
+
+ if (state == HOME || state == BG_LAUNCHER) {
+ cleanupRecentsWindow()
+ }
}
- 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")
- }
- }
+ private fun getStateName(state: RecentsState?): String {
+ return when (state) {
+ null -> "NULL"
+ DEFAULT -> "default"
+ MODAL_TASK -> "MODAL_TASK"
+ BACKGROUND_APP -> "BACKGROUND_APP"
+ HOME -> "HOME"
+ BG_LAUNCHER -> "BG_LAUNCHER"
+ OVERVIEW_SPLIT_SELECT -> "OVERVIEW_SPLIT_SELECT"
+ else -> "ordinal=" + state.ordinal
}
}
@@ -329,7 +372,7 @@
}
override fun isStarted(): Boolean {
- return isShown
+ return isShowing() && isInState(DEFAULT)
}
/** Adds a callback for the provided activity event */
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandler.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandler.java
index 1d00e53..1a825a4 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandler.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandler.java
@@ -16,25 +16,67 @@
package com.android.quickstep.inputconsumers;
+import static android.app.contextualsearch.ContextualSearchManager.ENTRYPOINT_LONG_PRESS_NAV_HANDLE;
+
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_ASSISTANT_SUCCESSFUL_NAV_HANDLE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OMNI_GET_LONG_PRESS_RUNNABLE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_OMNI_RUNNABLE;
+
import android.content.Context;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.ViewConfiguration;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import com.android.launcher3.LauncherApplication;
import com.android.launcher3.R;
+import com.android.launcher3.logging.InstanceId;
+import com.android.launcher3.logging.InstanceIdSequence;
+import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.util.ResourceBasedOverride;
+import com.android.launcher3.util.VibratorWrapper;
+import com.android.quickstep.DeviceConfigWrapper;
import com.android.quickstep.NavHandle;
+import com.android.quickstep.TopTaskTracker;
+import com.android.quickstep.util.ContextualSearchHapticManager;
+import com.android.quickstep.util.ContextualSearchInvoker;
+import com.android.quickstep.util.ContextualSearchStateManager;
/**
* Class for extending nav handle long press behavior
*/
public class NavHandleLongPressHandler implements ResourceBasedOverride {
+ private static final String TAG = "NavHandleLongPressHandler";
+
+ protected final Context mContext;
+ protected final VibratorWrapper mVibratorWrapper;
+ protected final ContextualSearchHapticManager mContextualSearchHapticManager;
+ protected final ContextualSearchInvoker mContextualSearchInvoker;
+ protected final StatsLogManager mStatsLogManager;
+ private boolean mPendingInvocation;
+
+ public NavHandleLongPressHandler(Context context) {
+ mContext = context;
+ mStatsLogManager = StatsLogManager.newInstance(context);
+ mVibratorWrapper = VibratorWrapper.INSTANCE.get(mContext);
+ mContextualSearchHapticManager = ((LauncherApplication) context.getApplicationContext())
+ .getAppComponent().getContextualSearchHapticManager();
+ mContextualSearchInvoker = ContextualSearchInvoker.newInstance(mContext);
+ }
+
/** Creates NavHandleLongPressHandler as specified by overrides */
public static NavHandleLongPressHandler newInstance(Context context) {
return Overrides.getObject(NavHandleLongPressHandler.class, context,
R.string.nav_handle_long_press_handler_class);
}
+ protected boolean isContextualSearchEntrypointEnabled(NavHandle navHandle) {
+ return DeviceConfigWrapper.get().getEnableLongPressNavHandle();
+ }
+
/**
* Called when nav handle is long pressed to get the Runnable that should be executed by the
* caller to invoke long press behavior. If null is returned that means long press couldn't be
@@ -46,8 +88,48 @@
*
* @param navHandle to handle this long press
*/
- public @Nullable Runnable getLongPressRunnable(NavHandle navHandle) {
- return null;
+ @Nullable
+ @VisibleForTesting
+ final Runnable getLongPressRunnable(NavHandle navHandle) {
+ if (!isContextualSearchEntrypointEnabled(navHandle)) {
+ Log.i(TAG, "Contextual Search invocation failed: entry point disabled");
+ mVibratorWrapper.cancelVibrate();
+ return null;
+ }
+
+ if (!mContextualSearchInvoker.runContextualSearchInvocationChecksAndLogFailures()) {
+ Log.i(TAG, "Contextual Search invocation failed: precondition not satisfied");
+ mVibratorWrapper.cancelVibrate();
+ return null;
+ }
+
+ mPendingInvocation = true;
+ Log.i(TAG, "Contextual Search invocation: invocation runnable created");
+ InstanceId instanceId = new InstanceIdSequence().newInstanceId();
+ mStatsLogManager.logger().withInstanceId(instanceId).log(
+ LAUNCHER_OMNI_GET_LONG_PRESS_RUNNABLE);
+ long startTimeMillis = SystemClock.elapsedRealtime();
+ return () -> {
+ mStatsLogManager.latencyLogger().withInstanceId(instanceId).withLatency(
+ SystemClock.elapsedRealtime() - startTimeMillis).log(
+ LAUNCHER_LATENCY_OMNI_RUNNABLE);
+ if (mContextualSearchInvoker.invokeContextualSearchUncheckedWithHaptic(
+ ENTRYPOINT_LONG_PRESS_NAV_HANDLE)) {
+ Log.i(TAG, "Contextual Search invocation successful");
+
+ String runningPackage = TopTaskTracker.INSTANCE.get(mContext).getCachedTopTask(
+ /* filterOnlyVisibleRecents */ true).getPackageName();
+ mStatsLogManager.logger().withPackageName(runningPackage)
+ .log(LAUNCHER_LAUNCH_ASSISTANT_SUCCESSFUL_NAV_HANDLE);
+ } else {
+ mVibratorWrapper.cancelVibrate();
+ if (DeviceConfigWrapper.get().getAnimateLpnh()
+ && !DeviceConfigWrapper.get().getShrinkNavHandleOnPress()) {
+ navHandle.animateNavBarLongPress(
+ /*isTouchDown*/false, /*shrink*/ false, /*durationMs*/160);
+ }
+ }
+ };
}
/**
@@ -55,7 +137,15 @@
*
* @param navHandle to handle the animation for this touch
*/
- public void onTouchStarted(NavHandle navHandle) {}
+ @VisibleForTesting
+ final void onTouchStarted(NavHandle navHandle) {
+ mPendingInvocation = false;
+ if (isContextualSearchEntrypointEnabled(navHandle)
+ && mContextualSearchInvoker.runContextualSearchInvocationChecksAndLogFailures()) {
+ Log.i(TAG, "Contextual Search invocation: touch started");
+ startNavBarAnimation(navHandle);
+ }
+ }
/**
* Called when nav handle gesture is finished by the user lifting their finger or the system
@@ -64,5 +154,46 @@
* @param navHandle to handle the animation for this touch
* @param reason why the touch ended
*/
- public void onTouchFinished(NavHandle navHandle, String reason) {}
+ @VisibleForTesting
+ final void onTouchFinished(NavHandle navHandle, String reason) {
+ Log.i(TAG, "Contextual Search invocation: touch finished with reason: " + reason);
+
+ if (!DeviceConfigWrapper.get().getShrinkNavHandleOnPress() || !mPendingInvocation) {
+ mVibratorWrapper.cancelVibrate();
+ }
+
+ if (DeviceConfigWrapper.get().getAnimateLpnh()) {
+ if (DeviceConfigWrapper.get().getShrinkNavHandleOnPress()) {
+ navHandle.animateNavBarLongPress(
+ /*isTouchDown*/false, /*shrink*/ true, /*durationMs*/200);
+ } else {
+ navHandle.animateNavBarLongPress(
+ /*isTouchDown*/false, /*shrink*/ false, /*durationMs*/ 160);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ final void startNavBarAnimation(NavHandle navHandle) {
+ mContextualSearchHapticManager.vibrateForSearchHint();
+
+ if (DeviceConfigWrapper.get().getAnimateLpnh()) {
+ if (DeviceConfigWrapper.get().getShrinkNavHandleOnPress()) {
+ navHandle.animateNavBarLongPress(
+ /*isTouchDown*/ true, /*shrink*/true, /*durationMs*/200);
+ } else {
+ long longPressTimeout;
+ ContextualSearchStateManager contextualSearchStateManager =
+ ContextualSearchStateManager.INSTANCE.get(mContext);
+ if (contextualSearchStateManager.getLPNHDurationMillis().isPresent()) {
+ longPressTimeout =
+ contextualSearchStateManager.getLPNHDurationMillis().get().intValue();
+ } else {
+ longPressTimeout = ViewConfiguration.getLongPressTimeout();
+ }
+ navHandle.animateNavBarLongPress(
+ /*isTouchDown*/ true, /*shrink*/ false, /*durationMs*/ longPressTimeout);
+ }
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
index f4d3695..f5bef05e 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
@@ -38,7 +38,7 @@
import com.android.quickstep.NavHandle;
import com.android.quickstep.RecentsAnimationDeviceState;
import com.android.quickstep.TopTaskTracker;
-import com.android.quickstep.util.AssistStateManager;
+import com.android.quickstep.util.ContextualSearchStateManager;
import com.android.systemui.shared.system.InputMonitorCompat;
/**
@@ -75,9 +75,11 @@
super(delegate, inputMonitor);
mScreenWidth = DisplayController.INSTANCE.get(context).getInfo().currentSize.x;
mDeepPressEnabled = DeviceConfigWrapper.get().getEnableLpnhDeepPress();
- AssistStateManager assistStateManager = AssistStateManager.INSTANCE.get(context);
- if (assistStateManager.getLPNHDurationMillis().isPresent()) {
- mLongPressTimeout = assistStateManager.getLPNHDurationMillis().get().intValue();
+ ContextualSearchStateManager contextualSearchStateManager =
+ ContextualSearchStateManager.INSTANCE.get(context);
+ if (contextualSearchStateManager.getLPNHDurationMillis().isPresent()) {
+ mLongPressTimeout =
+ contextualSearchStateManager.getLPNHDurationMillis().get().intValue();
} else {
mLongPressTimeout = ViewConfiguration.getLongPressTimeout();
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
index 5ad55ae..49bff8d 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
@@ -228,10 +228,13 @@
}
float velocityYPxPerS = mVelocityTracker.getYVelocity();
+ float dY = Math.abs(mLastPos.y - mDownPos.y);
if (mCanPlayTaskbarBgAlphaAnimation
&& mMotionMoveCount >= NUM_MOTION_MOVE_THRESHOLD // Arbitrary value
&& velocityYPxPerS != 0 // Ignore these
- && velocityYPxPerS >= mTaskbarSlowVelocityYThreshold) {
+ && velocityYPxPerS >= mTaskbarSlowVelocityYThreshold
+ && dY != 0
+ && dY > mTouchSlop) {
mTaskbarActivityContext.playTaskbarBackgroundAlphaAnimation();
mCanPlayTaskbarBgAlphaAnimation = false;
}
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index 5028da4..9510a05 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -52,7 +52,6 @@
import androidx.annotation.StringRes;
import androidx.annotation.StyleRes;
import androidx.appcompat.app.AlertDialog;
-import androidx.appcompat.content.res.AppCompatResources;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
@@ -610,8 +609,8 @@
private void updateDrawables() {
if (mContext != null) {
- mTutorialFragment.getRootView().setBackground(AppCompatResources.getDrawable(
- mContext, getMockWallpaperResId()));
+ mTutorialFragment.getRootView()
+ .setBackground(mContext.getDrawable(getMockWallpaperResId()));
mTutorialFragment.updateFeedbackAnimation();
mFakeLauncherView.setBackgroundColor(getFakeLauncherColor());
updateFakeViewLayout(mFakeHotseatView, getMockHotseatResId());
@@ -619,9 +618,7 @@
mFakeTaskView.animate().alpha(1).setListener(
AnimatorListeners.forSuccessCallback(() -> mFakeTaskView.animate().cancel()));
mFakePreviousTaskView.setFakeTaskViewFillColor(getMockPreviousAppTaskThumbnailColor());
- mFakeIconView.setBackground(AppCompatResources.getDrawable(
- mContext, getMockAppIconResId()));
-
+ mFakeIconView.setBackground(mContext.getDrawable(getMockAppIconResId()));
mExitingAppView.setBackgroundColor(getExitingAppColor());
mFakeTaskView.setBackgroundColor(getFakeTaskViewColor());
updateHotseatChildViewColor(mHotseatIconView);
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialStepIndicator.java b/quickstep/src/com/android/quickstep/interaction/TutorialStepIndicator.java
index ae0e725..f1fc179 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialStepIndicator.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialStepIndicator.java
@@ -22,8 +22,6 @@
import android.widget.ImageView;
import android.widget.LinearLayout;
-import androidx.appcompat.content.res.AppCompatResources;
-
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.icons.GraphicsUtils;
@@ -91,9 +89,8 @@
int inactiveStepIndicatorColor = GraphicsUtils.getAttrColor(
getContext(), android.R.attr.textColorSecondaryInverse);
for (int i = 0; i < mTotalSteps; i++) {
- Drawable pageIndicatorPillDrawable = AppCompatResources.getDrawable(
- getContext(), R.drawable.tutorial_step_indicator_pill);
-
+ Drawable pageIndicatorPillDrawable =
+ getContext().getDrawable(R.drawable.tutorial_step_indicator_pill);
if (i >= getChildCount()) {
ImageView pageIndicatorPill = new ImageView(getContext());
pageIndicatorPill.setImageDrawable(pageIndicatorPillDrawable);
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 1d4160d..2daaaf9 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -386,12 +386,12 @@
// and then write to StatsLog.
app.getModel().enqueueModelUpdateTask((taskController, dataModel, apps) ->
write(event, applyOverwrites(mItemInfo.buildProto(
- dataModel.collections.get(mItemInfo.container)))));
+ dataModel.collections.get(mItemInfo.container), mContext))));
})) {
// Write log on the model thread so that logs do not go out of order
// (for eg: drop comes after drag)
Executors.MODEL_EXECUTOR.execute(
- () -> write(event, applyOverwrites(mItemInfo.buildProto())));
+ () -> write(event, applyOverwrites(mItemInfo.buildProto(mContext))));
}
}
diff --git a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
index b53650e..44b8b8d 100644
--- a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
+++ b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
@@ -34,6 +34,7 @@
import com.android.quickstep.task.viewmodel.TaskContainerData
import com.android.quickstep.task.viewmodel.TaskOverlayViewModel
import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
+import com.android.quickstep.task.viewmodel.TaskThumbnailViewModelImpl
import com.android.quickstep.task.viewmodel.TaskViewData
import com.android.quickstep.task.viewmodel.TaskViewModel
import com.android.quickstep.views.TaskViewType
@@ -180,7 +181,7 @@
TaskContainerData::class.java -> TaskContainerData()
TaskThumbnailViewData::class.java -> TaskThumbnailViewData()
TaskThumbnailViewModel::class.java ->
- TaskThumbnailViewModel(
+ TaskThumbnailViewModelImpl(
recentsViewData = inject(),
taskViewData = inject(scopeId, extras),
taskContainerData = inject(scopeId),
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
index 4970685..f55462a 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
@@ -10,144 +10,40 @@
* 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 goveryning permissions and
+ * See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep.task.viewmodel
-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
-import com.android.quickstep.recents.usecase.ThumbnailPositionState
-import com.android.quickstep.recents.viewmodel.RecentsViewData
-import com.android.quickstep.task.thumbnail.SplashAlphaUseCase
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.SnapshotSplash
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
-import com.android.systemui.shared.recents.model.Task
-import kotlin.math.max
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.flow.StateFlow
-@OptIn(ExperimentalCoroutinesApi::class)
-class TaskThumbnailViewModel(
- recentsViewData: RecentsViewData,
- taskViewData: TaskViewData,
- taskContainerData: TaskContainerData,
- private val tasksRepository: RecentTasksRepository,
- private val getThumbnailPositionUseCase: GetThumbnailPositionUseCase,
- private val splashAlphaUseCase: SplashAlphaUseCase,
-) {
- private val task = MutableStateFlow<Flow<Task?>>(flowOf(null))
- private val splashProgress = MutableStateFlow(flowOf(0f))
- private var taskId: Int = INVALID_TASK_ID
-
+/** ViewModel for representing TaskThumbnails */
+interface TaskThumbnailViewModel {
/**
* Progress for changes in corner radius. progress: 0 = overview corner radius; 1 = fullscreen
* corner radius.
*/
- val cornerRadiusProgress =
- if (taskViewData.isOutlineFormedByThumbnailView) recentsViewData.fullscreenProgress
- else MutableStateFlow(1f).asStateFlow()
+ val cornerRadiusProgress: StateFlow<Float>
- val inheritedScale =
- combine(recentsViewData.scale, taskViewData.scale) { recentsScale, taskScale ->
- recentsScale * taskScale
- }
+ /** The accumulated View.scale value for parent Views up to and including RecentsView */
+ val inheritedScale: Flow<Float>
- val dimProgress: Flow<Float> =
- combine(taskContainerData.taskMenuOpenProgress, recentsViewData.tintAmount) {
- taskMenuOpenProgress,
- tintAmount ->
- max(taskMenuOpenProgress * MAX_SCRIM_ALPHA, tintAmount)
- }
- val splashAlpha = splashProgress.flatMapLatest { it }
+ /** Provides the level of dimming that the View should have */
+ val dimProgress: Flow<Float>
- private val isLiveTile =
- combine(
- task.flatMapLatest { it }.map { it?.key?.id }.distinctUntilChanged(),
- recentsViewData.runningTaskIds,
- recentsViewData.runningTaskShowScreenshot,
- ) { taskId, runningTaskIds, runningTaskShowScreenshot ->
- runningTaskIds.contains(taskId) && !runningTaskShowScreenshot
- }
- .distinctUntilChanged()
+ /** Provides the alpha of the splash icon */
+ val splashAlpha: Flow<Float>
- 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
- isBackgroundOnly(taskVal) ->
- BackgroundOnly(taskVal.colorBackground.removeAlpha())
- isSnapshotSplashState(taskVal) ->
- SnapshotSplash(createSnapshotState(taskVal), taskVal.icon)
- else -> Uninitialized
- }
- }
- .distinctUntilChanged()
+ /** Provides the UiState by which the task thumbnail can be represented */
+ val uiState: Flow<TaskThumbnailUiState>
- fun bind(taskId: Int) {
- Log.d(TAG, "bind taskId: $taskId")
- this.taskId = taskId
- task.value = tasksRepository.getTaskDataById(taskId)
- splashProgress.value = splashAlphaUseCase.execute(taskId)
- }
+ /** Attaches this ViewModel to a specific task id for it to provide data from. */
+ fun bind(taskId: Int)
- fun getThumbnailPositionState(width: Int, height: Int, isRtl: Boolean): Matrix {
- return runBlocking {
- when (
- val thumbnailPositionState =
- getThumbnailPositionUseCase.run(taskId, width, height, isRtl)
- ) {
- is ThumbnailPositionState.MatrixScaling -> thumbnailPositionState.matrix
- is ThumbnailPositionState.MissingThumbnail -> Matrix.IDENTITY_MATRIX
- }
- }
- }
-
- private fun isBackgroundOnly(task: Task): Boolean = task.isLocked || task.thumbnail == null
-
- private fun isSnapshotSplashState(task: Task): Boolean {
- val thumbnailPresent = task.thumbnail?.thumbnail != null
- val taskLocked = task.isLocked
-
- return thumbnailPresent && !taskLocked
- }
-
- private fun createSnapshotState(task: Task): Snapshot {
- val thumbnailData = task.thumbnail
- val bitmap = thumbnailData?.thumbnail!!
- return Snapshot(bitmap, thumbnailData.rotation, task.colorBackground.removeAlpha())
- }
-
- @ColorInt private fun Int.removeAlpha(): Int = ColorUtils.setAlphaComponent(this, 0xff)
-
- private companion object {
- const val MAX_SCRIM_ALPHA = 0.4f
- const val TAG = "TaskThumbnailViewModel"
- }
+ /** Returns a Matrix which can be applied to the snapshot */
+ fun getThumbnailPositionState(width: Int, height: Int, isRtl: Boolean): Matrix
}
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
new file mode 100644
index 0000000..bd47cec
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
@@ -0,0 +1,149 @@
+/*
+ * 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 goveryning permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.task.viewmodel
+
+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
+import com.android.quickstep.recents.usecase.ThumbnailPositionState
+import com.android.quickstep.recents.viewmodel.RecentsViewData
+import com.android.quickstep.task.thumbnail.SplashAlphaUseCase
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.SnapshotSplash
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
+import com.android.systemui.shared.recents.model.Task
+import kotlin.math.max
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.runBlocking
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class TaskThumbnailViewModelImpl(
+ recentsViewData: RecentsViewData,
+ taskViewData: TaskViewData,
+ taskContainerData: TaskContainerData,
+ private val tasksRepository: RecentTasksRepository,
+ private val getThumbnailPositionUseCase: GetThumbnailPositionUseCase,
+ private val splashAlphaUseCase: SplashAlphaUseCase,
+) : TaskThumbnailViewModel {
+ private val task = MutableStateFlow<Flow<Task?>>(flowOf(null))
+ private val splashProgress = MutableStateFlow(flowOf(0f))
+ private var taskId: Int = INVALID_TASK_ID
+
+ override val cornerRadiusProgress =
+ if (taskViewData.isOutlineFormedByThumbnailView) recentsViewData.fullscreenProgress
+ else MutableStateFlow(1f).asStateFlow()
+
+ override val inheritedScale =
+ combine(recentsViewData.scale, taskViewData.scale) { recentsScale, taskScale ->
+ recentsScale * taskScale
+ }
+
+ override val dimProgress: Flow<Float> =
+ combine(taskContainerData.taskMenuOpenProgress, recentsViewData.tintAmount) {
+ taskMenuOpenProgress,
+ tintAmount ->
+ max(taskMenuOpenProgress * MAX_SCRIM_ALPHA, tintAmount)
+ }
+ override val splashAlpha = splashProgress.flatMapLatest { it }
+
+ private val isLiveTile =
+ combine(
+ task.flatMapLatest { it }.map { it?.key?.id }.distinctUntilChanged(),
+ recentsViewData.runningTaskIds,
+ recentsViewData.runningTaskShowScreenshot,
+ ) { taskId, runningTaskIds, runningTaskShowScreenshot ->
+ runningTaskIds.contains(taskId) && !runningTaskShowScreenshot
+ }
+ .distinctUntilChanged()
+
+ override 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
+ isBackgroundOnly(taskVal) ->
+ BackgroundOnly(taskVal.colorBackground.removeAlpha())
+ isSnapshotSplashState(taskVal) ->
+ SnapshotSplash(createSnapshotState(taskVal), taskVal.icon)
+ else -> Uninitialized
+ }
+ }
+ .distinctUntilChanged()
+
+ override fun bind(taskId: Int) {
+ Log.d(TAG, "bind taskId: $taskId")
+ this.taskId = taskId
+ task.value = tasksRepository.getTaskDataById(taskId)
+ splashProgress.value = splashAlphaUseCase.execute(taskId)
+ }
+
+ override fun getThumbnailPositionState(width: Int, height: Int, isRtl: Boolean): Matrix {
+ return runBlocking {
+ when (
+ val thumbnailPositionState =
+ getThumbnailPositionUseCase.run(taskId, width, height, isRtl)
+ ) {
+ is ThumbnailPositionState.MatrixScaling -> thumbnailPositionState.matrix
+ is ThumbnailPositionState.MissingThumbnail -> Matrix.IDENTITY_MATRIX
+ }
+ }
+ }
+
+ private fun isBackgroundOnly(task: Task): Boolean = task.isLocked || task.thumbnail == null
+
+ private fun isSnapshotSplashState(task: Task): Boolean {
+ val thumbnailPresent = task.thumbnail?.thumbnail != null
+ val taskLocked = task.isLocked
+
+ return thumbnailPresent && !taskLocked
+ }
+
+ private fun createSnapshotState(task: Task): Snapshot {
+ val thumbnailData = task.thumbnail
+ val bitmap = thumbnailData?.thumbnail!!
+ return Snapshot(bitmap, thumbnailData.rotation, task.colorBackground.removeAlpha())
+ }
+
+ @ColorInt private fun Int.removeAlpha(): Int = ColorUtils.setAlphaComponent(this, 0xff)
+
+ 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/AssistStateManager.java b/quickstep/src/com/android/quickstep/util/AssistStateManager.java
deleted file mode 100644
index 7acb28d..0000000
--- a/quickstep/src/com/android/quickstep/util/AssistStateManager.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2023 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 com.android.launcher3.util.MainThreadInitializedObject.forOverride;
-
-import com.android.launcher3.R;
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.ResourceBasedOverride;
-import com.android.launcher3.util.SafeCloseable;
-
-import java.io.PrintWriter;
-import java.util.Optional;
-
-/** Class to manage Assistant states. */
-public class AssistStateManager implements ResourceBasedOverride, SafeCloseable {
-
- public static final MainThreadInitializedObject<AssistStateManager> INSTANCE =
- forOverride(AssistStateManager.class, R.string.assist_state_manager_class);
-
- public AssistStateManager() {}
-
- /** Return {@code true} if the Settings toggle is enabled. */
- public boolean isSettingsAllEntrypointsEnabled() {
- return false;
- }
-
- /** Whether search supports showing on the lockscreen. */
- public boolean supportsShowWhenLocked() {
- return false;
- }
-
- /** Whether ContextualSearchService invocation path is available. */
- public boolean isContextualSearchServiceAvailable() {
- return false;
- }
-
- /** Get the Launcher overridden long press nav handle duration to trigger Assistant. */
- public Optional<Long> getLPNHDurationMillis() {
- return Optional.empty();
- }
-
- /**
- * Get the Launcher overridden long press nav handle touch slop multiplier to trigger Assistant.
- */
- public Optional<Float> getLPNHCustomSlopMultiplier() {
- return Optional.empty();
- }
-
- /** Get the Launcher overridden long press home duration to trigger Assistant. */
- public Optional<Long> getLPHDurationMillis() {
- return Optional.empty();
- }
-
- /** Get the Launcher overridden long press home touch slop multiplier to trigger Assistant. */
- public Optional<Float> getLPHCustomSlopMultiplier() {
- return Optional.empty();
- }
-
- /** Get the long press duration data source. */
- public int getDurationDataSource() {
- return 0;
- }
-
- /** Get the long press touch slop multiplier data source. */
- public int getSlopDataSource() {
- return 0;
- }
-
- /** Get the haptic bit overridden by AGSA. */
- public Optional<Boolean> getShouldPlayHapticOverride() {
- return Optional.empty();
- }
-
- /** Dump states. */
- public void dump(String prefix, PrintWriter writer) {}
-
- @Override
- public void close() {}
-}
diff --git a/quickstep/src/com/android/quickstep/util/AssistUtils.java b/quickstep/src/com/android/quickstep/util/AssistUtils.java
deleted file mode 100644
index 11b6ea7..0000000
--- a/quickstep/src/com/android/quickstep/util/AssistUtils.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.util;
-
-import android.content.Context;
-
-import com.android.launcher3.R;
-import com.android.launcher3.util.ResourceBasedOverride;
-
-/** Utilities to work with Assistant functionality. */
-public class AssistUtils implements ResourceBasedOverride {
-
- public AssistUtils() {}
-
- /** Creates AssistUtils as specified by overrides */
- public static AssistUtils newInstance(Context context) {
- return Overrides.getObject(AssistUtils.class, context, R.string.assist_utils_class);
- }
-
- /** @return Array of AssistUtils.INVOCATION_TYPE_* that we want to handle instead of SysUI. */
- public int[] getSysUiAssistOverrideInvocationTypes() {
- return new int[0];
- }
-
- /**
- * @return {@code true} if the override was handled, i.e. an assist surface was shown or the
- * request should be ignored. {@code false} means the caller should start assist another way.
- */
- public boolean tryStartAssistOverride(int invocationType) {
- return false;
- }
-}
diff --git a/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java b/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java
index 38ae303..4a84b1b 100644
--- a/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java
+++ b/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java
@@ -32,23 +32,33 @@
import androidx.annotation.WorkerThread;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.dagger.LauncherBaseAppComponent;
+import com.android.launcher3.util.DaggerSingletonObject;
+import com.android.launcher3.util.DaggerSingletonTracker;
+import com.android.launcher3.util.ExecutorUtil;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.SettingsCache.OnChangeListener;
import com.android.launcher3.util.SimpleBroadcastReceiver;
+import com.android.quickstep.dagger.QuickstepBaseAppComponent;
import java.util.ArrayList;
import java.util.List;
+import javax.inject.Inject;
+
/**
* Extension of {@link ClockEventDelegate} to support async event registration
*/
+@LauncherAppSingleton
public class AsyncClockEventDelegate extends ClockEventDelegate
implements OnChangeListener, SafeCloseable {
- public static final MainThreadInitializedObject<AsyncClockEventDelegate> INSTANCE =
- new MainThreadInitializedObject<>(AsyncClockEventDelegate::new);
+ public static final DaggerSingletonObject<AsyncClockEventDelegate> INSTANCE =
+ new DaggerSingletonObject<>(QuickstepBaseAppComponent::getAsyncClockEventDelegate);
private final Context mContext;
private final SimpleBroadcastReceiver mReceiver =
@@ -61,10 +71,12 @@
private boolean mFormatRegistered = false;
private boolean mDestroyed = false;
- private AsyncClockEventDelegate(Context context) {
+ @Inject
+ AsyncClockEventDelegate(@ApplicationContext Context context, DaggerSingletonTracker tracker) {
super(context);
mContext = context;
mReceiver.register(mContext, ACTION_TIME_CHANGED, ACTION_TIMEZONE_CHANGED);
+ ExecutorUtil.executeSyncOnMainOrFail(() -> tracker.addCloseable(this));
}
@Override
diff --git a/quickstep/src/com/android/quickstep/util/ContextualSearchHapticManager.kt b/quickstep/src/com/android/quickstep/util/ContextualSearchHapticManager.kt
new file mode 100644
index 0000000..8c246a5
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/ContextualSearchHapticManager.kt
@@ -0,0 +1,101 @@
+/*
+ * 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 android.content.Context
+import android.os.VibrationEffect
+import android.os.VibrationEffect.Composition
+import android.os.Vibrator
+import com.android.launcher3.dagger.ApplicationContext
+import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.util.VibratorWrapper
+import com.android.quickstep.DeviceConfigWrapper.Companion.get
+import javax.inject.Inject
+import kotlin.math.pow
+
+/** Manages haptics relating to Contextual Search invocations. */
+@LauncherAppSingleton
+class ContextualSearchHapticManager
+@Inject
+internal constructor(@ApplicationContext private val context: Context) {
+
+ private var searchEffect = createSearchEffect()
+ private var contextualSearchStateManager = ContextualSearchStateManager.INSTANCE[context]
+
+ private fun createSearchEffect() =
+ if (
+ context
+ .getSystemService(Vibrator::class.java)!!
+ .areAllPrimitivesSupported(Composition.PRIMITIVE_TICK)
+ ) {
+ VibrationEffect.startComposition()
+ .addPrimitive(Composition.PRIMITIVE_TICK, 1f)
+ .compose()
+ } else {
+ // fallback for devices without composition support
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_HEAVY_CLICK)
+ }
+
+ /** Indicates that search has been invoked. */
+ fun vibrateForSearch() {
+ searchEffect.let { VibratorWrapper.INSTANCE[context].vibrate(it) }
+ }
+
+ /** Indicates that search will be invoked if the current gesture is maintained. */
+ fun vibrateForSearchHint() {
+ val navbarConfig = get()
+ // Whether we should play the hint (ramp up) haptic
+ val shouldVibrate: Boolean =
+ if (
+ context
+ .getSystemService(Vibrator::class.java)!!
+ .areAllPrimitivesSupported(Composition.PRIMITIVE_LOW_TICK)
+ ) {
+ if (contextualSearchStateManager.shouldPlayHapticOverride.isPresent) {
+ contextualSearchStateManager.shouldPlayHapticOverride.get()
+ } else {
+ navbarConfig.enableSearchHapticHint
+ }
+ } else {
+ false
+ }
+
+ if (shouldVibrate) {
+ val startScale = navbarConfig.lpnhHapticHintStartScalePercent / 100f
+ val endScale = navbarConfig.lpnhHapticHintEndScalePercent / 100f
+ val scaleExponent = navbarConfig.lpnhHapticHintScaleExponent
+ val iterations = navbarConfig.lpnhHapticHintIterations
+ val delayMs = navbarConfig.lpnhHapticHintDelay
+ val composition = VibrationEffect.startComposition()
+ for (i in 0 until iterations) {
+ val t = i / (iterations - 1f)
+ val scale =
+ ((1 - t) * startScale + t * endScale)
+ .toDouble()
+ .pow(scaleExponent.toDouble())
+ .toFloat()
+ if (i == 0) {
+ // Adds a delay before the ramp starts
+ composition.addPrimitive(Composition.PRIMITIVE_LOW_TICK, scale, delayMs)
+ } else {
+ composition.addPrimitive(Composition.PRIMITIVE_LOW_TICK, scale)
+ }
+ }
+ VibratorWrapper.INSTANCE[context].vibrate(composition.compose())
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt b/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt
new file mode 100644
index 0000000..dcb72aa
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt
@@ -0,0 +1,224 @@
+/*
+ * 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 android.app.contextualsearch.ContextualSearchManager
+import android.app.contextualsearch.ContextualSearchManager.ENTRYPOINT_LONG_PRESS_HOME
+import android.app.contextualsearch.ContextualSearchManager.FEATURE_CONTEXTUAL_SEARCH
+import android.content.Context
+import android.util.Log
+import com.android.internal.app.AssistUtils
+import com.android.launcher3.LauncherApplication
+import com.android.launcher3.R
+import com.android.launcher3.logging.StatsLogManager
+import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_ASSISTANT_FAILED_SERVICE_ERROR
+import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_OMNI_ATTEMPTED_OVER_KEYGUARD
+import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_OMNI_ATTEMPTED_OVER_NOTIFICATION_SHADE
+import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_OMNI_ATTEMPTED_SPLITSCREEN
+import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_OMNI_FAILED_NOT_AVAILABLE
+import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_OMNI_FAILED_SETTING_DISABLED
+import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_OMNI_SUCCESSFUL_HOME
+import com.android.launcher3.util.ResourceBasedOverride
+import com.android.quickstep.DeviceConfigWrapper
+import com.android.quickstep.SystemUiProxy
+import com.android.quickstep.TopTaskTracker
+import com.android.systemui.shared.system.QuickStepContract
+
+/** Handles invocations and checks for Contextual Search. */
+open class ContextualSearchInvoker
+internal constructor(
+ protected val context: Context,
+ private val contextualSearchStateManager: ContextualSearchStateManager,
+ private val topTaskTracker: TopTaskTracker,
+ private val systemUiProxy: SystemUiProxy,
+ protected val statsLogManager: StatsLogManager,
+ private val contextualSearchHapticManager: ContextualSearchHapticManager,
+ private val contextualSearchManager: ContextualSearchManager?,
+) : ResourceBasedOverride {
+ constructor(
+ context: Context
+ ) : this(
+ context,
+ ContextualSearchStateManager.INSTANCE[context],
+ TopTaskTracker.INSTANCE[context],
+ SystemUiProxy.INSTANCE[context],
+ StatsLogManager.newInstance(context),
+ (context.applicationContext as LauncherApplication)
+ .appComponent
+ .contextualSearchHapticManager,
+ context.getSystemService(ContextualSearchManager::class.java),
+ )
+
+ /** @return Array of AssistUtils.INVOCATION_TYPE_* that we want to handle instead of SysUI. */
+ open fun getSysUiAssistOverrideInvocationTypes(): IntArray {
+ val overrideInvocationTypes = com.android.launcher3.util.IntArray()
+ if (context.packageManager.hasSystemFeature(FEATURE_CONTEXTUAL_SEARCH)) {
+ overrideInvocationTypes.add(AssistUtils.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS)
+ }
+ return overrideInvocationTypes.toArray()
+ }
+
+ /**
+ * @return `true` if the override was handled, i.e. an assist surface was shown or the request
+ * should be ignored. `false` means the caller should start assist another way.
+ */
+ fun tryStartAssistOverride(invocationType: Int): Boolean {
+ if (invocationType == AssistUtils.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS) {
+ if (!context.packageManager.hasSystemFeature(FEATURE_CONTEXTUAL_SEARCH)) {
+ // When Contextual Search is disabled, fall back to Assistant.
+ return false
+ }
+
+ val success = show(ENTRYPOINT_LONG_PRESS_HOME)
+ if (success) {
+ val runningPackage =
+ TopTaskTracker.INSTANCE[context].getCachedTopTask(
+ /* filterOnlyVisibleRecents */ true
+ )
+ .getPackageName()
+ statsLogManager
+ .logger()
+ .withPackageName(runningPackage)
+ .log(LAUNCHER_LAUNCH_OMNI_SUCCESSFUL_HOME)
+ }
+
+ // Regardless of success, do not fall back to other assistant.
+ return true
+ }
+ return false
+ }
+
+ /**
+ * Invoke Contextual Search via ContextualSearchService if availability checks are successful
+ *
+ * @param entryPoint one of the ENTRY_POINT_* constants defined in this class
+ * @return true if invocation was successful, false otherwise
+ */
+ fun show(entryPoint: Int): Boolean {
+ return if (!runContextualSearchInvocationChecksAndLogFailures()) false
+ else invokeContextualSearchUnchecked(entryPoint)
+ }
+
+ /**
+ * Run availability checks and log errors to WW. If successful the caller is expected to call
+ * {@link invokeContextualSearchUnchecked}
+ *
+ * @return true if availability checks were successful, false otherwise.
+ */
+ fun runContextualSearchInvocationChecksAndLogFailures(): Boolean {
+ if (
+ contextualSearchManager == null ||
+ !context.packageManager.hasSystemFeature(FEATURE_CONTEXTUAL_SEARCH)
+ ) {
+ Log.i(TAG, "Contextual Search invocation failed: no ContextualSearchManager")
+ statsLogManager.logger().log(LAUNCHER_LAUNCH_ASSISTANT_FAILED_SERVICE_ERROR)
+ return false
+ }
+ if (!contextualSearchStateManager.isContextualSearchSettingEnabled) {
+ Log.i(TAG, "Contextual Search invocation failed: setting disabled")
+ statsLogManager.logger().log(LAUNCHER_LAUNCH_OMNI_FAILED_SETTING_DISABLED)
+ return false
+ }
+ if (isNotificationShadeShowing()) {
+ Log.i(TAG, "Contextual Search invocation failed: notification shade")
+ statsLogManager.logger().log(LAUNCHER_LAUNCH_OMNI_ATTEMPTED_OVER_NOTIFICATION_SHADE)
+ return false
+ }
+ if (isKeyguardShowing()) {
+ Log.i(TAG, "Contextual Search invocation attempted: keyguard")
+ statsLogManager.logger().log(LAUNCHER_LAUNCH_OMNI_ATTEMPTED_OVER_KEYGUARD)
+ if (!contextualSearchStateManager.isInvocationAllowedOnKeyguard) {
+ Log.i(TAG, "Contextual Search invocation failed: keyguard not allowed")
+ return false
+ } else if (!contextualSearchStateManager.supportsShowWhenLocked()) {
+ Log.i(TAG, "Contextual Search invocation failed: AGA doesn't support keyguard")
+ return false
+ }
+ }
+ if (isInSplitscreen()) {
+ Log.i(TAG, "Contextual Search invocation attempted: splitscreen")
+ statsLogManager.logger().log(LAUNCHER_LAUNCH_OMNI_ATTEMPTED_SPLITSCREEN)
+ if (!contextualSearchStateManager.isInvocationAllowedInSplitscreen) {
+ Log.i(TAG, "Contextual Search invocation failed: splitscreen not allowed")
+ return false
+ }
+ }
+ if (!contextualSearchStateManager.isContextualSearchIntentAvailable) {
+ Log.i(TAG, "Contextual Search invocation failed: no matching CSS intent filter")
+ statsLogManager.logger().log(LAUNCHER_LAUNCH_OMNI_FAILED_NOT_AVAILABLE)
+ return false
+ }
+
+ return true
+ }
+
+ /**
+ * Invoke Contextual Search via ContextualSearchService and do haptic
+ *
+ * @param entryPoint Entry point identifier, passed to ContextualSearchService.
+ * @return true if invocation was successful, false otherwise
+ */
+ fun invokeContextualSearchUncheckedWithHaptic(entryPoint: Int): Boolean {
+ return invokeContextualSearchUnchecked(entryPoint, withHaptic = true)
+ }
+
+ private fun invokeContextualSearchUnchecked(
+ entryPoint: Int,
+ withHaptic: Boolean = false,
+ ): Boolean {
+ if (withHaptic && DeviceConfigWrapper.get().enableSearchHapticCommit) {
+ contextualSearchHapticManager.vibrateForSearch()
+ }
+ if (contextualSearchManager == null) {
+ return false
+ }
+ contextualSearchManager.startContextualSearch(entryPoint)
+ return true
+ }
+
+ private fun isInSplitscreen(): Boolean {
+ return topTaskTracker.getRunningSplitTaskIds().isNotEmpty()
+ }
+
+ private fun isNotificationShadeShowing(): Boolean {
+ return systemUiProxy.lastSystemUiStateFlags and SHADE_EXPANDED_SYSUI_FLAGS != 0L
+ }
+
+ private fun isKeyguardShowing(): Boolean {
+ return systemUiProxy.lastSystemUiStateFlags and KEYGUARD_SHOWING_SYSUI_FLAGS != 0L
+ }
+
+ companion object {
+ private const val TAG = "ContextualSearchInvoker"
+ const val SHADE_EXPANDED_SYSUI_FLAGS =
+ QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED or
+ QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED
+ const val KEYGUARD_SHOWING_SYSUI_FLAGS =
+ (QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING or
+ QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING or
+ QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED)
+
+ @JvmStatic
+ fun newInstance(context: Context): ContextualSearchInvoker {
+ return ResourceBasedOverride.Overrides.getObject(
+ ContextualSearchInvoker::class.java,
+ context,
+ R.string.contextual_search_invoker_class,
+ )
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java b/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java
new file mode 100644
index 0000000..083f192
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2023 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.app.contextualsearch.ContextualSearchManager.ACTION_LAUNCH_CONTEXTUAL_SEARCH;
+import static android.app.contextualsearch.ContextualSearchManager.ENTRYPOINT_SYSTEM_ACTION;
+import static android.app.contextualsearch.ContextualSearchManager.FEATURE_CONTEXTUAL_SEARCH;
+
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_OMNI_SUCCESSFUL_SYSTEM_ACTION;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
+import static com.android.quickstep.util.SystemActionConstants.SYSTEM_ACTION_ID_SEARCH_SCREEN;
+
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.content.Context;
+import android.content.IIntentReceiver;
+import android.content.IIntentSender;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.annotation.CallSuper;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.launcher3.R;
+import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.util.EventLogArray;
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.ResourceBasedOverride;
+import com.android.launcher3.util.SafeCloseable;
+import com.android.launcher3.util.SettingsCache;
+import com.android.launcher3.util.SimpleBroadcastReceiver;
+import com.android.quickstep.DeviceConfigWrapper;
+import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.TopTaskTracker;
+
+import java.io.PrintWriter;
+import java.util.Optional;
+
+/** Long-lived class to manage Contextual Search states like the user setting and availability. */
+public class ContextualSearchStateManager implements ResourceBasedOverride, SafeCloseable {
+
+ public static final MainThreadInitializedObject<ContextualSearchStateManager> INSTANCE =
+ forOverride(ContextualSearchStateManager.class,
+ R.string.contextual_search_state_manager_class);
+
+ private static final String TAG = "ContextualSearchStMgr";
+ private static final int MAX_DEBUG_EVENT_SIZE = 20;
+ private static final Uri SEARCH_ALL_ENTRYPOINTS_ENABLED_URI =
+ Settings.Secure.getUriFor(Settings.Secure.SEARCH_ALL_ENTRYPOINTS_ENABLED);
+
+ private final Runnable mSysUiStateChangeListener = this::updateOverridesToSysUi;
+ private final SimpleBroadcastReceiver mContextualSearchPackageReceiver =
+ new SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, (unused) -> requestUpdateProperties());
+ private final SettingsCache.OnChangeListener mContextualSearchSettingChangedListener =
+ this::onContextualSearchSettingChanged;
+ protected final EventLogArray mEventLogArray = new EventLogArray(TAG, MAX_DEBUG_EVENT_SIZE);
+
+ @Nullable private SettingsCache mSettingsCache;
+ // Cached value whether the ContextualSearch intent filter matched any enabled components.
+ private boolean mIsContextualSearchIntentAvailable;
+ private boolean mIsContextualSearchSettingEnabled;
+
+ protected Context mContext;
+ protected String mContextualSearchPackage;
+
+ public ContextualSearchStateManager() {}
+
+ public ContextualSearchStateManager(Context context) {
+ mContext = context;
+ mContextualSearchPackage = mContext.getResources().getString(
+ com.android.internal.R.string.config_defaultContextualSearchPackageName);
+
+ if (areAllContextualSearchFlagsDisabled()
+ || !context.getPackageManager().hasSystemFeature(FEATURE_CONTEXTUAL_SEARCH)) {
+ // If we had previously registered a SystemAction which is no longer valid, we need to
+ // unregister it here.
+ unregisterSearchScreenSystemAction();
+ // Don't listen for stuff we aren't gonna use.
+ return;
+ }
+
+ requestUpdateProperties();
+ registerSearchScreenSystemAction();
+ mContextualSearchPackageReceiver.registerPkgActions(
+ context, mContextualSearchPackage, Intent.ACTION_PACKAGE_ADDED,
+ Intent.ACTION_PACKAGE_CHANGED, Intent.ACTION_PACKAGE_REMOVED);
+
+ mSettingsCache = SettingsCache.INSTANCE.get(context);
+ mSettingsCache.register(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI,
+ mContextualSearchSettingChangedListener);
+ onContextualSearchSettingChanged(
+ mSettingsCache.getValue(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI));
+ SystemUiProxy.INSTANCE.get(mContext).addOnStateChangeListener(mSysUiStateChangeListener);
+ }
+
+ /** Return {@code true} if the Settings toggle is enabled. */
+ public final boolean isContextualSearchSettingEnabled() {
+ return mIsContextualSearchSettingEnabled;
+ }
+
+ private void onContextualSearchSettingChanged(boolean isEnabled) {
+ mIsContextualSearchSettingEnabled = isEnabled;
+ }
+
+ /** Whether search supports showing on the lockscreen. */
+ protected boolean supportsShowWhenLocked() {
+ return false;
+ }
+
+ /** Whether ContextualSearchService invocation path is available. */
+ @VisibleForTesting
+ protected final boolean isContextualSearchIntentAvailable() {
+ return mIsContextualSearchIntentAvailable;
+ }
+
+ /** Get the Launcher overridden long press nav handle duration to trigger Assistant. */
+ public Optional<Long> getLPNHDurationMillis() {
+ return Optional.empty();
+ }
+
+ /**
+ * Get the Launcher overridden long press nav handle touch slop multiplier to trigger Assistant.
+ */
+ public Optional<Float> getLPNHCustomSlopMultiplier() {
+ return Optional.empty();
+ }
+
+ /** Get the Launcher overridden long press home duration to trigger Assistant. */
+ public Optional<Long> getLPHDurationMillis() {
+ return Optional.empty();
+ }
+
+ /** Get the Launcher overridden long press home touch slop multiplier to trigger Assistant. */
+ public Optional<Float> getLPHCustomSlopMultiplier() {
+ return Optional.empty();
+ }
+
+ /** Get the long press duration data source. */
+ public int getDurationDataSource() {
+ return 0;
+ }
+
+ /** Get the long press touch slop multiplier data source. */
+ public int getSlopDataSource() {
+ return 0;
+ }
+
+ /**
+ * Get the User group based on the behavior to trigger Assistant.
+ */
+ public Optional<Integer> getLPUserGroup() {
+ return Optional.empty();
+ }
+
+ /** Get the haptic bit overridden by AGSA. */
+ public Optional<Boolean> getShouldPlayHapticOverride() {
+ return Optional.empty();
+ }
+
+ protected boolean isInvocationAllowedOnKeyguard() {
+ return false;
+ }
+
+ protected boolean isInvocationAllowedInSplitscreen() {
+ return true;
+ }
+
+ @CallSuper
+ protected boolean areAllContextualSearchFlagsDisabled() {
+ return !DeviceConfigWrapper.get().getEnableLongPressNavHandle();
+ }
+
+ @CallSuper
+ protected void requestUpdateProperties() {
+ UI_HELPER_EXECUTOR.execute(() -> {
+ // Check that Contextual Search intent filters are enabled.
+ Intent csIntent = new Intent(ACTION_LAUNCH_CONTEXTUAL_SEARCH).setPackage(
+ mContextualSearchPackage);
+ mIsContextualSearchIntentAvailable =
+ !mContext.getPackageManager().queryIntentActivities(csIntent,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE).isEmpty();
+
+ addEventLog("Updated isContextualSearchIntentAvailable",
+ mIsContextualSearchIntentAvailable);
+ });
+ }
+
+ protected final void updateOverridesToSysUi() {
+ // LPH commit haptic is always enabled
+ SystemUiProxy.INSTANCE.get(mContext).setOverrideHomeButtonLongPress(
+ getLPHDurationMillis().orElse(0L), getLPHCustomSlopMultiplier().orElse(0f), true);
+ Log.i(TAG, "Sent LPH override to sysui: " + getLPHDurationMillis().orElse(0L) + ";"
+ + getLPHCustomSlopMultiplier().orElse(0f));
+ }
+
+ private void registerSearchScreenSystemAction() {
+ PendingIntent searchScreenPendingIntent = new PendingIntent(new IIntentSender.Stub() {
+ @Override
+ public void send(int i, Intent intent, String s, IBinder iBinder,
+ IIntentReceiver iIntentReceiver, String s1, Bundle bundle)
+ throws RemoteException {
+ // Delayed slightly to minimize chance of capturing the System Actions dialog.
+ UI_HELPER_EXECUTOR.getHandler().postDelayed(
+ () -> {
+ boolean contextualSearchInvoked =
+ ContextualSearchInvoker.newInstance(mContext).show(
+ ENTRYPOINT_SYSTEM_ACTION);
+ if (contextualSearchInvoked) {
+ String runningPackage =
+ TopTaskTracker.INSTANCE.get(mContext).getCachedTopTask(
+ /* filterOnlyVisibleRecents */
+ true).getPackageName();
+ StatsLogManager.newInstance(mContext).logger()
+ .withPackageName(runningPackage)
+ .log(LAUNCHER_LAUNCH_OMNI_SUCCESSFUL_SYSTEM_ACTION);
+ }
+ }, 200);
+ }
+ });
+
+ mContext.getSystemService(AccessibilityManager.class).registerSystemAction(new RemoteAction(
+ Icon.createWithResource(mContext, R.drawable.ic_allapps_search),
+ mContext.getString(R.string.search_gesture_feature_title),
+ mContext.getString(R.string.search_gesture_feature_title),
+ searchScreenPendingIntent),
+ SYSTEM_ACTION_ID_SEARCH_SCREEN);
+ }
+
+ private void unregisterSearchScreenSystemAction() {
+ mContext.getSystemService(AccessibilityManager.class).unregisterSystemAction(
+ SYSTEM_ACTION_ID_SEARCH_SCREEN);
+ }
+
+ /** Dump states. */
+ public final void dump(String prefix, PrintWriter writer) {
+ synchronized (mEventLogArray) {
+ mEventLogArray.dump(prefix, writer);
+ }
+ }
+
+ @Override
+ public void close() {
+ mContextualSearchPackageReceiver.unregisterReceiverSafely(mContext);
+ unregisterSearchScreenSystemAction();
+
+ if (mSettingsCache != null) {
+ mSettingsCache.unregister(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI,
+ mContextualSearchSettingChangedListener);
+ }
+ SystemUiProxy.INSTANCE.get(mContext).removeOnStateChangeListener(mSysUiStateChangeListener);
+ }
+
+ protected final void addEventLog(String event) {
+ synchronized (mEventLogArray) {
+ mEventLogArray.addLog(event);
+ }
+ }
+
+ protected final void addEventLog(String event, boolean extras) {
+ synchronized (mEventLogArray) {
+ mEventLogArray.addLog(event, extras);
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/DesktopTask.java b/quickstep/src/com/android/quickstep/util/DesktopTask.java
index a727aa2..fc4fc4d 100644
--- a/quickstep/src/com/android/quickstep/util/DesktopTask.java
+++ b/quickstep/src/com/android/quickstep/util/DesktopTask.java
@@ -50,6 +50,11 @@
@Override
public boolean hasMultipleTasks() {
+ return tasks.size() > 1;
+ }
+
+ @Override
+ public boolean supportsMultipleTasks() {
return true;
}
diff --git a/quickstep/src/com/android/quickstep/util/GroupTask.java b/quickstep/src/com/android/quickstep/util/GroupTask.java
index fba08a9..7aeeb2f 100644
--- a/quickstep/src/com/android/quickstep/util/GroupTask.java
+++ b/quickstep/src/com/android/quickstep/util/GroupTask.java
@@ -66,6 +66,13 @@
}
/**
+ * Returns whether this task supports multiple tasks or not.
+ */
+ public boolean supportsMultipleTasks() {
+ return taskViewType == TaskViewType.GROUPED;
+ }
+
+ /**
* Returns a List of all the Tasks in this GroupTask
*/
public List<Task> getTasks() {
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
index 4a9e0d8..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());
}
- ActiveGestureProtoLogProxy.logMotionPauseDetectorEvent(logString.toString());
+ ActiveGestureProtoLogProxy.logMotionPauseDetectorEvent(event);
}
public void clear() {
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index 9335e7e..a5be89a 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -30,7 +30,6 @@
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.content.Context;
-import android.content.SharedPreferences;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.PointF;
@@ -45,6 +44,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherPrefChangeListener;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.touch.PagedOrientationHandler;
@@ -66,8 +66,7 @@
* This class has initial default state assuming the device and foreground app have
* no ({@link Surface#ROTATION_0} rotation.
*/
-public class RecentsOrientedState implements
- SharedPreferences.OnSharedPreferenceChangeListener {
+public class RecentsOrientedState implements LauncherPrefChangeListener {
private static final String TAG = "RecentsOrientedState";
private static final boolean DEBUG = false;
@@ -283,7 +282,7 @@
}
@Override
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
+ public void onPrefChanged(String s) {
if (LauncherPrefs.ALLOW_ROTATION.getSharedPrefKey().equals(s)) {
updateHomeRotationSetting();
}
diff --git a/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt
index e70372f..c3b072d 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt
+++ b/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt
@@ -73,9 +73,21 @@
* 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? =
@@ -83,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)
@@ -109,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..2f0a6df 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
@@ -60,7 +62,8 @@
* Custom interpolator for both the home and wallpaper scaling. Necessary because EMPHASIZED
* is too aggressive, but EMPHASIZED_DECELERATE is too soft.
*/
- private val SCALE_INTERPOLATOR =
+ @JvmField
+ val SCALE_INTERPOLATOR =
PathInterpolator(
Path().apply {
moveTo(0f, 0f)
@@ -86,25 +89,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 +134,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 +155,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 +182,7 @@
1 / workspace.scaleX,
1 / workspace.scaleY,
transformed.centerX(),
- transformed.centerY()
+ transformed.centerY(),
)
}
)
@@ -183,6 +201,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 +217,14 @@
}
fun start() {
- getAnimators().start()
+ val animators = getAnimators()
+ if (Flags.coordinateWorkspaceScale()) {
+ // Make sure to cache the current animation, so it can be properly interrupted.
+ // TODO(b/367591368): ideally these animations would be refactored to be controlled
+ // centrally so each instances doesn't need to care about this coordination.
+ Animations.setOngoingAnimation(launcher.workspace, animators)
+ Animations.setOngoingAnimation(launcher.hotseat, animators)
+ }
+ animators.start()
}
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index 3449cf2..f708f4b 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -238,7 +238,7 @@
taskViewHeight,
)
val snapshotViewSize =
- if (isPrimaryTaskSplitting) primarySnapshotViewSize else secondarySnapshotViewSize
+ if (isPrimaryTaskSplitting) secondarySnapshotViewSize else primarySnapshotViewSize
if (deviceProfile.isLeftRightSplit) {
// Center view first so scaling happens uniformly, alternatively we can move pivotX to 0
val centerThumbnailTranslationX: Float = (taskViewWidth - snapshotViewSize.x) / 2f
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java b/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java
index b618546..90569b4 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java
@@ -105,6 +105,10 @@
default Interpolator getGridSlidePrimaryInterpolator() { return LINEAR; }
default Interpolator getGridSlideSecondaryInterpolator() { return LINEAR; }
+ default Interpolator getDesktopTaskFadeInterpolator() {
+ return LINEAR;
+ }
+
// Defaults for HomeToSplit
default float getScrimFadeInStartOffset() { return 0; }
default float getScrimFadeInEndOffset() { return 0; }
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index fbeeef2..ea582c4 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;
@@ -77,6 +77,7 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.taskbar.LauncherTaskbarUIController;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.uioverrides.QuickstepLauncher;
@@ -363,7 +364,7 @@
* A version of {@link #launchSplitTasks(int, Consumer)} that launches with default split ratio.
*/
public void launchSplitTasks(@Nullable Consumer<Boolean> callback) {
- launchSplitTasks(SNAP_TO_50_50, callback);
+ launchSplitTasks(SNAP_TO_2_50_50, callback);
}
/**
@@ -371,7 +372,7 @@
* ratio and no callback.
*/
public void launchSplitTasks() {
- launchSplitTasks(SNAP_TO_50_50, null);
+ launchSplitTasks(SNAP_TO_2_50_50, null);
}
/**
@@ -565,13 +566,13 @@
switch (launchData.getSplitLaunchType()) {
case SPLIT_SINGLE_TASK_FULLSCREEN -> mSystemUiProxy.startTasks(firstTaskId,
optionsBundle, secondTaskId, null /* options2 */, initialStagePosition,
- SNAP_TO_50_50, remoteTransition, instanceId);
+ SNAP_TO_2_50_50, remoteTransition, instanceId);
case SPLIT_SINGLE_INTENT_FULLSCREEN -> mSystemUiProxy.startIntentAndTask(firstPI,
firstUserId, optionsBundle, secondTaskId, null /*options2*/,
- initialStagePosition, SNAP_TO_50_50, remoteTransition, instanceId);
+ initialStagePosition, SNAP_TO_2_50_50, remoteTransition, instanceId);
case SPLIT_SINGLE_SHORTCUT_FULLSCREEN -> mSystemUiProxy.startShortcutAndTask(
initialShortcut, optionsBundle, firstTaskId, null /* options2 */,
- initialStagePosition, SNAP_TO_50_50, remoteTransition, instanceId);
+ initialStagePosition, SNAP_TO_2_50_50, remoteTransition, instanceId);
}
}
@@ -743,6 +744,7 @@
*/
public void resetState() {
mSplitSelectDataHolder.resetState();
+ mContainer.<RecentsView>getOverviewPanel().resetDesktopTaskFromSplitSelectState();
dispatchOnSplitSelectionExit();
mRecentsAnimationRunning = false;
mLaunchingTaskView = null;
@@ -898,7 +900,7 @@
SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext())
.startRecentsActivity(
mOverviewComponentObserver.getOverviewIntent(), options,
- callbacks);
+ callbacks, false /* useSyntheticRecentsTransition */);
});
}
@@ -942,7 +944,16 @@
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
- controller.finish(true /* toRecents */, null /* onFinishComplete */,
+ controller.finish(
+ true /* toRecents */,
+ () -> {
+ LauncherTaskbarUIController controller =
+ mLauncher.getTaskbarUIController();
+ if (controller != null) {
+ controller.updateTaskbarLauncherStateGoingHome();
+ }
+
+ },
false /* sendUserLeaveHint */);
}
@Override
@@ -950,7 +961,16 @@
SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext())
.onDesktopSplitSelectAnimComplete(mTaskInfo);
}
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mLauncher.getDragLayer().removeView(floatingTaskView);
+ getSplitAnimationController()
+ .removeSplitInstructionsView(mLauncher);
+ resetState();
+ }
});
+ anim.add(getSplitAnimationController()
+ .getShowSplitInstructionsAnim(mLauncher).buildAnim());
anim.buildAnim().start();
}
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java b/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
index 4c6e4ff..744c08c 100644
--- a/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
@@ -99,7 +99,8 @@
options.setTransientLaunch();
SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext())
.startRecentsActivity(mOverviewComponentObserver.getOverviewIntent(),
- ActivityOptions.makeBasic(), callbacks);
+ ActivityOptions.makeBasic(), callbacks,
+ false /* useSyntheticRecentsTransition */);
});
});
}
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/FloatingWidgetView.java b/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java
index bdca596..b719ee5 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java
@@ -18,6 +18,7 @@
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.annotation.TargetApi;
+import android.app.TaskInfo;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.RectF;
@@ -333,11 +334,16 @@
* context's theme background color.
*/
public static int getDefaultBackgroundColor(
- Context context, RemoteAnimationTarget target) {
- return (target != null && target.taskInfo != null
- && target.taskInfo.taskDescription != null)
- ? target.taskInfo.taskDescription.getBackgroundColor()
- : Themes.getColorBackground(context);
+ Context context, @Nullable RemoteAnimationTarget target) {
+ final int fallbackColor = Themes.getColorBackground(context);
+ if (target == null) {
+ return fallbackColor;
+ }
+ final TaskInfo taskInfo = target.taskInfo;
+ if (taskInfo == null) {
+ return fallbackColor;
+ }
+ return taskInfo.taskDescription.getBackgroundColor();
}
private static void getRelativePosition(View descendant, View ancestor, RectF position) {
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 8f20cd38..bbb8cc8 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -16,7 +16,7 @@
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 android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY;
import static com.android.launcher3.LauncherState.CLEAR_ALL_BUTTON;
import static com.android.launcher3.LauncherState.NORMAL;
@@ -250,7 +250,7 @@
}
@Override
- protected boolean canLaunchFullscreenTask() {
+ public boolean canLaunchFullscreenTask() {
if (FeatureFlags.enableSplitContextually()) {
return !mSplitSelectStateController.isSplitSelectActive();
} else {
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 8b2c95d..b18af65 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -129,6 +129,7 @@
import android.widget.ListView;
import android.widget.OverScroller;
import android.widget.Toast;
+import android.window.DesktopModeFlags;
import android.window.PictureInPictureSurfaceTransaction;
import androidx.annotation.NonNull;
@@ -235,6 +236,8 @@
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
+import kotlin.Unit;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -248,8 +251,6 @@
import java.util.function.Consumer;
import java.util.stream.Collectors;
-import kotlin.Unit;
-
/**
* A list of recent tasks.
*
@@ -324,20 +325,13 @@
new FloatProperty<RecentsView>("runningTaskAttachAlpha") {
@Override
public void setValue(RecentsView recentsView, float v) {
- TaskView runningTask = recentsView.getRunningTaskView();
- if (runningTask == null) {
- return;
- }
- runningTask.setAttachAlpha(v);
+ recentsView.mRunningTaskAttachAlpha = v;
+ recentsView.applyAttachAlpha();
}
@Override
public Float get(RecentsView recentsView) {
- TaskView runningTask = recentsView.getRunningTaskView();
- if (runningTask == null) {
- return null;
- }
- return runningTask.getAttachAlpha();
+ return recentsView.mRunningTaskAttachAlpha;
}
};
@@ -463,6 +457,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.
@@ -574,6 +583,7 @@
private int mClampedScrollOffsetBound;
private float mAdjacentPageHorizontalOffset = 0;
+ private float mDesktopCarouselDetachProgress = 0;
protected float mTaskViewsSecondaryTranslation = 0;
protected float mTaskViewsPrimarySplitTranslation = 0;
protected float mTaskViewsSecondarySplitTranslation = 0;
@@ -680,13 +690,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;
@@ -798,12 +808,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
@@ -1177,7 +1181,7 @@
*
* @return {@code true} if child TaskViews can be launched when user taps on them
*/
- protected boolean canLaunchFullscreenTask() {
+ public boolean canLaunchFullscreenTask() {
return true;
}
@@ -1703,10 +1707,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;
}
@@ -1870,7 +1875,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()) {
@@ -1881,19 +1885,22 @@
// taskGroups backwards populates the thumbnail grid from least recent to most recent.
for (int i = taskGroups.size() - 1; i >= 0; i--) {
GroupTask groupTask = taskGroups.get(i);
- boolean isRemovalNeeded = stagedTaskIdToBeRemoved != INVALID_TASK_ID
+ boolean containsStagedTask = stagedTaskIdToBeRemoved != INVALID_TASK_ID
&& groupTask.containsTask(stagedTaskIdToBeRemoved);
+ boolean shouldSkipGroupTask = containsStagedTask && !groupTask.hasMultipleTasks();
- if (isRemovalNeeded && !groupTask.hasMultipleTasks()) {
- // If the task we need to remove is not part of a pair, avoiding creating the
- // TaskView.
+ if ((isSplitSelectionActive() && groupTask.taskViewType == TaskViewType.DESKTOP)
+ || shouldSkipGroupTask) {
+ // To avoid these tasks from being chosen as the app pair, the creation of a
+ // TaskView is bypassed. The staged task is already selected for the app pair,
+ // and the Desktop task should be hidden when selecting a pair.
continue;
}
// If we need to remove half of a pair of tasks, force a TaskView with Type.SINGLE
// to be a temporary container for the remaining task.
TaskView taskView = getTaskViewFromPool(
- isRemovalNeeded ? TaskViewType.SINGLE : groupTask.taskViewType);
+ containsStagedTask ? TaskViewType.SINGLE : groupTask.taskViewType);
if (taskView instanceof GroupedTaskView) {
boolean firstTaskIsLeftTopTask =
groupTask.mSplitBounds.leftTopTaskId == groupTask.task1.key.id;
@@ -1909,7 +1916,6 @@
.toList();
((DesktopTaskView) taskView).bind(nonMinimizedTasks, mOrientationState,
mTaskOverlayFactory);
- mDesktopTaskView = (DesktopTaskView) taskView;
} else {
Task task = groupTask.task1.key.id == stagedTaskIdToBeRemoved ? groupTask.task2
: groupTask.task1;
@@ -2741,9 +2747,6 @@
setEnableFreeScroll(false);
setEnableDrawingLiveTile(false);
setRunningTaskHidden(true);
- if (enableLargeDesktopWindowingTile()) {
- setNonRunningTaskCategoryHidden(true);
- }
setTaskIconScaledDown(true);
}
@@ -2867,6 +2870,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);
+ }
+ }
}
/**
@@ -2882,9 +2893,6 @@
setEnableDrawingLiveTile(mCurrentGestureEndTarget == GestureState.GestureEndTarget.RECENTS);
Log.d(TAG, "onGestureAnimationEnd - mEnableDrawingLiveTile: " + mEnableDrawingLiveTile);
setRunningTaskHidden(false);
- if (enableLargeDesktopWindowingTile()) {
- setNonRunningTaskCategoryHidden(false);
- }
animateUpTaskIconScale();
animateActionsViewIn();
@@ -3036,6 +3044,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;
@@ -3047,18 +3058,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) {
@@ -3141,7 +3145,9 @@
// Horizontal grid translation for each task
float[] gridTranslations = new float[taskCount];
- int focusedTaskIndex = Integer.MAX_VALUE;
+ TaskView lastLargeTaskView = mUtils.getLastLargeTaskView(getTaskViews());
+ int lastLargeTaskIndex =
+ (lastLargeTaskView == null) ? Integer.MAX_VALUE : indexOfChild(lastLargeTaskView);
Set<Integer> largeTasksIndices = new HashSet<>();
int focusedTaskShift = 0;
int largeTaskWidthAndSpacing = 0;
@@ -3164,8 +3170,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;
@@ -3173,9 +3183,6 @@
taskView.setGridTranslationY((mLastComputedTaskSize.height() + taskTopMargin
- taskView.getLayoutParams().height) / 2f);
- if (taskView.getTaskViewId() == mFocusedTaskViewId) {
- focusedTaskIndex = i;
- }
largeTasksIndices.add(i);
largeTaskWidthAndSpacing = taskWidthAndSpacing;
@@ -3184,8 +3191,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 {
@@ -3610,10 +3617,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) {
@@ -3643,13 +3650,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;
@@ -3801,89 +3808,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.
@@ -3991,9 +3939,11 @@
if (shouldRemoveTask) {
if (dismissedTaskView.isRunningTask()) {
finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
- () -> removeTaskInternal(dismissedTaskViewId));
+ () -> removeTaskInternal(dismissedTaskViewId,
+ dismissedTaskView instanceof DesktopTaskView));
} else {
- removeTaskInternal(dismissedTaskViewId);
+ removeTaskInternal(dismissedTaskViewId,
+ dismissedTaskView instanceof DesktopTaskView);
}
announceForAccessibility(
getResources().getString(R.string.task_view_closed));
@@ -4185,6 +4135,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
@@ -4277,16 +4311,21 @@
return lastVisibleIndex;
}
- private void removeTaskInternal(int dismissedTaskViewId) {
+ private void removeTaskInternal(int dismissedTaskViewId, boolean isDesktop) {
int[] taskIds = getTaskIdsForTaskViewId(dismissedTaskViewId);
- UI_HELPER_EXECUTOR.getHandler().post(
- () -> {
- for (int taskId : taskIds) {
- if (taskId != -1) {
- ActivityManagerWrapper.getInstance().removeTask(taskId);
- }
+ UI_HELPER_EXECUTOR.getHandler().post(() -> {
+ if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue() && isDesktop) {
+ // TODO: b/372005228 - Use the api with desktop id instead.
+ SystemUiProxy.INSTANCE.get(getContext()).removeDesktop(
+ mContainer.getDisplay().getDisplayId());
+ } else {
+ for (int taskId : taskIds) {
+ if (taskId != -1) {
+ ActivityManagerWrapper.getInstance().removeTask(taskId);
}
- });
+ }
+ }
+ });
}
protected void onDismissAnimationEnds() {
@@ -4545,6 +4584,20 @@
}
@Nullable
+ public TaskView getPreviousTaskView() {
+ return getTaskViewAt(getRunningTaskIndex() - 1);
+ }
+
+ @Nullable
+ public TaskView getLastLargeTaskView() {
+ return mUtils.getLastLargeTaskView(getTaskViews());
+ }
+
+ public int getLargeTilesCount() {
+ return mUtils.getLargeTileCount(getTaskViews());
+ }
+
+ @Nullable
public TaskView getCurrentPageTaskView() {
return getTaskViewAt(getCurrentPage());
}
@@ -4679,6 +4732,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;
@@ -4698,6 +4755,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
@@ -4715,7 +4773,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
@@ -4725,16 +4786,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();
@@ -4791,6 +4867,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;
@@ -4850,7 +4934,7 @@
}
distanceToOffscreen -= mLastComputedTaskEndPushOutDistance;
}
- return distanceToOffscreen * offsetProgress;
+ return distanceToOffscreen;
}
/**
@@ -4948,7 +5032,6 @@
mSplitSelectStateController.setAnimateCurrentTaskDismissal(
true /*animateCurrentTaskDismissal*/);
mSplitHiddenTaskViewIndex = indexOfChild(taskView);
- updateDesktopTaskVisibility(false /* visible */);
}
/**
@@ -4970,12 +5053,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);
+ }
+ }
}
}
@@ -5186,7 +5291,6 @@
mSplitHiddenTaskView.setThumbnailVisibility(VISIBLE, INVALID_TASK_ID);
mSplitHiddenTaskView = null;
}
- updateDesktopTaskVisibility(true /* visible */);
}
private void safeRemoveDragLayerView(@Nullable View viewToRemove) {
@@ -5326,6 +5430,7 @@
int taskIndex = indexOfChild(taskView);
int centerTaskIndex = getCurrentPage();
+ boolean isRunningTask = taskView.isRunningTask();
float toScale = getMaxScaleForFullScreen();
boolean showAsGrid = showAsGrid();
@@ -5344,6 +5449,16 @@
mTempPointF);
setPivotX(mTempPointF.x);
setPivotY(mTempPointF.y);
+
+ if (!isRunningTask) {
+ runActionOnRemoteHandles(
+ remoteTargetHandle -> {
+ remoteTargetHandle.getTaskViewSimulator().setPivotOverride(
+ mTempPointF);
+ remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(
+ false);
+ });
+ }
}
});
} else if (!showAsGrid) {
@@ -5460,15 +5575,13 @@
remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
.addOverviewToAppAnim(mPendingAnimation, interpolator));
mPendingAnimation.addOnFrameCallback(this::redrawLiveTile);
- if (taskView instanceof DesktopTaskView && mRemoteTargetHandles != null) {
- mPendingAnimation.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- runActionOnRemoteHandles(remoteTargetHandle ->
- remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(false));
- }
- });
- }
+ mPendingAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ runActionOnRemoteHandles(remoteTargetHandle ->
+ remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(false));
+ }
+ });
mPendingAnimation.addEndListener(isSuccess -> {
if (isSuccess) {
if (taskView instanceof GroupedTaskView && hasAllValidTaskIds(taskView.getTaskIds())
@@ -5500,6 +5613,13 @@
protected Unit onTaskLaunchAnimationEnd(boolean success) {
if (success) {
resetTaskVisuals();
+ } else {
+ // If launch animation didn't complete i.e. user dragged live tile down and then
+ // back up and returned to Overview, then we need to ensure we reset the
+ // view to draw below recents so that it can't be interacted with.
+ runActionOnRemoteHandles(remoteTargetHandle ->
+ remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true));
+ redrawLiveTile();
}
return Unit.INSTANCE;
}
@@ -5812,16 +5932,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);
@@ -5842,7 +5964,8 @@
lastView = mUtils.getLastLargeTaskView(getTaskViews());
}
} else {
- lastView = mUtils.getLastTaskViewInCarousel(mNonRunningTaskCategoryHidden,
+ lastView = mUtils.getLastTaskViewInCarousel(
+ /*nonRunningTaskCarouselHidden=*/mDesktopCarouselDetachProgress > 0,
getTaskViews(), getRunningTaskView());
}
return indexOfChild(lastView);
@@ -6558,6 +6681,26 @@
successCallback.run();
}
+ /**
+ * Move the provided task into external display and invoke {@code successCallback} if succeeded.
+ */
+ public void moveTaskToExternalDisplay(TaskContainer taskContainer, Runnable successCallback) {
+ if (!DesktopModeStatus.canEnterDesktopMode(mContext)) {
+ return;
+ }
+ switchToScreenshot(() -> finishRecentsAnimation(/* toRecents= */true, /* shouldPip= */false,
+ () -> moveTaskToDesktopInternal(taskContainer, successCallback)));
+ }
+
+ private void moveTaskToDesktopInternal(TaskContainer taskContainer, Runnable successCallback) {
+ if (mDesktopRecentsTransitionController == null) {
+ return;
+ }
+ mDesktopRecentsTransitionController.moveToExternalDisplay(taskContainer.getTask().key.id);
+ successCallback.run();
+ }
+
+
// Logs when the orientation of Overview changes. We log both real and fake orientation changes.
private void logOrientationChanged() {
// Only log when Overview is showing.
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index f513a82..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()) {
@@ -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_protolog/com/android/launcher3/util/StateManagerProtoLogProxy.java b/quickstep/src_protolog/com/android/launcher3/util/StateManagerProtoLogProxy.java
new file mode 100644
index 0000000..bc989dc
--- /dev/null
+++ b/quickstep/src_protolog/com/android/launcher3/util/StateManagerProtoLogProxy.java
@@ -0,0 +1,68 @@
+/*
+ * 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.enableStateManagerProtoLog;
+import static com.android.quickstep.util.QuickstepProtoLogGroup.LAUNCHER_STATE_MANAGER;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.protolog.ProtoLog;
+
+/**
+ * Proxy class used for StateManager ProtoLog support.
+ */
+public class StateManagerProtoLogProxy {
+
+ public static void logGoToState(
+ @NonNull Object fromState, @NonNull Object toState, @NonNull String trace) {
+ if (!enableStateManagerProtoLog()) return;
+ ProtoLog.d(LAUNCHER_STATE_MANAGER,
+ "StateManager.goToState: fromState: %s, toState: %s, partial trace:\n%s",
+ fromState,
+ toState,
+ trace);
+ }
+
+ public static void logCreateAtomicAnimation(
+ @NonNull Object fromState, @NonNull Object toState, @NonNull String trace) {
+ if (!enableStateManagerProtoLog()) return;
+ ProtoLog.d(LAUNCHER_STATE_MANAGER, "StateManager.createAtomicAnimation: "
+ + "fromState: %s, toState: %s, partial trace:\n%s",
+ fromState,
+ toState,
+ trace);
+ }
+
+ public static void logOnStateTransitionStart(@NonNull Object state) {
+ if (!enableStateManagerProtoLog()) return;
+ ProtoLog.d(LAUNCHER_STATE_MANAGER, "StateManager.onStateTransitionStart: state: %s", state);
+ }
+
+ public static void logOnStateTransitionEnd(@NonNull Object state) {
+ if (!enableStateManagerProtoLog()) return;
+ ProtoLog.d(LAUNCHER_STATE_MANAGER, "StateManager.onStateTransitionEnd: state: %s", state);
+ }
+
+ public static void logCancelAnimation(boolean animationOngoing, @NonNull String trace) {
+ if (!enableStateManagerProtoLog()) return;
+ ProtoLog.d(LAUNCHER_STATE_MANAGER,
+ "StateManager.cancelAnimation: animation ongoing: %b, partial trace:\n%s",
+ animationOngoing,
+ trace);
+ }
+}
diff --git a/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureLog.java b/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureLog.java
index 0eb6f88..23e245c 100644
--- a/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureLog.java
+++ b/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureLog.java
@@ -21,6 +21,7 @@
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;
@@ -70,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.
*
@@ -88,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);
}
@@ -250,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) {
@@ -281,76 +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() {
- return mArgs.toArray();
- }
-
@Override
public String toString() {
- return String.format(toUnformattedString(), getArgs());
- }
-
- private String toUnformattedString() {
+ 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
@@ -360,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
index 308eeb5..f43a125 100644
--- a/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java
+++ b/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java
@@ -86,25 +86,20 @@
ActiveGestureErrorDetector.GestureEvent.CANCEL_CURRENT_ANIMATION);
if (!enableActiveGestureProtoLog()) return;
ProtoLog.d(ACTIVE_GESTURE_LOG, "AbsSwipeUpHandler.cancelCurrentAnimation");
-
}
public static void logAbsSwipeUpHandlerOnTasksAppeared() {
- ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
- "AbsSwipeUpHandler.onTasksAppeared: ")
- .append("force finish recents animation complete; clearing state callback."));
+ 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.");
-
+ 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() {
@@ -113,7 +108,6 @@
/* gestureEvent= */ ON_CANCEL_RECENTS_ANIMATION);
if (!enableActiveGestureProtoLog()) return;
ProtoLog.d(ACTIVE_GESTURE_LOG, "RecentsAnimationCallbacks.onAnimationCanceled");
-
}
public static void logRecentsAnimationCallbacksOnTasksAppeared() {
@@ -121,7 +115,6 @@
ActiveGestureErrorDetector.GestureEvent.TASK_APPEARED);
if (!enableActiveGestureProtoLog()) return;
ProtoLog.d(ACTIVE_GESTURE_LOG, "RecentsAnimationCallbacks.onTasksAppeared");
-
}
public static void logStartRecentsAnimation() {
@@ -130,21 +123,18 @@
/* 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() {
@@ -152,46 +142,39 @@
/* 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(new ActiveGestureLog.CompoundString("TIS.onInputEvent: ")
- .append("Cannot process input event: user is locked"));
+ 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(
- new ActiveGestureLog.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."),
+ 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.");
+ 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(new ActiveGestureLog.CompoundString("TIS.onInputEvent: ")
- .append("Cannot process input event: ")
- .append("using 3-button nav and event is not a trackpad event"));
+ 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");
+ 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() {
@@ -226,31 +209,23 @@
}
public static void logInputConsumerBecameActive(@NonNull String consumerName) {
- ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(consumerName)
- .append(" became active"));
+ 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=")
- .append(launchedTaskId)
- .append(") finished mid transition"));
+ 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 logMotionPauseDetectorEvent(@NonNull String event) {
- ActiveGestureLog.INSTANCE.addLog(event);
- if (!enableActiveGestureProtoLog()) return;
- ProtoLog.d(ACTIVE_GESTURE_LOG, "MotionPauseDetector: %s", event);
- }
-
public static void logOnPageEndTransition(int nextPageIndex) {
- ActiveGestureLog.INSTANCE.addLog(
- "onPageEndTransition: current page index updated", 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);
@@ -258,9 +233,8 @@
public static void logQuickSwitchFromHomeFallback(int taskIndex) {
ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
- "Quick switch from home fallback case: The TaskView at index ")
- .append(taskIndex)
- .append(" is missing."),
+ "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,
@@ -270,9 +244,8 @@
public static void logQuickSwitchFromHomeFailed(int taskIndex) {
ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
- "Quick switch from home failed: TaskViews at indices ")
- .append(taskIndex)
- .append(" and 0 are missing."),
+ "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,
@@ -281,50 +254,44 @@
}
public static void logFinishRecentsAnimation(boolean toRecents) {
- ActiveGestureLog.INSTANCE.addLog(
- /* event= */ "finishRecentsAnimation",
- /* extras= */ 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 ")
- .append(target),
- /* gestureEvent= */ SET_END_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: ").append(reason));
+ "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=")
- .append(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=")
- .append(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: ")
- .append(consumerName)
- .append(". reason(s):")
- .append(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);
@@ -332,12 +299,11 @@
public static void logUpdateGestureStateRunningTask(
@NonNull String otherTaskPackage, @NonNull String runningTaskPackage) {
- ActiveGestureLog.INSTANCE.addLog(
- new ActiveGestureLog.CompoundString("Changing active task to ")
- .append(otherTaskPackage)
- .append(" because the previous task running on top of this one (")
- .append(runningTaskPackage)
- .append(") was excluded from recents"));
+ 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 "
@@ -349,15 +315,8 @@
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(")
- .append(x)
- .append(", ")
- .append(y)
- .append("): ")
- .append(actionString)
- .append(", ")
- .append(classification),
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "onMotionEvent(%d, %d): %s, %s", x, y, actionString, classification),
/* gestureEvent= */ action == ACTION_DOWN
? MOTION_DOWN
: MOTION_UP);
@@ -368,13 +327,11 @@
public static void logOnInputEventActionMove(
@NonNull String action, @NonNull String classification, int pointerCount) {
- ActiveGestureLog.INSTANCE.addLog(
- new ActiveGestureLog.CompoundString("onMotionEvent: ")
- .append(action)
- .append(",")
- .append(classification)
- .append(", pointerCount: ")
- .append(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,
@@ -383,23 +340,19 @@
public static void logOnInputEventGenericAction(
@NonNull String action, @NonNull String classification) {
- ActiveGestureLog.INSTANCE.addLog(
- new ActiveGestureLog.CompoundString("onMotionEvent: ")
- .append(action)
- .append(",")
- .append(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: ")
- .append("Navigation mode switched mid-gesture (")
- .append(startNavMode)
- .append(" -> ")
- .append(currentNavMode)
- .append("); cancelling gesture."),
+ 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,
@@ -410,84 +363,100 @@
}
public static void logUnknownInputEvent(@NonNull String event) {
- ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString("TIS.onInputEvent: ")
- .append("Cannot process input event: received unknown event ")
- .append(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(
- /* event= */ "finishRunningRecentsAnimation", 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(
- /* event= */ "RecentsAnimationCallbacks.onAnimationStart (canceled)",
- /* extras= */ 0,
+ 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(
- /* event= */ "RecentsAnimationCallbacks.onAnimationStart",
- /* extras= */ 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(")
- .append(callback)
- .append("): ")
- .append("Setting mRecentsAnimationStartPending = false"));
+ "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: ")
- .append("Setting mRecentsAnimationStartPending = ")
- .append(value));
+ "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=")
- .append(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=", taskId);
-
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "Launching side task id=%d", taskId);
}
- public static void logDynamicString(@NonNull String string) {
- logDynamicString(string, null);
+ 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) {
@@ -497,22 +466,19 @@
}
public static void logOnSettledOnEndTarget(@NonNull String endTarget) {
- ActiveGestureLog.INSTANCE.addLog(
- new ActiveGestureLog.CompoundString("onSettledOnEndTarget ")
- .append(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=")
- .append(velocityX)
- .append("dp/ms, y=")
- .append(velocityY)
- .append("dp/ms), angle=")
- .append(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,
@@ -523,12 +489,10 @@
}
public static void logUnexpectedTaskAppeared(int taskId, @NonNull String packageName) {
- ActiveGestureLog.INSTANCE.addLog(
- new ActiveGestureLog.CompoundString("Forcefully finishing recents animation: ")
- .append("Unexpected task appeared id=")
- .append(taskId)
- .append(" pkg=")
- .append(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",
diff --git a/quickstep/src_protolog/com/android/quickstep/util/QuickstepProtoLogGroup.java b/quickstep/src_protolog/com/android/quickstep/util/QuickstepProtoLogGroup.java
index d0863f8..bb02a11 100644
--- a/quickstep/src_protolog/com/android/quickstep/util/QuickstepProtoLogGroup.java
+++ b/quickstep/src_protolog/com/android/quickstep/util/QuickstepProtoLogGroup.java
@@ -26,7 +26,9 @@
/** Enums used to interface with the ProtoLog API. */
public enum QuickstepProtoLogGroup implements IProtoLogGroup {
- ACTIVE_GESTURE_LOG(true, true, false, "ActiveGestureLog");
+ ACTIVE_GESTURE_LOG(true, true, false, "ActiveGestureLog"),
+ RECENTS_WINDOW(true, true, Constants.DEBUG_RECENTS_WINDOW, "RecentsWindow"),
+ LAUNCHER_STATE_MANAGER(true, true, Constants.DEBUG_STATE_MANAGER, "LauncherStateManager");
private final boolean mEnabled;
private volatile boolean mLogToProto;
@@ -95,6 +97,9 @@
private static final class Constants {
+ private static final boolean DEBUG_RECENTS_WINDOW = false;
+ private static final boolean DEBUG_STATE_MANAGER = true; // b/279059025, b/325463989
+
private static final int LOG_START_ID =
(int) (UUID.nameUUIDFromBytes(QuickstepProtoLogGroup.class.getName().getBytes())
.getMostSignificantBits() % Integer.MAX_VALUE);
diff --git a/quickstep/src_protolog/com/android/quickstep/util/RecentsWindowProtoLogProxy.java b/quickstep/src_protolog/com/android/quickstep/util/RecentsWindowProtoLogProxy.java
new file mode 100644
index 0000000..f54ad67
--- /dev/null
+++ b/quickstep/src_protolog/com/android/quickstep/util/RecentsWindowProtoLogProxy.java
@@ -0,0 +1,60 @@
+/*
+ * 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 com.android.launcher3.Flags.enableRecentsWindowProtoLog;
+import static com.android.quickstep.util.QuickstepProtoLogGroup.RECENTS_WINDOW;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.protolog.ProtoLog;
+import com.android.internal.protolog.common.IProtoLogGroup;
+
+/**
+ * Proxy class used for Recents Window 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 Recents Window log needs to be added to the codebase, add it here under a new unique
+ * method. Or, if an existing entry needs to be modified, simply update it here.
+ */
+public class RecentsWindowProtoLogProxy {
+
+ public static void logOnStateSetStart(@NonNull String stateName) {
+ if (!enableRecentsWindowProtoLog()) return;
+ ProtoLog.d(RECENTS_WINDOW, "onStateSetStart: %s", stateName);
+ }
+
+ public static void logOnStateSetEnd(@NonNull String stateName) {
+ if (!enableRecentsWindowProtoLog()) return;
+ ProtoLog.d(RECENTS_WINDOW, "onStateSetEnd: %s", stateName);
+ }
+
+ public static void logStartRecentsWindow(boolean isShown, boolean windowViewIsNull) {
+ if (!enableRecentsWindowProtoLog()) return;
+ ProtoLog.d(RECENTS_WINDOW,
+ "Starting recents window: isShow= %b, windowViewIsNull=%b",
+ isShown,
+ windowViewIsNull);
+ }
+
+ public static void logCleanup(boolean isShown) {
+ if (!enableRecentsWindowProtoLog()) return;
+ ProtoLog.d(RECENTS_WINDOW, "Cleaning up recents window: isShow= %b", isShown);
+ }
+}
diff --git a/quickstep/testing/com/android/launcher3/taskbar/bubbles/testing/FakeBubbleViewFactory.kt b/quickstep/testing/com/android/launcher3/taskbar/bubbles/testing/FakeBubbleViewFactory.kt
index 37a07c3..2f1f0b5 100644
--- a/quickstep/testing/com/android/launcher3/taskbar/bubbles/testing/FakeBubbleViewFactory.kt
+++ b/quickstep/testing/com/android/launcher3/taskbar/bubbles/testing/FakeBubbleViewFactory.kt
@@ -54,14 +54,35 @@
val flags =
if (suppressNotification) Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION else 0
val bubbleInfo =
- BubbleInfo(key, flags, null, null, 0, context.packageName, null, null, false, true)
+ BubbleInfo(
+ key,
+ flags,
+ null,
+ null,
+ 0,
+ context.packageName,
+ null,
+ null,
+ false,
+ true,
+ null,
+ )
val bubbleView = inflater.inflate(R.layout.bubblebar_item_view, parent, false) as BubbleView
val dotPath =
PathParser.createPathFromPathData(
context.resources.getString(com.android.internal.R.string.config_icon_mask)
)
val bubble =
- BubbleBarBubble(bubbleInfo, bubbleView, badge, icon, dotColor, dotPath, "test app")
+ BubbleBarBubble(
+ bubbleInfo,
+ bubbleView,
+ badge,
+ icon,
+ dotColor,
+ dotPath,
+ "test app",
+ null,
+ )
bubbleView.setBubble(bubble)
return bubbleView
}
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 82a7625..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
@@ -98,7 +98,7 @@
// 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 = 50
+ screenshotRule.frameLimit = 500
screenshotRule.screenshotTest(
"bubbleBarView_expanded_threeBubbles",
checkView = shouldWait,
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 467a9cb..11c7fe9 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
@@ -63,12 +63,7 @@
val flyout =
BubbleBarFlyoutView(context, FakeBubbleBarFlyoutPositioner(isOnLeft = false))
flyout.showFromCollapsed(
- BubbleBarFlyoutMessage(
- senderAvatar = null,
- senderName = "sender",
- message = "message",
- isGroupChat = false,
- )
+ BubbleBarFlyoutMessage(icon = null, title = "sender", message = "message")
) {}
flyout.updateExpansionProgress(1f)
flyout
@@ -82,12 +77,7 @@
val flyout =
BubbleBarFlyoutView(context, FakeBubbleBarFlyoutPositioner(isOnLeft = true))
flyout.showFromCollapsed(
- BubbleBarFlyoutMessage(
- senderAvatar = null,
- senderName = "sender",
- message = "message",
- isGroupChat = false,
- )
+ BubbleBarFlyoutMessage(icon = null, title = "sender", message = "message")
) {}
flyout.updateExpansionProgress(1f)
flyout
@@ -102,10 +92,9 @@
BubbleBarFlyoutView(context, FakeBubbleBarFlyoutPositioner(isOnLeft = true))
flyout.showFromCollapsed(
BubbleBarFlyoutMessage(
- senderAvatar = null,
- senderName = "sender",
+ icon = null,
+ title = "sender",
message = "really, really, really, really, really long message. like really.",
- isGroupChat = false,
)
) {}
flyout.updateExpansionProgress(1f)
@@ -121,10 +110,9 @@
BubbleBarFlyoutView(context, FakeBubbleBarFlyoutPositioner(isOnLeft = false))
flyout.showFromCollapsed(
BubbleBarFlyoutMessage(
- senderAvatar = ColorDrawable(Color.RED),
- senderName = "sender",
+ icon = ColorDrawable(Color.RED),
+ title = "sender",
message = "message",
- isGroupChat = true,
)
) {}
flyout.updateExpansionProgress(1f)
@@ -140,10 +128,9 @@
BubbleBarFlyoutView(context, FakeBubbleBarFlyoutPositioner(isOnLeft = true))
flyout.showFromCollapsed(
BubbleBarFlyoutMessage(
- senderAvatar = ColorDrawable(Color.RED),
- senderName = "sender",
+ icon = ColorDrawable(Color.RED),
+ title = "sender",
message = "message",
- isGroupChat = true,
)
) {}
flyout.updateExpansionProgress(1f)
@@ -159,10 +146,9 @@
BubbleBarFlyoutView(context, FakeBubbleBarFlyoutPositioner(isOnLeft = true))
flyout.showFromCollapsed(
BubbleBarFlyoutMessage(
- senderAvatar = ColorDrawable(Color.RED),
- senderName = "sender",
+ icon = ColorDrawable(Color.RED),
+ title = "sender",
message = "really, really, really, really, really long message. like really.",
- isGroupChat = true,
)
) {}
flyout.updateExpansionProgress(1f)
@@ -170,10 +156,98 @@
}
}
- private class FakeBubbleBarFlyoutPositioner(override val isOnLeft: Boolean) :
- BubbleBarFlyoutPositioner {
+ @Test
+ fun bubbleBarFlyoutView_collapsed_onLeft() {
+ screenshotRule.screenshotTest("bubbleBarFlyoutView_collapsed_onLeft") { activity ->
+ activity.actionBar?.hide()
+ val flyout =
+ BubbleBarFlyoutView(context, FakeBubbleBarFlyoutPositioner(isOnLeft = true))
+ flyout.showFromCollapsed(
+ BubbleBarFlyoutMessage(
+ icon = ColorDrawable(Color.RED),
+ title = "sender",
+ message = "collapsed on left",
+ )
+ ) {}
+ 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(
+ icon = ColorDrawable(Color.RED),
+ title = "sender",
+ message = "collapsed on right",
+ )
+ ) {}
+ 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(
+ icon = ColorDrawable(Color.RED),
+ title = "sender",
+ message = "expanded 90% on left",
+ )
+ ) {}
+ 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(
+ icon = ColorDrawable(Color.RED),
+ title = "sender",
+ message = "expanded 80% on right",
+ )
+ ) {}
+ 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 distanceToCollapsedPosition = PointF(0f, 0f)
override val collapsedSize = 30f
+ override val collapsedColor = Color.BLUE
+ override val collapsedElevation = 1f
+ override val distanceToRevealTriangle = 10f
}
}
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/FakeTaskThumbnailViewModel.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/FakeTaskThumbnailViewModel.kt
new file mode 100644
index 0000000..ff5d8bd
--- /dev/null
+++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/FakeTaskThumbnailViewModel.kt
@@ -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.quickstep.task.thumbnail
+
+import android.graphics.Matrix
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
+import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeTaskThumbnailViewModel : TaskThumbnailViewModel {
+ override val cornerRadiusProgress = MutableStateFlow(0f)
+ override val inheritedScale = MutableStateFlow(1f)
+ override val dimProgress = MutableStateFlow(0f)
+ override val splashAlpha = MutableStateFlow(0f)
+ override val uiState = MutableStateFlow<TaskThumbnailUiState>(Uninitialized)
+
+ override fun bind(taskId: Int) {
+ // no-op
+ }
+
+ override fun getThumbnailPositionState(width: Int, height: Int, isRtl: Boolean) =
+ Matrix.IDENTITY_MATRIX
+}
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
new file mode 100644
index 0000000..75769e9
--- /dev/null
+++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.task.thumbnail
+
+import android.content.Context
+import android.graphics.Color
+import android.view.LayoutInflater
+import com.android.launcher3.R
+import com.android.quickstep.recents.di.RecentsDependencies
+import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
+import com.google.android.apps.nexuslauncher.imagecomparison.goldenpathmanager.ViewScreenshotGoldenPathManager
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.Displays
+import platform.test.screenshot.ViewScreenshotTestRule
+import platform.test.screenshot.getEmulatedDevicePathConfig
+
+/** Screenshot tests for [TaskThumbnailView]. */
+@RunWith(ParameterizedAndroidJunit4::class)
+class TaskThumbnailViewScreenshotTest(emulationSpec: DeviceEmulationSpec) {
+
+ @get:Rule
+ val screenshotRule =
+ ViewScreenshotTestRule(
+ emulationSpec,
+ ViewScreenshotGoldenPathManager(getEmulatedDevicePathConfig(emulationSpec)),
+ )
+
+ private val taskThumbnailViewModel = FakeTaskThumbnailViewModel()
+
+ @Test
+ fun taskThumbnailView_uninitialized() {
+ screenshotRule.screenshotTest("taskThumbnailView_uninitialized") { activity ->
+ activity.actionBar?.hide()
+ createTaskThumbnailView(activity)
+ }
+ }
+
+ @Test
+ fun taskThumbnailView_backgroundOnly() {
+ screenshotRule.screenshotTest("taskThumbnailView_backgroundOnly") { activity ->
+ activity.actionBar?.hide()
+ taskThumbnailViewModel.uiState.value = TaskThumbnailUiState.BackgroundOnly(Color.YELLOW)
+ createTaskThumbnailView(activity)
+ }
+ }
+
+ private fun createTaskThumbnailView(context: Context): TaskThumbnailView {
+ val di = RecentsDependencies.initialize(context)
+ val taskThumbnailView =
+ LayoutInflater.from(context).inflate(R.layout.task_thumbnail, null, false)
+ val ttvDiScopeId = di.getScope(taskThumbnailView).scopeId
+ di.provide(TaskThumbnailViewData::class.java, ttvDiScopeId) { TaskThumbnailViewData() }
+ di.provide(TaskThumbnailViewModel::class.java, ttvDiScopeId) { taskThumbnailViewModel }
+
+ return taskThumbnailView as TaskThumbnailView
+ }
+
+ companion object {
+ @Parameters(name = "{0}")
+ @JvmStatic
+ fun getTestSpecs() =
+ DeviceEmulationSpec.forDisplays(
+ Displays.Phone,
+ isDarkTheme = false,
+ isLandscape = false,
+ )
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendControllerTest.kt
index f3fff9f..59900b1 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendControllerTest.kt
@@ -41,15 +41,14 @@
@EmulatedDevices(["pixelTablet2023"])
class TaskbarAutohideSuspendControllerTest {
- private val context = TaskbarWindowSandboxContext.create(getInstrumentation().targetContext)
-
- @get:Rule(order = 0) val animatorTestRule = AnimatorTestRule(this)
- @get:Rule(order = 1)
+ @get:Rule(order = 0) val context = TaskbarWindowSandboxContext.create()
+ @get:Rule(order = 1) val animatorTestRule = AnimatorTestRule(this)
+ @get:Rule(order = 2)
val systemUiProxyRule = TestRule { base, _ ->
object : Statement() {
override fun evaluate() {
getInstrumentation().runOnMainSync {
- context.applicationContext.putObject(
+ context.putObject(
SystemUiProxy.INSTANCE,
object : SystemUiProxy(context) {
override fun notifyTaskbarAutohideSuspend(suspend: Boolean) {
@@ -62,8 +61,8 @@
}
}
}
- @get:Rule(order = 2) val taskbarModeRule = TaskbarModeRule(context)
- @get:Rule(order = 3) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
+ @get:Rule(order = 3) val taskbarModeRule = TaskbarModeRule(context)
+ @get:Rule(order = 4) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
@InjectController lateinit var autohideSuspendController: TaskbarAutohideSuspendController
@InjectController lateinit var stashController: TaskbarStashController
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarControllerTestUtil.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarControllerTestUtil.kt
index a57fb70..6e2f74a 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarControllerTestUtil.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarControllerTestUtil.kt
@@ -16,10 +16,34 @@
package com.android.launcher3.taskbar
+import android.content.Context
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.ConstantItem
+import com.android.launcher3.LauncherPrefs
+import kotlin.properties.ReadWriteProperty
+import kotlin.reflect.KProperty
object TaskbarControllerTestUtil {
inline fun runOnMainSync(crossinline runTest: () -> Unit) {
getInstrumentation().runOnMainSync { runTest() }
}
+
+ /** Returns a property to read/write the value of a [ConstantItem]. */
+ fun <T : Any> ConstantItem<T>.asProperty(context: Context): ReadWriteProperty<Any?, T> {
+ return TaskbarItemProperty(context, this)
+ }
+
+ private class TaskbarItemProperty<T : Any>(
+ private val context: Context,
+ private val item: ConstantItem<T>,
+ ) : ReadWriteProperty<Any?, T> {
+
+ override fun getValue(thisRef: Any?, property: KProperty<*>): T {
+ return LauncherPrefs.get(context).get(item)
+ }
+
+ override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
+ runOnMainSync { LauncherPrefs.get(context).put(item, value) }
+ }
+ }
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarDesktopModeControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarDesktopModeControllerTest.kt
index 72bbfc9..455b6c5 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarDesktopModeControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarDesktopModeControllerTest.kt
@@ -16,7 +16,6 @@
package com.android.launcher3.taskbar
-import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.taskbar.TaskbarBackgroundRenderer.Companion.MAX_ROUNDNESS
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule
import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext
@@ -30,12 +29,8 @@
@LauncherMultivalentJUnit.EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"])
class TaskbarDesktopModeControllerTest {
- private val context =
- TaskbarWindowSandboxContext.create(
- InstrumentationRegistry.getInstrumentation().targetContext
- )
-
- @get:Rule(order = 0) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
+ @get:Rule(order = 0) val context = TaskbarWindowSandboxContext.create()
+ @get:Rule(order = 1) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
@TaskbarUnitTestRule.InjectController
lateinit var taskbarDesktopModeController: TaskbarDesktopModeController
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarEduTooltipControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarEduTooltipControllerTest.kt
index 961d4dc..e60717b 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarEduTooltipControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarEduTooltipControllerTest.kt
@@ -14,25 +14,17 @@
* limitations under the License.
*/
-package com.android.launcher3.taskbar.test
+package com.android.launcher3.taskbar
import android.util.Log
-import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.Utilities
-import com.android.launcher3.taskbar.TOOLTIP_STEP_FEATURES
-import com.android.launcher3.taskbar.TOOLTIP_STEP_NONE
-import com.android.launcher3.taskbar.TOOLTIP_STEP_PINNING
-import com.android.launcher3.taskbar.TOOLTIP_STEP_SWIPE
-import com.android.launcher3.taskbar.TaskbarActivityContext
+import com.android.launcher3.taskbar.TaskbarControllerTestUtil.asProperty
import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync
-import com.android.launcher3.taskbar.TaskbarEduTooltipController
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.TaskbarPreferenceRule
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController
import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext
@@ -52,30 +44,11 @@
@Ignore
class TaskbarEduTooltipControllerTest {
- private val context =
- TaskbarWindowSandboxContext.create(
- InstrumentationRegistry.getInstrumentation().targetContext
- )
+ @get:Rule(order = 0) val context = TaskbarWindowSandboxContext.create()
- @get:Rule(order = 0)
- val tooltipStepPreferenceRule =
- TaskbarPreferenceRule(
- context,
- OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP.prefItem,
- )
+ @get:Rule(order = 1) val taskbarModeRule = TaskbarModeRule(context)
- @get:Rule(order = 1)
- val searchEduPreferenceRule =
- TaskbarPreferenceRule(
- context,
- OnboardingPrefs.TASKBAR_SEARCH_EDU_SEEN,
- )
-
- @get:Rule(order = 2) val taskbarPinningPreferenceRule = TaskbarPinningPreferenceRule(context)
-
- @get:Rule(order = 3) val taskbarModeRule = TaskbarModeRule(context)
-
- @get:Rule(order = 4) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
+ @get:Rule(order = 2) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
@InjectController lateinit var taskbarEduTooltipController: TaskbarEduTooltipController
@@ -84,6 +57,9 @@
private val wasInTestHarness = Utilities.isRunningInTestHarness()
+ private var tooltipStep by OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP.prefItem.asProperty(context)
+ private var searchEduSeen by OnboardingPrefs.TASKBAR_SEARCH_EDU_SEEN.asProperty(context)
+
@Before
fun setUp() {
Log.e("Taskbar", "TaskbarEduTooltipControllerTest test started")
@@ -101,7 +77,7 @@
@Test
@TaskbarMode(THREE_BUTTONS)
fun testMaybeShowSwipeEdu_whenTaskbarIsInThreeButtonMode_doesNotShowSwipeEdu() {
- tooltipStepPreferenceRule.value = TOOLTIP_STEP_SWIPE
+ tooltipStep = TOOLTIP_STEP_SWIPE
assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_SWIPE)
runOnMainSync { taskbarEduTooltipController.maybeShowSwipeEdu() }
assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_SWIPE)
@@ -111,7 +87,7 @@
@Test
@TaskbarMode(TRANSIENT)
fun testMaybeShowSwipeEdu_whenSwipeEduAlreadyShown_doesNotShowSwipeEdu() {
- tooltipStepPreferenceRule.value = TOOLTIP_STEP_FEATURES
+ tooltipStep = TOOLTIP_STEP_FEATURES
assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_FEATURES)
runOnMainSync { taskbarEduTooltipController.maybeShowSwipeEdu() }
assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_FEATURES)
@@ -121,7 +97,7 @@
@Test
@TaskbarMode(TRANSIENT)
fun testMaybeShowSwipeEdu_whenUserHasNotSeen_doesShowSwipeEdu() {
- tooltipStepPreferenceRule.value = TOOLTIP_STEP_SWIPE
+ tooltipStep = TOOLTIP_STEP_SWIPE
assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_SWIPE)
runOnMainSync { taskbarEduTooltipController.maybeShowSwipeEdu() }
assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_FEATURES)
@@ -131,7 +107,7 @@
@Test
@TaskbarMode(TRANSIENT)
fun testMaybeShowFeaturesEdu_whenFeatureEduAlreadyShown_doesNotShowFeatureEdu() {
- tooltipStepPreferenceRule.value = TOOLTIP_STEP_NONE
+ tooltipStep = TOOLTIP_STEP_NONE
assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_NONE)
runOnMainSync { taskbarEduTooltipController.maybeShowFeaturesEdu() }
assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_NONE)
@@ -141,7 +117,7 @@
@Test
@TaskbarMode(TRANSIENT)
fun testMaybeShowFeaturesEdu_whenUserHasNotSeen_doesShowFeatureEdu() {
- tooltipStepPreferenceRule.value = TOOLTIP_STEP_FEATURES
+ tooltipStep = TOOLTIP_STEP_FEATURES
assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_FEATURES)
runOnMainSync { taskbarEduTooltipController.maybeShowFeaturesEdu() }
assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_NONE)
@@ -151,7 +127,7 @@
@Test
@TaskbarMode(THREE_BUTTONS)
fun testMaybeShowPinningEdu_whenTaskbarIsInThreeButtonMode_doesNotShowPinningEdu() {
- tooltipStepPreferenceRule.value = TOOLTIP_STEP_PINNING
+ tooltipStep = TOOLTIP_STEP_PINNING
assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_PINNING)
runOnMainSync { taskbarEduTooltipController.maybeShowFeaturesEdu() }
assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_PINNING)
@@ -162,7 +138,7 @@
@TaskbarMode(TRANSIENT)
fun testMaybeShowPinningEdu_whenUserHasNotSeen_doesShowPinningEdu() {
// Test standalone pinning edu, where user has seen taskbar edu before, but not pinning edu.
- tooltipStepPreferenceRule.value = TOOLTIP_STEP_PINNING
+ tooltipStep = TOOLTIP_STEP_PINNING
assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_PINNING)
runOnMainSync { taskbarEduTooltipController.maybeShowFeaturesEdu() }
assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_NONE)
@@ -172,21 +148,21 @@
@Test
@TaskbarMode(TRANSIENT)
fun testIsBeforeTooltipFeaturesStep_whenUserHasNotSeenFeatureEdu_shouldReturnTrue() {
- tooltipStepPreferenceRule.value = TOOLTIP_STEP_SWIPE
+ tooltipStep = TOOLTIP_STEP_SWIPE
assertThat(taskbarEduTooltipController.isBeforeTooltipFeaturesStep).isTrue()
}
@Test
@TaskbarMode(TRANSIENT)
fun testIsBeforeTooltipFeaturesStep_whenUserHasSeenFeatureEdu_shouldReturnFalse() {
- tooltipStepPreferenceRule.value = TOOLTIP_STEP_NONE
+ tooltipStep = TOOLTIP_STEP_NONE
assertThat(taskbarEduTooltipController.isBeforeTooltipFeaturesStep).isFalse()
}
@Test
@TaskbarMode(TRANSIENT)
fun testHide_whenTooltipIsOpen_shouldCloseTooltip() {
- tooltipStepPreferenceRule.value = TOOLTIP_STEP_SWIPE
+ tooltipStep = TOOLTIP_STEP_SWIPE
assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_SWIPE)
assertThat(taskbarEduTooltipController.isTooltipOpen).isFalse()
runOnMainSync { taskbarEduTooltipController.maybeShowSwipeEdu() }
@@ -206,7 +182,7 @@
@Test
@TaskbarMode(PINNED)
fun testMaybeShowSearchEdu_whenTaskbarIsPinnedAndUserHasSeenSearchEdu_shouldNotShowSearchEdu() {
- searchEduPreferenceRule.value = true
+ searchEduSeen = true
assertThat(taskbarEduTooltipController.userHasSeenSearchEdu).isTrue()
runOnMainSync { taskbarEduTooltipController.hide() }
assertThat(taskbarEduTooltipController.isTooltipOpen).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 02d6218..253d921 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
@@ -39,7 +39,7 @@
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.quickstep.util.ContextualSearchInvoker;
import com.android.systemui.contextualeducation.GestureType;
import org.junit.Before;
@@ -64,7 +64,7 @@
@Mock
Handler mockHandler;
@Mock
- AssistUtils mockAssistUtils;
+ ContextualSearchInvoker mockContextualSearchInvoker;
@Mock
StatsLogManager mockStatsLogManager;
@Mock
@@ -109,7 +109,7 @@
mockSystemUiProxy,
mockContextualEduStatsManager,
mockHandler,
- mockAssistUtils);
+ mockContextualSearchInvoker);
}
@Test
@@ -166,40 +166,40 @@
@Test
public void testLongPressHome_enabled_withoutOverride() {
mNavButtonController.setAssistantLongPressEnabled(true /*assistantLongPressEnabled*/);
- when(mockAssistUtils.tryStartAssistOverride(anyInt())).thenReturn(false);
+ when(mockContextualSearchInvoker.tryStartAssistOverride(anyInt())).thenReturn(false);
mNavButtonController.onButtonLongClick(BUTTON_HOME, mockView);
- verify(mockAssistUtils, times(1)).tryStartAssistOverride(anyInt());
+ verify(mockContextualSearchInvoker, times(1)).tryStartAssistOverride(anyInt());
verify(mockSystemUiProxy, times(1)).startAssistant(any());
}
@Test
public void testLongPressHome_enabled_withOverride() {
mNavButtonController.setAssistantLongPressEnabled(true /*assistantLongPressEnabled*/);
- when(mockAssistUtils.tryStartAssistOverride(anyInt())).thenReturn(true);
+ when(mockContextualSearchInvoker.tryStartAssistOverride(anyInt())).thenReturn(true);
mNavButtonController.onButtonLongClick(BUTTON_HOME, mockView);
- verify(mockAssistUtils, times(1)).tryStartAssistOverride(anyInt());
+ verify(mockContextualSearchInvoker, times(1)).tryStartAssistOverride(anyInt());
verify(mockSystemUiProxy, never()).startAssistant(any());
}
@Test
public void testLongPressHome_disabled_withoutOverride() {
mNavButtonController.setAssistantLongPressEnabled(false /*assistantLongPressEnabled*/);
- when(mockAssistUtils.tryStartAssistOverride(anyInt())).thenReturn(false);
+ when(mockContextualSearchInvoker.tryStartAssistOverride(anyInt())).thenReturn(false);
mNavButtonController.onButtonLongClick(BUTTON_HOME, mockView);
- verify(mockAssistUtils, never()).tryStartAssistOverride(anyInt());
+ verify(mockContextualSearchInvoker, never()).tryStartAssistOverride(anyInt());
verify(mockSystemUiProxy, never()).startAssistant(any());
}
@Test
public void testLongPressHome_disabled_withOverride() {
mNavButtonController.setAssistantLongPressEnabled(false /*assistantLongPressEnabled*/);
- when(mockAssistUtils.tryStartAssistOverride(anyInt())).thenReturn(true);
+ when(mockContextualSearchInvoker.tryStartAssistOverride(anyInt())).thenReturn(true);
mNavButtonController.onButtonLongClick(BUTTON_HOME, mockView);
- verify(mockAssistUtils, never()).tryStartAssistOverride(anyInt());
+ verify(mockContextualSearchInvoker, never()).tryStartAssistOverride(anyInt());
verify(mockSystemUiProxy, never()).startAssistant(any());
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarScrimViewControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarScrimViewControllerTest.kt
index 3524961..12e84b8 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarScrimViewControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarScrimViewControllerTest.kt
@@ -42,11 +42,10 @@
@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)
+ @get:Rule(order = 0) val context = TaskbarWindowSandboxContext.create()
+ @get:Rule(order = 1) val taskbarModeRule = TaskbarModeRule(context)
+ @get:Rule(order = 2) val animatorTestRule = AnimatorTestRule(this)
+ @get:Rule(order = 3) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
@InjectController lateinit var scrimViewController: TaskbarScrimViewController
@@ -132,7 +131,7 @@
@TaskbarMode(PINNED)
fun testOnClick_scrimShown_performsSystemBack() {
var backPressed = false
- context.applicationContext.putObject(
+ context.putObject(
SystemUiProxy.INSTANCE,
object : SystemUiProxy(context) {
override fun onBackPressed() {
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt
index e736446..71f4ef4 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt
@@ -18,10 +18,13 @@
import android.animation.AnimatorTestRule
import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING
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.TaskbarControllerTestUtil.asProperty
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
@@ -35,13 +38,13 @@
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.bubbles.BubbleBarViewController
+import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController
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
@@ -52,7 +55,6 @@
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
@@ -60,21 +62,22 @@
import org.junit.runner.RunWith
@RunWith(LauncherMultivalentJUnit::class)
+@EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
@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)
+ @get:Rule(order = 0) val setFlagsRule = SetFlagsRule()
+ @get:Rule(order = 1) val context = TaskbarWindowSandboxContext.create()
+ @get:Rule(order = 2) val taskbarModeRule = TaskbarModeRule(context)
+ @get:Rule(order = 4) val animatorTestRule = AnimatorTestRule(this)
+ @get:Rule(order = 5) 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>
+ @InjectController lateinit var bubbleBarViewController: BubbleBarViewController
+ @InjectController lateinit var bubbleStashController: BubbleStashController
private val activityContext by taskbarUnitTestRule::activityContext
@@ -120,10 +123,11 @@
@Test
fun testRecreateAsTransient_timeoutStarted() {
- taskbarPinningPreferenceRule.isPinned = true
+ var isPinned by TASKBAR_PINNING.asProperty(context)
+ isPinned = true
activityContext.controllers.sharedState?.taskbarWasPinned = true
- taskbarPinningPreferenceRule.isPinned = false
+ isPinned = false
assertThat(stashController.timeoutAlarm.alarmPending()).isTrue()
}
@@ -420,60 +424,55 @@
}
@Test
- @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
@TaskbarMode(TRANSIENT)
fun testUpdateAndAnimateTransientTaskbar_unstashTaskbarWithBubbles_bubbleBarUnstashes() {
getInstrumentation().runOnMainSync {
- bubbleControllers.get().bubbleBarViewController.setHiddenForBubbles(false)
- bubbleControllers.get().bubbleStashController.stashBubbleBarImmediate()
+ bubbleBarViewController.setHiddenForBubbles(false)
+ bubbleStashController.stashBubbleBarImmediate()
stashController.updateAndAnimateTransientTaskbar(false, true)
}
- assertThat(bubbleControllers.get().bubbleStashController.isStashed).isFalse()
+ assertThat(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()
+ bubbleBarViewController.setHiddenForBubbles(false)
+ bubbleStashController.stashBubbleBarImmediate()
stashController.updateAndAnimateTransientTaskbar(false, false)
}
- assertThat(bubbleControllers.get().bubbleStashController.isStashed).isTrue()
+ assertThat(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()
+ bubbleBarViewController.setHiddenForBubbles(false)
+ bubbleStashController.showBubbleBarImmediate()
stashController.updateAndAnimateTransientTaskbar(true, true)
}
- assertThat(bubbleControllers.get().bubbleStashController.isStashed).isTrue()
+ assertThat(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()
+ bubbleBarViewController.setHiddenForBubbles(false)
+ bubbleStashController.showBubbleBarImmediate()
stashController.updateAndAnimateTransientTaskbar(true, false)
}
- assertThat(bubbleControllers.get().bubbleStashController.isStashed).isFalse()
+ assertThat(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
+ bubbleBarViewController.setHiddenForBubbles(false)
+ bubbleBarViewController.isExpanded = true
stashController.updateAndAnimateTransientTaskbar(false)
animatorTestRule.advanceTimeBy(stashController.stashDuration)
}
@@ -483,7 +482,7 @@
stashController.timeoutAlarm.finishAlarm()
animatorTestRule.advanceTimeBy(stashController.stashDuration)
}
- assertThat(bubbleControllers.get().bubbleBarViewController.isExpanded).isTrue()
+ assertThat(bubbleBarViewController.isExpanded).isTrue()
}
@Test
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewControllerTest.kt
index 4aac1dc..b13eafe 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewControllerTest.kt
@@ -17,7 +17,6 @@
package com.android.launcher3.taskbar
import android.view.View
-import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.launcher3.taskbar.TaskbarViewController.DIVIDER_VIEW_POSITION_OFFSET
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController
@@ -33,19 +32,23 @@
@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"])
/**
* Legend for the comments below:
+ * ```
* A: All Apps Button
* H: Hotseat item
* |: Divider
* R: Recent item
+ * ```
*
* The comments are formatted in two lines:
+ * ```
* // Items in taskbar, e.g. A | HHHHHH
* // Index of items relative to Hotseat: -1 -.5 012345
+ * ```
*/
class TaskbarViewControllerTest {
- private val context = TaskbarWindowSandboxContext.create(getInstrumentation().targetContext)
- @get:Rule val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
+ @get:Rule(order = 0) val context = TaskbarWindowSandboxContext.create()
+ @get:Rule(order = 1) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
@InjectController lateinit var taskbarViewController: TaskbarViewController
@@ -59,7 +62,7 @@
/* isAllAppsButton = */ true,
/* isTaskbarDividerView = */ false,
/* isDividerForRecents = */ false,
- /* recentTaskIndex = */ -1
+ /* recentTaskIndex = */ -1,
)
// [>A<] | [HHHHHH]
// -1 -.5 012345
@@ -77,7 +80,7 @@
/* isAllAppsButton = */ true,
/* isTaskbarDividerView = */ false,
/* isDividerForRecents = */ false,
- /* recentTaskIndex = */ -1
+ /* recentTaskIndex = */ -1,
)
// [HHHHHH] | [>A<]
// 012345 5.5 6
@@ -94,7 +97,7 @@
/* isAllAppsButton = */ false,
/* isTaskbarDividerView = */ true,
/* isDividerForRecents = */ false,
- /* recentTaskIndex = */ -1
+ /* recentTaskIndex = */ -1,
)
// [A] >|< [HHHHHH]
// -1 -.5 012345
@@ -112,7 +115,7 @@
/* isAllAppsButton = */ false,
/* isTaskbarDividerView = */ true,
/* isDividerForRecents = */ true,
- /* recentTaskIndex = */ -1
+ /* recentTaskIndex = */ -1,
)
// [A] [HHHHHH] >|< [RR]
// -1 012345 5.5 67
@@ -130,7 +133,7 @@
/* isAllAppsButton = */ false,
/* isTaskbarDividerView = */ true,
/* isDividerForRecents = */ false,
- /* recentTaskIndex = */ -1
+ /* recentTaskIndex = */ -1,
)
// [HHHHHH] >|< [A]
// 012345 5.5 6
@@ -148,7 +151,7 @@
/* isAllAppsButton = */ false,
/* isTaskbarDividerView = */ true,
/* isDividerForRecents = */ true,
- /* recentTaskIndex = */ -1
+ /* recentTaskIndex = */ -1,
)
// [HHHHHH][A] >|< [RR]
// 012345 6 6.5 78
@@ -167,7 +170,7 @@
/* isAllAppsButton = */ false,
/* isTaskbarDividerView = */ false,
/* isDividerForRecents = */ false,
- /* recentTaskIndex = */ recentTaskIndex
+ /* recentTaskIndex = */ recentTaskIndex,
)
// [A][HHHHHH] | [>R<R]
// -1 012345 5.5 6 7
@@ -186,7 +189,7 @@
/* isAllAppsButton = */ false,
/* isTaskbarDividerView = */ false,
/* isDividerForRecents = */ false,
- /* recentTaskIndex = */ recentTaskIndex
+ /* recentTaskIndex = */ recentTaskIndex,
)
// [A][HHHHHH] | [R>R<]
// -1 012345 5.5 6 7
@@ -205,7 +208,7 @@
/* isAllAppsButton = */ false,
/* isTaskbarDividerView = */ false,
/* isDividerForRecents = */ false,
- /* recentTaskIndex = */ recentTaskIndex
+ /* recentTaskIndex = */ recentTaskIndex,
)
// [HHHHHH][A] | [>R<R]
// 012345 6 6.5 7 8
@@ -224,7 +227,7 @@
/* isAllAppsButton = */ false,
/* isTaskbarDividerView = */ false,
/* isDividerForRecents = */ false,
- /* recentTaskIndex = */ recentTaskIndex
+ /* recentTaskIndex = */ recentTaskIndex,
)
// [HHHHHH][A] | [R>R<]
// 012345 6 6.5 7 8
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 f783e40..60c94a8 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
@@ -44,13 +44,9 @@
@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"])
class TaskbarAllAppsControllerTest {
- @get:Rule
- val taskbarUnitTestRule =
- TaskbarUnitTestRule(
- this,
- TaskbarWindowSandboxContext.create(getInstrumentation().targetContext),
- )
- @get:Rule val animatorTestRule = AnimatorTestRule(this)
+ @get:Rule(order = 0) val context = TaskbarWindowSandboxContext.create()
+ @get:Rule(order = 1) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
+ @get:Rule(order = 2) val animatorTestRule = AnimatorTestRule(this)
@InjectController lateinit var allAppsController: TaskbarAllAppsController
@InjectController lateinit var overlayController: TaskbarOverlayController
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
index 04f02e9..3c0d9c6 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewControllerTest.kt
@@ -21,6 +21,7 @@
import com.android.launcher3.appprediction.AppsDividerView
import com.android.launcher3.appprediction.AppsDividerView.DividerType
import com.android.launcher3.appprediction.PredictionRowView
+import com.android.launcher3.taskbar.TaskbarControllerTestUtil.asProperty
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
@@ -29,7 +30,6 @@
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
@@ -47,17 +47,14 @@
@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 = 0) val context = TaskbarWindowSandboxContext.create()
+ @get:Rule(order = 1) val taskbarModeRule = TaskbarModeRule(context)
@get:Rule(order = 2) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
@InjectController lateinit var overlayController: TaskbarOverlayController
@InjectController lateinit var stashController: TaskbarStashController
+ private var allAppsVisitedCount by ALL_APPS_VISITED_COUNT.prefItem.asProperty(context)
private val searchSessionController =
TestUtil.getOnUiThread { TaskbarSearchSessionController.newInstance(context) }
@@ -103,7 +100,7 @@
@Test
fun testShow_firstAllAppsVisit_hasAllAppsTextDivider() {
- allAppsVisitedPreferenceRule.value = 0
+ allAppsVisitedCount = 0
val viewController = createViewController()
getInstrumentation().runOnMainSync { viewController.show(false) }
@@ -121,7 +118,7 @@
@Test
fun testShow_maxAllAppsVisitedCount_hasLineDivider() {
- allAppsVisitedPreferenceRule.value = ALL_APPS_VISITED_COUNT.maxCount
+ allAppsVisitedCount = ALL_APPS_VISITED_COUNT.maxCount
val viewController = createViewController()
getInstrumentation().runOnMainSync { viewController.show(false) }
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleBarSwipeControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleBarSwipeControllerTest.kt
index 97847be..2e471b8 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleBarSwipeControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleBarSwipeControllerTest.kt
@@ -25,6 +25,7 @@
import com.android.launcher3.touch.OverScroll
import com.google.common.truth.Truth.assertThat
import java.util.Optional
+import kotlin.math.abs
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -36,6 +37,7 @@
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.never
+import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -44,13 +46,11 @@
companion object {
const val UNSTASH_THRESHOLD = 100
- const val EXPAND_THRESHOLD = 200
const val MAX_OVERSCROLL = 300
const val UP_BELOW_UNSTASH = -UNSTASH_THRESHOLD + 10f
const val UP_ABOVE_UNSTASH = -UNSTASH_THRESHOLD - 10f
- const val UP_ABOVE_EXPAND = -EXPAND_THRESHOLD - 10f
- const val DOWN_BELOW_UNSTASH = UNSTASH_THRESHOLD + 10f
+ const val DOWN = UNSTASH_THRESHOLD + 10f
}
private val context = ApplicationProvider.getApplicationContext<Context>()
@@ -77,9 +77,6 @@
override val unstashThreshold: Int
get() = UNSTASH_THRESHOLD
- override val expandThreshold: Int
- get() = EXPAND_THRESHOLD
-
override val maxOverscroll: Int
get() = MAX_OVERSCROLL
}
@@ -102,8 +99,12 @@
bubbleBarSwipeController.init(bubbleControllers)
}
+ // region Test that views have damped translation on swipe
+
private fun testViewsHaveDampedTranslationOnSwipe(swipe: Float) {
- val dampedTranslation = -OverScroll.dampedScroll(-swipe, MAX_OVERSCROLL).toFloat()
+ val isUp = swipe < 0
+ val damped = OverScroll.dampedScroll(abs(swipe), MAX_OVERSCROLL).toFloat()
+ val dampedTranslation = if (isUp) -damped else damped
getInstrumentation().runOnMainSync {
bubbleBarSwipeController.start()
bubbleBarSwipeController.swipeTo(swipe)
@@ -125,22 +126,14 @@
}
@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)
- }
+ // endregion
+
+ // region Test that translation on views is reset on finish
private fun testViewsTranslationResetOnFinish(swipe: Float) {
getInstrumentation().runOnMainSync {
@@ -177,22 +170,14 @@
}
@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)
- }
+ // endregion
+
+ // region Test swipe interactions on stashed bar
@Test
fun swipeUp_stashedBar_belowUnstashThreshold_doesNotShowBar() {
@@ -215,7 +200,7 @@
}
@Test
- fun swipeUp_stashedBar_aboveUnstashThreshold_unstashBubbleBar() {
+ fun swipeUp_stashedBar_overUnstashThreshold_unstashBubbleBar() {
setUpStashedBar()
getInstrumentation().runOnMainSync {
bubbleBarSwipeController.start()
@@ -235,64 +220,45 @@
}
@Test
- fun swipeUp_stashedBar_overUnstashThresholdMultipleTimes_unstashBubbleBarOnce() {
+ fun swipeUp_stashedBar_overUnstashThresholdMultipleTimes_unstashesMultipleTimes() {
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)
+ verify(bubbleStashController).stashBubbleBar()
+
+ getInstrumentation().runOnMainSync { bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH) }
+ verify(bubbleStashController, times(2)).showBubbleBar(expandBubbles = false)
}
@Test
- fun swipeUp_stashedBar_overExpandThreshold_doesNotExpandBeforeFinish() {
+ fun swipeUp_stashedBar_releaseOverUnstashThreshold_expandsBar() {
setUpStashedBar()
getInstrumentation().runOnMainSync {
bubbleBarSwipeController.start()
- bubbleBarSwipeController.swipeTo(UP_ABOVE_EXPAND)
+ bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH)
}
- verify(bubbleStashController).showBubbleBar(expandBubbles = false)
+ verify(bubbleStashController, never()).showBubbleBar(expandBubbles = true)
getInstrumentation().runOnMainSync { bubbleBarSwipeController.finish() }
verify(bubbleStashController).showBubbleBar(expandBubbles = true)
}
@Test
- fun swipeUp_stashedBar_overExpandThreshold_isSwipeGestureTrue() {
+ fun swipeUp_stashedBar_overUnstashReleaseBelowUnstash_doesNotExpandBar() {
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 swipeUp_expandedBar_swipeIgnored() {
- setUpExpandedBar()
getInstrumentation().runOnMainSync {
- bubbleBarSwipeController.start()
- bubbleBarSwipeController.swipeTo(UP_ABOVE_EXPAND)
- bubbleBarSwipeController.swipeTo(DOWN_BELOW_UNSTASH)
+ bubbleBarSwipeController.swipeTo(UP_BELOW_UNSTASH)
bubbleBarSwipeController.finish()
}
- verify(bubbleStashedHandleViewController, never()).setTranslationYForSwipe(any())
- verify(bubbleBarViewController, never()).setTranslationYForSwipe(any())
- verify(bubbleStashController, never()).showBubbleBar(any())
+ verify(bubbleStashController, never()).showBubbleBar(expandBubbles = true)
}
@Test
@@ -300,13 +266,103 @@
setUpStashedBar()
getInstrumentation().runOnMainSync {
bubbleBarSwipeController.start()
- bubbleBarSwipeController.swipeTo(DOWN_BELOW_UNSTASH)
+ bubbleBarSwipeController.swipeTo(DOWN)
}
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_UNSTASH)
+ bubbleBarSwipeController.swipeTo(DOWN)
+ 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 swipeUp_collapsedBar_doesNotShowBarDuringDrag() {
+ setUpCollapsedBar()
+ getInstrumentation().runOnMainSync {
+ bubbleBarSwipeController.start()
+ bubbleBarSwipeController.swipeTo(UP_BELOW_UNSTASH)
+ bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH)
+ }
+ verify(bubbleStashController, never()).showBubbleBar(any())
+ }
+
+ @Test
+ fun swipeUp_collapsedBar_belowUnstashThreshold_isSwipeGestureFalse() {
+ setUpCollapsedBar()
+ getInstrumentation().runOnMainSync {
+ bubbleBarSwipeController.start()
+ bubbleBarSwipeController.swipeTo(UP_BELOW_UNSTASH)
+ }
+ assertThat(bubbleBarSwipeController.isSwipeGesture()).isFalse()
+ }
+
+ @Test
+ fun swipeUp_collapsedBar_overUnstashThreshold_isSwipeGestureTrue() {
+ setUpCollapsedBar()
+ getInstrumentation().runOnMainSync {
+ bubbleBarSwipeController.start()
+ bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH)
+ }
+ assertThat(bubbleBarSwipeController.isSwipeGesture()).isTrue()
+ }
+
+ @Test
+ fun swipeUp_collapsedBar_finishOverUnstashThreshold_expandsBar() {
+ setUpCollapsedBar()
+ getInstrumentation().runOnMainSync {
+ bubbleBarSwipeController.start()
+ bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH)
+ bubbleBarSwipeController.finish()
+ }
+ verify(bubbleStashController).showBubbleBar(expandBubbles = true)
+ }
+
+ @Test
+ fun swipeUp_collapsedBar_finishBelowUnstashThreshold_doesNotExpandBar() {
+ setUpCollapsedBar()
+ getInstrumentation().runOnMainSync {
+ bubbleBarSwipeController.start()
+ bubbleBarSwipeController.swipeTo(UP_BELOW_UNSTASH)
+ bubbleBarSwipeController.finish()
+ }
+ verify(bubbleStashController, never()).showBubbleBar(any())
+ }
+
+ @Test
+ fun swipeDown_collapsedBar_swipeIgnored() {
+ setUpCollapsedBar()
+ getInstrumentation().runOnMainSync {
+ bubbleBarSwipeController.start()
+ bubbleBarSwipeController.swipeTo(DOWN)
+ }
+ verify(bubbleStashedHandleViewController, never()).setTranslationYForSwipe(any())
+ verify(bubbleBarViewController, never()).setTranslationYForSwipe(any())
+ verify(bubbleStashController, never()).showBubbleBar(any())
+ verify(bubbleStashController, never()).stashBubbleBar()
+ }
+
+ // endregion
+
private fun setUpStashedBar() {
whenever(bubbleStashController.isStashed).thenReturn(true)
whenever(bubbleStashController.isBubbleBarVisible()).thenReturn(false)
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleViewTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleViewTest.kt
index 94f9cf5..4ae8877 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleViewTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleViewTest.kt
@@ -65,10 +65,31 @@
overflowView.setOverflow(BubbleBarOverflow(overflowView), bitmap)
val bubbleInfo =
- BubbleInfo("key", 0, null, null, 0, context.packageName, null, null, false, true)
+ BubbleInfo(
+ "key",
+ 0,
+ null,
+ null,
+ 0,
+ context.packageName,
+ null,
+ null,
+ false,
+ true,
+ null,
+ )
bubbleView = inflater.inflate(R.layout.bubblebar_item_view, null, false) as BubbleView
bubble =
- BubbleBarBubble(bubbleInfo, bubbleView, bitmap, bitmap, Color.WHITE, Path(), "")
+ BubbleBarBubble(
+ bubbleInfo,
+ bubbleView,
+ bitmap,
+ bitmap,
+ Color.WHITE,
+ Path(),
+ "",
+ null,
+ )
bubbleView.setBubble(bubble)
}
InstrumentationRegistry.getInstrumentation().waitForIdleSync()
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
index 84e872d..7eee4de 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
@@ -86,7 +86,7 @@
bubbleBarView,
bubbleStashController,
onExpandedNoOp,
- animatorScheduler
+ animatorScheduler,
)
InstrumentationRegistry.getInstrumentation().runOnMainSync {
@@ -135,7 +135,7 @@
bubbleBarView,
bubbleStashController,
onExpandedNoOp,
- animatorScheduler
+ animatorScheduler,
)
InstrumentationRegistry.getInstrumentation().runOnMainSync {
@@ -183,7 +183,7 @@
bubbleBarView,
bubbleStashController,
onExpandedNoOp,
- animatorScheduler
+ animatorScheduler,
)
InstrumentationRegistry.getInstrumentation().runOnMainSync {
@@ -228,7 +228,7 @@
bubbleBarView,
bubbleStashController,
onExpandedNoOp,
- animatorScheduler
+ animatorScheduler,
)
InstrumentationRegistry.getInstrumentation().runOnMainSync {
@@ -274,7 +274,7 @@
bubbleBarView,
bubbleStashController,
onExpandedNoOp,
- animatorScheduler
+ animatorScheduler,
)
InstrumentationRegistry.getInstrumentation().runOnMainSync {
@@ -311,7 +311,7 @@
bubbleBarView,
bubbleStashController,
onExpanded,
- animatorScheduler
+ animatorScheduler,
)
InstrumentationRegistry.getInstrumentation().runOnMainSync {
@@ -355,7 +355,7 @@
bubbleBarView,
bubbleStashController,
onExpanded,
- animatorScheduler
+ animatorScheduler,
)
InstrumentationRegistry.getInstrumentation().runOnMainSync {
@@ -405,7 +405,7 @@
bubbleBarView,
bubbleStashController,
onExpanded,
- animatorScheduler
+ animatorScheduler,
)
InstrumentationRegistry.getInstrumentation().runOnMainSync {
@@ -454,7 +454,7 @@
bubbleBarView,
bubbleStashController,
onExpandedNoOp,
- animatorScheduler
+ animatorScheduler,
)
InstrumentationRegistry.getInstrumentation().runOnMainSync {
@@ -504,7 +504,7 @@
bubbleBarView,
bubbleStashController,
onExpanded,
- animatorScheduler
+ animatorScheduler,
)
InstrumentationRegistry.getInstrumentation().runOnMainSync {
@@ -538,7 +538,7 @@
bubbleBarView,
bubbleStashController,
onExpandedNoOp,
- animatorScheduler
+ animatorScheduler,
)
InstrumentationRegistry.getInstrumentation().runOnMainSync {
@@ -577,7 +577,7 @@
bubbleBarView,
bubbleStashController,
onExpanded,
- animatorScheduler
+ animatorScheduler,
)
InstrumentationRegistry.getInstrumentation().runOnMainSync {
@@ -625,7 +625,7 @@
bubbleBarView,
bubbleStashController,
onExpanded,
- animatorScheduler
+ animatorScheduler,
)
InstrumentationRegistry.getInstrumentation().runOnMainSync {
@@ -666,7 +666,7 @@
bubbleBarView,
bubbleStashController,
onExpandedNoOp,
- animatorScheduler
+ animatorScheduler,
)
InstrumentationRegistry.getInstrumentation().runOnMainSync {
@@ -713,7 +713,7 @@
bubbleBarView,
bubbleStashController,
onExpanded,
- animatorScheduler
+ animatorScheduler,
)
InstrumentationRegistry.getInstrumentation().runOnMainSync {
@@ -760,7 +760,7 @@
bubbleBarView,
bubbleStashController,
onExpanded,
- animatorScheduler
+ animatorScheduler,
)
InstrumentationRegistry.getInstrumentation().runOnMainSync {
@@ -818,7 +818,7 @@
bubbleBarView,
bubbleStashController,
onExpanded,
- animatorScheduler
+ animatorScheduler,
)
InstrumentationRegistry.getInstrumentation().runOnMainSync {
@@ -870,11 +870,32 @@
bubbleBarView.addView(overflowView)
val bubbleInfo =
- BubbleInfo("key", 0, null, null, 0, context.packageName, null, null, false, true)
+ BubbleInfo(
+ "key",
+ 0,
+ null,
+ null,
+ 0,
+ context.packageName,
+ null,
+ null,
+ false,
+ true,
+ null,
+ )
bubbleView =
inflater.inflate(R.layout.bubblebar_item_view, bubbleBarView, false) as BubbleView
bubble =
- BubbleBarBubble(bubbleInfo, bubbleView, bitmap, bitmap, Color.WHITE, Path(), "")
+ BubbleBarBubble(
+ bubbleInfo,
+ bubbleView,
+ bitmap,
+ bitmap,
+ Color.WHITE,
+ Path(),
+ "",
+ null,
+ )
bubbleView.setBubble(bubble)
bubbleBarView.addView(bubbleView)
}
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 fb12ac9..3dd7689 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,16 +17,20 @@
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
+import androidx.core.animation.AnimatorTestRule
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.R
import com.google.common.truth.Truth.assertThat
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -35,12 +39,15 @@
@RunWith(AndroidJUnit4::class)
class BubbleBarFlyoutControllerTest {
+ @get:Rule val animatorTestRule = AnimatorTestRule()
+
private lateinit var flyoutController: BubbleBarFlyoutController
private lateinit var flyoutContainer: FrameLayout
+ private lateinit var topBoundaryListener: FakeTopBoundaryListener
private val context = ApplicationProvider.getApplicationContext<Context>()
- private val flyoutMessage =
- BubbleBarFlyoutMessage(senderAvatar = null, "sender name", "message", isGroupChat = false)
+ private val flyoutMessage = BubbleBarFlyoutMessage(icon = null, "sender name", "message")
private var onLeft = true
+ private var flyoutTy = 50f
@Before
fun setUp() {
@@ -50,50 +57,126 @@
override val isOnLeft
get() = onLeft
- override val targetTy = 50f
+ override val targetTy
+ get() = flyoutTy
+
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)
+ topBoundaryListener = FakeTopBoundaryListener()
+ val flyoutScheduler = FlyoutScheduler { block -> block.invoke() }
+ flyoutController =
+ BubbleBarFlyoutController(
+ flyoutContainer,
+ positioner,
+ topBoundaryListener,
+ flyoutScheduler,
+ )
}
@Test
fun flyoutPosition_left() {
- flyoutController.setUpFlyout(flyoutMessage)
- assertThat(flyoutContainer.childCount).isEqualTo(1)
- val flyout = flyoutContainer.getChildAt(0)
- val lp = flyout.layoutParams as FrameLayout.LayoutParams
- assertThat(lp.gravity).isEqualTo(Gravity.BOTTOM or Gravity.LEFT)
- assertThat(flyout.translationY).isEqualTo(50f)
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ flyoutController.setUpFlyout(flyoutMessage)
+ assertThat(flyoutContainer.childCount).isEqualTo(1)
+ val flyout = flyoutContainer.getChildAt(0)
+ val lp = flyout.layoutParams as FrameLayout.LayoutParams
+ assertThat(lp.gravity).isEqualTo(Gravity.BOTTOM or Gravity.LEFT)
+ assertThat(flyout.translationY).isEqualTo(50f)
+ }
}
@Test
fun flyoutPosition_right() {
onLeft = false
- flyoutController.setUpFlyout(flyoutMessage)
- assertThat(flyoutContainer.childCount).isEqualTo(1)
- val flyout = flyoutContainer.getChildAt(0)
- val lp = flyout.layoutParams as FrameLayout.LayoutParams
- assertThat(lp.gravity).isEqualTo(Gravity.BOTTOM or Gravity.RIGHT)
- assertThat(flyout.translationY).isEqualTo(50f)
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ flyoutController.setUpFlyout(flyoutMessage)
+ assertThat(flyoutContainer.childCount).isEqualTo(1)
+ val flyout = flyoutContainer.getChildAt(0)
+ val lp = flyout.layoutParams as FrameLayout.LayoutParams
+ assertThat(lp.gravity).isEqualTo(Gravity.BOTTOM or Gravity.RIGHT)
+ assertThat(flyout.translationY).isEqualTo(50f)
+ }
}
@Test
fun flyoutMessage() {
- flyoutController.setUpFlyout(flyoutMessage)
- assertThat(flyoutContainer.childCount).isEqualTo(1)
- val flyout = flyoutContainer.getChildAt(0)
- val sender = flyout.findViewById<TextView>(R.id.bubble_flyout_name)
- assertThat(sender.text).isEqualTo("sender name")
- val message = flyout.findViewById<TextView>(R.id.bubble_flyout_text)
- assertThat(message.text).isEqualTo("message")
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ flyoutController.setUpFlyout(flyoutMessage)
+ assertThat(flyoutContainer.childCount).isEqualTo(1)
+ val flyout = flyoutContainer.getChildAt(0)
+ val sender = flyout.findViewById<TextView>(R.id.bubble_flyout_title)
+ assertThat(sender.text).isEqualTo("sender name")
+ val message = flyout.findViewById<TextView>(R.id.bubble_flyout_text)
+ assertThat(message.text).isEqualTo("message")
+ }
}
@Test
fun hideFlyout_removedFromContainer() {
- flyoutController.setUpFlyout(flyoutMessage)
- assertThat(flyoutContainer.childCount).isEqualTo(1)
- flyoutController.hideFlyout()
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ flyoutController.setUpFlyout(flyoutMessage)
+ assertThat(flyoutContainer.childCount).isEqualTo(1)
+ flyoutController.hideFlyout {}
+ animatorTestRule.advanceTimeBy(300)
+ }
assertThat(flyoutContainer.childCount).isEqualTo(0)
}
+
+ @Test
+ fun showFlyout_extendsTopBoundary() {
+ // set negative translation for the flyout so that it will request to extend the top
+ // boundary
+ flyoutTy = -50f
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ flyoutController.setUpFlyout(flyoutMessage)
+ assertThat(flyoutContainer.childCount).isEqualTo(1)
+ }
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ animatorTestRule.advanceTimeBy(300)
+ }
+ assertThat(topBoundaryListener.topBoundaryExtendedSpace).isEqualTo(50)
+ }
+
+ @Test
+ fun showFlyout_withinBoundary() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ flyoutController.setUpFlyout(flyoutMessage)
+ assertThat(flyoutContainer.childCount).isEqualTo(1)
+ }
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ animatorTestRule.advanceTimeBy(300)
+ }
+ assertThat(topBoundaryListener.topBoundaryExtendedSpace).isEqualTo(0)
+ }
+
+ @Test
+ fun hideFlyout_resetsTopBoundary() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ flyoutController.setUpFlyout(flyoutMessage)
+ assertThat(flyoutContainer.childCount).isEqualTo(1)
+ flyoutController.hideFlyout {}
+ animatorTestRule.advanceTimeBy(300)
+ }
+ assertThat(topBoundaryListener.topBoundaryReset).isTrue()
+ }
+
+ class FakeTopBoundaryListener : BubbleBarFlyoutController.TopBoundaryListener {
+
+ var topBoundaryExtendedSpace = 0
+ var topBoundaryReset = false
+
+ override fun extendTopBoundary(space: Int) {
+ topBoundaryExtendedSpace = space
+ }
+
+ override fun resetTopBoundary() {
+ topBoundaryReset = true
+ }
+ }
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashControllerTest.kt
index 4106a2c..5dc78a9 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashControllerTest.kt
@@ -27,6 +27,7 @@
import com.android.launcher3.taskbar.TaskbarInsetsController
import com.android.launcher3.taskbar.bubbles.BubbleBarView
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController
+import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.BubbleLauncherState
import com.android.launcher3.util.MultiValueAlpha
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -85,12 +86,12 @@
fun setBubblesShowingOnHomeUpdatedToFalse_barPositionYUpdated_controllersNotified() {
// Given bubble bar is on home and has bubbles
whenever(bubbleBarViewController.hasBubbles()).thenReturn(false)
- persistentTaskBarStashController.isBubblesShowingOnHome = true
+ persistentTaskBarStashController.launcherState = BubbleLauncherState.HOME
whenever(bubbleBarViewController.hasBubbles()).thenReturn(true)
// When switch out of the home screen
getInstrumentation().runOnMainSync {
- persistentTaskBarStashController.isBubblesShowingOnHome = false
+ persistentTaskBarStashController.launcherState = BubbleLauncherState.IN_APP
}
// Then translation Y is animating and the bubble bar controller is notified
@@ -110,7 +111,7 @@
// When switch to home screen
getInstrumentation().runOnMainSync {
- persistentTaskBarStashController.isBubblesShowingOnHome = true
+ persistentTaskBarStashController.launcherState = BubbleLauncherState.HOME
}
// Then translation Y is animating and the bubble bar controller is notified
@@ -127,11 +128,11 @@
@Test
fun setBubblesShowingOnOverviewUpdatedToFalse_controllersNotified() {
// Given bubble bar is on overview
- persistentTaskBarStashController.isBubblesShowingOnOverview = true
+ persistentTaskBarStashController.launcherState = BubbleLauncherState.OVERVIEW
clearInvocations(bubbleBarViewController)
// When switch out of the overview screen
- persistentTaskBarStashController.isBubblesShowingOnOverview = false
+ persistentTaskBarStashController.launcherState = BubbleLauncherState.IN_APP
// Then bubble bar controller is notified
verify(bubbleBarViewController).onBubbleBarConfigurationChanged(/* animate= */ true)
@@ -140,7 +141,7 @@
@Test
fun setBubblesShowingOnOverviewUpdatedToTrue_controllersNotified() {
// When switch to the overview screen
- persistentTaskBarStashController.isBubblesShowingOnOverview = true
+ persistentTaskBarStashController.launcherState = BubbleLauncherState.OVERVIEW
// Then bubble bar controller is notified
verify(bubbleBarViewController).onBubbleBarConfigurationChanged(/* animate= */ true)
@@ -150,7 +151,7 @@
fun isSysuiLockedSwitchedToFalseForOverview_unlockAnimationIsShown() {
// Given screen is locked and bubble bar has bubbles
persistentTaskBarStashController.isSysuiLocked = true
- persistentTaskBarStashController.isBubblesShowingOnOverview = true
+ persistentTaskBarStashController.launcherState = BubbleLauncherState.OVERVIEW
whenever(bubbleBarViewController.hasBubbles()).thenReturn(true)
// When switch to the overview screen
@@ -211,14 +212,14 @@
fun bubbleBarTranslationYForTaskbar() {
// Give bubble bar is on home
whenever(bubbleBarViewController.hasBubbles()).thenReturn(false)
- persistentTaskBarStashController.isBubblesShowingOnHome = true
+ persistentTaskBarStashController.launcherState = BubbleLauncherState.HOME
// Then bubbleBarTranslationY would be HOTSEAT_TRANSLATION_Y
assertThat(persistentTaskBarStashController.bubbleBarTranslationY)
.isEqualTo(HOTSEAT_TRANSLATION_Y)
// Give bubble bar is not on home
- persistentTaskBarStashController.isBubblesShowingOnHome = false
+ persistentTaskBarStashController.launcherState = BubbleLauncherState.IN_APP
// Then bubbleBarTranslationY would be TASK_BAR_TRANSLATION_Y
assertThat(persistentTaskBarStashController.bubbleBarTranslationY)
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt
index d4a3b3a..8b277e7 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt
@@ -29,13 +29,16 @@
import com.android.launcher3.anim.AnimatedFloat
import com.android.launcher3.taskbar.StashedHandleView
import com.android.launcher3.taskbar.TaskbarInsetsController
+import com.android.launcher3.taskbar.TaskbarStashController
import com.android.launcher3.taskbar.bubbles.BubbleBarView
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController
import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController
import com.android.launcher3.taskbar.bubbles.BubbleView
+import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.BubbleLauncherState
import com.android.launcher3.util.MultiValueAlpha
import com.android.wm.shell.shared.animation.PhysicsAnimator
import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
+import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
@@ -46,6 +49,7 @@
import org.mockito.junit.MockitoRule
import org.mockito.kotlin.any
import org.mockito.kotlin.atLeastOnce
+import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -59,7 +63,7 @@
const val BUBBLE_BAR_WIDTH = 200
const val BUBBLE_BAR_HEIGHT = 100
const val HOTSEAT_TRANSLATION_Y = -45f
- const val TASK_BAR_TRANSLATION_Y = -TASKBAR_BOTTOM_SPACE
+ const val TASK_BAR_TRANSLATION_Y = -TASKBAR_BOTTOM_SPACE.toFloat()
const val HANDLE_VIEW_WIDTH = 150
const val HANDLE_VIEW_HEIGHT = 4
const val BUBBLE_BAR_STASHED_TRANSLATION_Y = -4.5f
@@ -119,7 +123,7 @@
// When switch out of the home screen
getInstrumentation().runOnMainSync {
- mTransientBubbleStashController.isBubblesShowingOnHome = true
+ mTransientBubbleStashController.launcherState = BubbleLauncherState.HOME
}
// Then BubbleBarView is animating, BubbleBarViewController controller is notified
@@ -137,12 +141,12 @@
@Test
fun setBubblesShowingOnOverviewUpdatedToTrue_barPositionYUpdated_controllersNotified() {
- // Given bubble bar is on home and has bubbles
+ // Given bubble bar is on overview and has bubbles
whenever(bubbleBarViewController.hasBubbles()).thenReturn(true)
// When switch out of the home screen
getInstrumentation().runOnMainSync {
- mTransientBubbleStashController.isBubblesShowingOnOverview = true
+ mTransientBubbleStashController.launcherState = BubbleLauncherState.OVERVIEW
}
// Then BubbleBarView is animating, BubbleBarViewController controller is notified
@@ -159,6 +163,27 @@
}
@Test
+ fun setBubblesShowingOnOverviewUpdatedToTrue_unstashes() {
+ // Given bubble bar is stashed with bubbles
+ whenever(bubbleBarViewController.hasBubbles()).thenReturn(true)
+
+ getInstrumentation().runOnMainSync {
+ mTransientBubbleStashController.updateStashedAndExpandedState(
+ stash = true,
+ expand = false,
+ )
+ }
+ assertThat(mTransientBubbleStashController.isStashed).isTrue()
+
+ // Move to overview
+ getInstrumentation().runOnMainSync {
+ mTransientBubbleStashController.launcherState = BubbleLauncherState.OVERVIEW
+ }
+ // No longer stashed in overview
+ assertThat(mTransientBubbleStashController.isStashed).isFalse()
+ }
+
+ @Test
fun updateStashedAndExpandedState_stashAndCollapse_bubbleBarHidden_stashedHandleShown() {
// Given bubble bar has bubbles and not stashed
mTransientBubbleStashController.isStashed = false
@@ -196,11 +221,136 @@
}
@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 updateStashedAndExpandedState_stash_updateBarVisibilityAfterAnimation() {
+ // 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,
+ )
+ }
+
+ // Hides bubble bar only after animation completes
+ verify(bubbleBarViewController, never()).setHiddenForStashed(true)
+ advanceTimeBy(BubbleStashController.BAR_STASH_DURATION)
+ verify(bubbleBarViewController).setHiddenForStashed(true)
+ }
+
+ @Test
+ fun updateStashedAndExpandedState_unstash_updateBarVisibilityBeforeAnimation() {
+ // 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,
+ )
+ }
+
+ // Shows bubble bar immediately
+ verify(bubbleBarViewController).setHiddenForStashed(false)
+ }
+
+ @Test
fun isSysuiLockedSwitchedToFalseForOverview_unlockAnimationIsShown() {
// Given screen is locked and bubble bar has bubbles
getInstrumentation().runOnMainSync {
mTransientBubbleStashController.isSysuiLocked = true
- mTransientBubbleStashController.isBubblesShowingOnOverview = true
+ mTransientBubbleStashController.launcherState = BubbleLauncherState.OVERVIEW
whenever(bubbleBarViewController.hasBubbles()).thenReturn(true)
}
advanceTimeBy(BubbleStashController.BAR_TRANSLATION_DURATION)
@@ -247,6 +397,8 @@
assertThat(stashedHandleView.alpha).isEqualTo(0)
// Insets controller is notified
verify(taskbarInsetsController).onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
+ // Bubble bar visibility updated
+ verify(bubbleBarViewController).setHiddenForStashed(false)
}
@Test
@@ -264,6 +416,8 @@
assertThat(stashedHandleView.translationY).isEqualTo(0)
// Insets controller is notified
verify(taskbarInsetsController).onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
+ // Bubble bar visibility updated
+ verify(bubbleBarViewController).setHiddenForStashed(true)
}
@Test
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt
index 4fa821d..1113129 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt
@@ -18,7 +18,6 @@
import android.app.ActivityManager.RunningTaskInfo
import android.view.MotionEvent
-import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.launcher3.AbstractFloatingView
import com.android.launcher3.AbstractFloatingView.TYPE_OPTIONS_POPUP
import com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_ALL_APPS
@@ -42,12 +41,8 @@
@EmulatedDevices(["pixelFoldable2023"])
class TaskbarOverlayControllerTest {
- @get:Rule
- val taskbarUnitTestRule =
- TaskbarUnitTestRule(
- this,
- TaskbarWindowSandboxContext.create(getInstrumentation().targetContext),
- )
+ @get:Rule(order = 0) val context = TaskbarWindowSandboxContext.create()
+ @get:Rule(order = 1) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
@InjectController lateinit var overlayController: TaskbarOverlayController
private val taskbarContext: TaskbarActivityContext
@@ -223,9 +218,8 @@
}
private class TestOverlayView
- private constructor(
- private val overlayContext: TaskbarOverlayContext,
- ) : AbstractFloatingView(overlayContext, null) {
+ private constructor(private val overlayContext: TaskbarOverlayContext) :
+ AbstractFloatingView(overlayContext, null) {
var type = TYPE_OPTIONS_POPUP
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt
index c48947e..74b154a 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt
@@ -61,7 +61,7 @@
val mode = taskbarMode.mode
getInstrumentation().runOnMainSync {
- context.applicationContext.putObject(
+ context.putObject(
DisplayController.INSTANCE,
object : DisplayController(context) {
override fun getInfo(): Info {
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRuleTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRuleTest.kt
index f7e4576..0dd1324 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRuleTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRuleTest.kt
@@ -16,7 +16,6 @@
package com.android.launcher3.taskbar.rules
-import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.PINNED
import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.THREE_BUTTONS
@@ -35,9 +34,8 @@
@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"])
class TaskbarModeRuleTest {
- private val context = TaskbarWindowSandboxContext.create(getInstrumentation().targetContext)
-
- @get:Rule val taskbarModeRule = TaskbarModeRule(context)
+ @get:Rule(order = 0) val context = TaskbarWindowSandboxContext.create()
+ @get:Rule(order = 1) val taskbarModeRule = TaskbarModeRule(context)
@Test
@TaskbarMode(TRANSIENT)
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPinningPreferenceRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPinningPreferenceRule.kt
deleted file mode 100644
index d417790..0000000
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPinningPreferenceRule.kt
+++ /dev/null
@@ -1,65 +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.taskbar.rules
-
-import android.platform.test.flag.junit.FlagsParameterization
-import android.platform.test.flag.junit.SetFlagsRule
-import com.android.launcher3.Flags.FLAG_ENABLE_TASKBAR_PINNING
-import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING
-import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING_IN_DESKTOP_MODE
-import com.android.launcher3.util.DisplayController
-import org.junit.rules.RuleChain
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
-
-/**
- * Rule that allows modifying the Taskbar pinned preferences.
- *
- * The original preference values are restored on teardown.
- *
- * If this rule is being used with [TaskbarUnitTestRule], make sure this rule is applied first.
- *
- * This rule is overkill if a test does not need to change the mode during Taskbar's lifecycle. If
- * the mode is static, use [TaskbarModeRule] instead, which forces the mode. A test can class can
- * declare both this rule and [TaskbarModeRule] but using both for a test method is unsupported.
- */
-class TaskbarPinningPreferenceRule(context: TaskbarWindowSandboxContext) : TestRule {
-
- private val setFlagsRule =
- SetFlagsRule(FlagsParameterization(mapOf(FLAG_ENABLE_TASKBAR_PINNING to true)))
- private val pinningRule = TaskbarPreferenceRule(context, TASKBAR_PINNING)
- private val desktopPinningRule = TaskbarPreferenceRule(context, TASKBAR_PINNING_IN_DESKTOP_MODE)
- private val ruleChain =
- RuleChain.outerRule(setFlagsRule).around(pinningRule).around(desktopPinningRule)
-
- var isPinned by pinningRule::value
- var isPinnedInDesktopMode by desktopPinningRule::value
-
- override fun apply(base: Statement, description: Description): Statement {
- return object : Statement() {
- override fun evaluate() {
- DisplayController.enableTaskbarModePreferenceForTests(true)
- try {
- ruleChain.apply(base, description).evaluate()
- } finally {
- DisplayController.enableTaskbarModePreferenceForTests(false)
- }
- }
- }
- }
-}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPinningPreferenceRuleTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPinningPreferenceRuleTest.kt
deleted file mode 100644
index a515405..0000000
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPinningPreferenceRuleTest.kt
+++ /dev/null
@@ -1,114 +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.taskbar.rules
-
-import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
-import com.android.launcher3.util.DisplayController
-import com.android.launcher3.util.LauncherMultivalentJUnit
-import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
-import com.android.launcher3.util.window.WindowManagerProxy
-import com.google.android.apps.nexuslauncher.deviceemulator.TestWindowManagerProxy
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.Description
-import org.junit.runner.RunWith
-import org.junit.runners.model.Statement
-
-@RunWith(LauncherMultivalentJUnit::class)
-@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"])
-class TaskbarPinningPreferenceRuleTest {
- private val context = TaskbarWindowSandboxContext.create(getInstrumentation().targetContext)
-
- private val preferenceRule = TaskbarPinningPreferenceRule(context)
-
- @Test
- fun testEnablePinning_verifyDisplayController() {
- onSetup {
- preferenceRule.isPinned = true
- preferenceRule.isPinnedInDesktopMode = false
- assertThat(DisplayController.isPinnedTaskbar(context)).isTrue()
- }
- }
-
- @Test
- fun testDisablePinning_verifyDisplayController() {
- onSetup {
- preferenceRule.isPinned = false
- preferenceRule.isPinnedInDesktopMode = false
- assertThat(DisplayController.isPinnedTaskbar(context)).isFalse()
- }
- }
-
- @Test
- fun testEnableDesktopPinning_verifyDisplayController() {
- context.applicationContext.putObject(
- WindowManagerProxy.INSTANCE,
- TestWindowManagerProxy(context).apply { isInDesktopMode = true },
- )
-
- onSetup {
- preferenceRule.isPinned = false
- preferenceRule.isPinnedInDesktopMode = true
- assertThat(DisplayController.isPinnedTaskbar(context)).isTrue()
- }
- }
-
- @Test
- fun testDisableDesktopPinning_verifyDisplayController() {
- context.applicationContext.putObject(
- WindowManagerProxy.INSTANCE,
- TestWindowManagerProxy(context).apply { isInDesktopMode = true },
- )
-
- onSetup {
- preferenceRule.isPinned = false
- preferenceRule.isPinnedInDesktopMode = false
- assertThat(DisplayController.isPinnedTaskbar(context)).isFalse()
- }
- }
-
- @Test
- fun testTearDown_afterTogglingPinnedPreference_preferenceReset() {
- val wasPinned = preferenceRule.isPinned
- onSetup { preferenceRule.isPinned = !preferenceRule.isPinned }
- assertThat(preferenceRule.isPinned).isEqualTo(wasPinned)
- }
-
- @Test
- fun testTearDown_afterTogglingDesktopPreference_preferenceReset() {
- val wasPinnedInDesktopMode = preferenceRule.isPinnedInDesktopMode
- onSetup { preferenceRule.isPinnedInDesktopMode = !preferenceRule.isPinnedInDesktopMode }
- assertThat(preferenceRule.isPinnedInDesktopMode).isEqualTo(wasPinnedInDesktopMode)
- }
-
- /** Executes [runTest] after the [preferenceRule] setup phase completes. */
- private fun onSetup(runTest: () -> Unit) {
- preferenceRule
- .apply(
- object : Statement() {
- override fun evaluate() = runTest()
- },
- DESCRIPTION,
- )
- .evaluate()
- }
-
- private companion object {
- private val DESCRIPTION =
- Description.createSuiteDescription(TaskbarPinningPreferenceRule::class.java)
- }
-}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPreferenceRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPreferenceRule.kt
deleted file mode 100644
index a76a77d..0000000
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPreferenceRule.kt
+++ /dev/null
@@ -1,54 +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.taskbar.rules
-
-import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
-import com.android.launcher3.ConstantItem
-import com.android.launcher3.LauncherPrefs
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
-
-/**
- * Rule for modifying a Taskbar preference.
- *
- * The original preference value is restored on teardown.
- */
-class TaskbarPreferenceRule<T : Any>(
- context: TaskbarWindowSandboxContext,
- private val constantItem: ConstantItem<T>
-) : TestRule {
-
- private val prefs = LauncherPrefs.get(context)
-
- var value: T
- get() = prefs.get(constantItem)
- set(value) = getInstrumentation().runOnMainSync { prefs.put(constantItem, value) }
-
- override fun apply(base: Statement, description: Description): Statement {
- return object : Statement() {
- override fun evaluate() {
- val originalValue = value
- try {
- base.evaluate()
- } finally {
- value = originalValue
- }
- }
- }
- }
-}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPreferenceRuleTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPreferenceRuleTest.kt
deleted file mode 100644
index 46817d2..0000000
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPreferenceRuleTest.kt
+++ /dev/null
@@ -1,67 +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.taskbar.rules
-
-import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
-import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING
-import com.android.launcher3.util.LauncherMultivalentJUnit
-import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.Description
-import org.junit.runner.RunWith
-import org.junit.runners.model.Statement
-
-@RunWith(LauncherMultivalentJUnit::class)
-@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"])
-class TaskbarPreferenceRuleTest {
-
- private val context = TaskbarWindowSandboxContext.create(getInstrumentation().targetContext)
- private val preferenceRule = TaskbarPreferenceRule(context, TASKBAR_PINNING)
-
- @Test
- fun testSetup_toggleBoolean_updatesPreferences() {
- val originalValue = preferenceRule.value
- onSetup {
- preferenceRule.value = !preferenceRule.value
- assertThat(preferenceRule.value).isNotEqualTo(originalValue)
- }
- }
-
- @Test
- fun testTeardown_afterTogglingBoolean_preferenceReset() {
- val originalValue = preferenceRule.value
- onSetup { preferenceRule.value = !preferenceRule.value }
- assertThat(preferenceRule.value).isEqualTo(originalValue)
- }
-
- private fun onSetup(runTest: () -> Unit) {
- preferenceRule
- .apply(
- object : Statement() {
- override fun evaluate() = runTest()
- },
- DESCRIPTION,
- )
- .evaluate()
- }
-
- private companion object {
- private val DESCRIPTION =
- Description.createSuiteDescription(TaskbarPreferenceRule::class.java)
- }
-}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt
index cb5e464..b0d706f 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt
@@ -28,9 +28,11 @@
import com.android.launcher3.LauncherAppState
import com.android.launcher3.statehandlers.DesktopVisibilityController
import com.android.launcher3.taskbar.TaskbarActivityContext
+import com.android.launcher3.taskbar.TaskbarControllers
import com.android.launcher3.taskbar.TaskbarManager
import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks
import com.android.launcher3.taskbar.TaskbarViewController
+import com.android.launcher3.taskbar.bubbles.BubbleControllers
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController
import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
import com.android.launcher3.util.LauncherMultivalentJUnit.Companion.isRunningInRobolectric
@@ -38,6 +40,9 @@
import com.android.quickstep.AllAppsActionManager
import com.android.quickstep.TouchInteractionService
import com.android.quickstep.TouchInteractionService.TISBinder
+import java.lang.reflect.Field
+import java.lang.reflect.ParameterizedType
+import java.util.Optional
import org.junit.Assume.assumeTrue
import org.junit.rules.RuleChain
import org.junit.rules.TestRule
@@ -180,19 +185,38 @@
fun recreateTaskbar() = instrumentation.runOnMainSync { taskbarManager.recreateTaskbar() }
private fun injectControllers() {
- val controllers = activityContext.controllers
- val controllerFieldsByType = controllers.javaClass.fields.associateBy { it.type }
+ val bubbleControllerTypes =
+ BubbleControllers::class.java.fields.map { f ->
+ if (f.type == Optional::class.java) {
+ (f.genericType as ParameterizedType).actualTypeArguments[0] as Class<*>
+ } else {
+ f.type
+ }
+ }
testInstance.javaClass.fields
.filter { it.isAnnotationPresent(InjectController::class.java) }
.forEach {
- it.set(
- testInstance,
- controllerFieldsByType[it.type]?.get(controllers)
- ?: throw NoSuchElementException("Failed to find controller for ${it.type}"),
- )
+ val controllers: Any =
+ if (it.type in bubbleControllerTypes) {
+ activityContext.controllers.bubbleControllers.orElseThrow {
+ NoSuchElementException("Bubble controllers are not initialized")
+ }
+ } else {
+ activityContext.controllers
+ }
+ injectController(it, testInstance, controllers)
}
}
+ private fun injectController(field: Field, testInstance: Any, controllers: Any) {
+ val controllerFieldsByType = controllers.javaClass.fields.associateBy { it.type }
+ field.set(
+ testInstance,
+ controllerFieldsByType[field.type]?.get(controllers)
+ ?: throw NoSuchElementException("Failed to find controller for ${field.type}"),
+ )
+ }
+
/**
* Annotates test controller fields to inject the corresponding controllers from the current
* [TaskbarControllers] instance.
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRuleTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRuleTest.kt
index 5d4fdc5..7daa142 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRuleTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRuleTest.kt
@@ -16,18 +16,23 @@
package com.android.launcher3.taskbar.rules
-import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.launcher3.taskbar.TaskbarKeyguardController
import com.android.launcher3.taskbar.TaskbarManager
import com.android.launcher3.taskbar.TaskbarStashController
+import com.android.launcher3.taskbar.bubbles.BubbleBarController
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.NavBarKidsMode
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.UserSetupMode
import com.android.launcher3.util.LauncherMultivalentJUnit
import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
+import com.android.wm.shell.Flags
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertThrows
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.Description
import org.junit.runner.RunWith
@@ -37,7 +42,8 @@
@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"])
class TaskbarUnitTestRuleTest {
- private val context = TaskbarWindowSandboxContext.create(getInstrumentation().targetContext)
+ @get:Rule(order = 0) val context = TaskbarWindowSandboxContext.create()
+ @get:Rule(order = 1) val setFlagsRule = SetFlagsRule()
@Test
fun testSetup_taskbarInitialized() {
@@ -127,6 +133,44 @@
}
}
+ @EnableFlags(Flags.FLAG_ENABLE_BUBBLE_BAR)
+ @Test
+ fun testInjectBubbleController_bubbleFlagOn_isInjected() {
+ val testClass =
+ object {
+ @InjectController lateinit var controller: BubbleBarController
+ val isInjected: Boolean
+ get() = ::controller.isInitialized
+ }
+
+ TaskbarUnitTestRule(testClass, context).apply(EMPTY_STATEMENT, DESCRIPTION).evaluate()
+
+ onSetup(TaskbarUnitTestRule(testClass, context)) {
+ assertThat(testClass.isInjected).isTrue()
+ }
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_BUBBLE_BAR)
+ @Test
+ fun testInjectBubbleController_bubbleFlagOff_exceptionThrown() {
+ val testClass =
+ object {
+ @InjectController lateinit var controller: BubbleBarController
+ }
+
+ // We cannot use #assertThrows because we also catch an assumption violated exception when
+ // running #evaluate on devices that do not support Taskbar.
+ val result =
+ try {
+ TaskbarUnitTestRule(testClass, context)
+ .apply(EMPTY_STATEMENT, DESCRIPTION)
+ .evaluate()
+ } catch (e: NoSuchElementException) {
+ e
+ }
+ assertThat(result).isInstanceOf(NoSuchElementException::class.java)
+ }
+
@Test
fun testUserSetupMode_default_isComplete() {
onSetup { assertThat(activityContext.isUserSetupComplete).isTrue() }
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
index ee21df8..2d3bfd6 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
@@ -18,44 +18,65 @@
import android.content.Context
import android.content.ContextWrapper
-import android.os.Bundle
-import android.view.Display
-import com.android.launcher3.util.MainThreadInitializedObject
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
+import android.hardware.display.DisplayManager
+import android.hardware.display.VirtualDisplay
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.test.core.app.ApplicationProvider
+import com.android.launcher3.FakeLauncherPrefs
+import com.android.launcher3.LauncherPrefs
+import com.android.launcher3.util.MainThreadInitializedObject.ObjectSandbox
+import com.android.launcher3.util.SandboxApplication
+import org.junit.rules.ExternalResource
+import org.junit.rules.RuleChain
+import org.junit.rules.TestRule
/**
- * Sandbox wrapper where [createWindowContext] provides contexts that are still sandboxed within
- * [application].
+ * [SandboxApplication] for running Taskbar tests.
*
- * Taskbar can create window contexts, which need to operate under the same sandbox application, but
- * [Context.getApplicationContext] by default returns the actual application. For this reason,
- * [SandboxContext] overrides [getApplicationContext] to return itself, which prevents leaving the
- * sandbox. [SandboxContext] and the real application have different sets of
- * [MainThreadInitializedObject] instances, so overriding the application prevents the latter set
- * from leaking into the sandbox. Similarly, this implementation overrides [getApplicationContext]
- * to return the original sandboxed [application], and it wraps created windowed contexts to
- * propagate this [application].
+ * Tests need to run on a [VirtualDisplay] to avoid conflicting with Launcher's Taskbar on the
+ * [DEFAULT_DISPLAY] (i.e. test is executing on a device).
*/
class TaskbarWindowSandboxContext
-private constructor(private val application: SandboxContext, base: Context) : ContextWrapper(base) {
+private constructor(base: SandboxApplication, val virtualDisplay: VirtualDisplay) :
+ ContextWrapper(base),
+ ObjectSandbox by base,
+ TestRule by RuleChain.outerRule(virtualDisplayRule(virtualDisplay)).around(base) {
- override fun createWindowContext(type: Int, options: Bundle?): Context {
- return TaskbarWindowSandboxContext(application, super.createWindowContext(type, options))
+ init {
+ putObject(LauncherPrefs.INSTANCE, FakeLauncherPrefs(this))
}
- override fun createWindowContext(display: Display, type: Int, options: Bundle?): Context {
- return TaskbarWindowSandboxContext(
- application,
- super.createWindowContext(display, type, options),
- )
- }
-
- override fun getApplicationContext(): SandboxContext = application
-
companion object {
- /** Creates a [TaskbarWindowSandboxContext] to sandbox [base] for Taskbar tests. */
- fun create(base: Context): TaskbarWindowSandboxContext {
- return SandboxContext(base).let { TaskbarWindowSandboxContext(it, it) }
+ private const val VIRTUAL_DISPLAY_NAME = "TaskbarSandboxDisplay"
+
+ /** Creates a [SandboxApplication] for Taskbar tests. */
+ fun create(): TaskbarWindowSandboxContext {
+ val base = ApplicationProvider.getApplicationContext<Context>()
+ val displayManager = checkNotNull(base.getSystemService(DisplayManager::class.java))
+
+ // Create virtual display to avoid clashing with Taskbar on default display.
+ val virtualDisplay =
+ base.resources.displayMetrics.let {
+ displayManager.createVirtualDisplay(
+ VIRTUAL_DISPLAY_NAME,
+ it.widthPixels,
+ it.heightPixels,
+ it.densityDpi,
+ /* surface= */ null,
+ /* flags= */ 0,
+ )
+ }
+
+ return TaskbarWindowSandboxContext(
+ SandboxApplication(base.createDisplayContext(virtualDisplay.display)),
+ virtualDisplay,
+ )
}
}
}
+
+private fun virtualDisplayRule(virtualDisplay: VirtualDisplay): TestRule {
+ return object : ExternalResource() {
+ override fun after() = virtualDisplay.release()
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContextTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContextTest.kt
index 4834d48..69095e7 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContextTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContextTest.kt
@@ -16,30 +16,32 @@
package com.android.launcher3.taskbar.rules
-import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
-import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.launcher3.util.LauncherMultivalentJUnit
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
+import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
import com.google.common.truth.Truth.assertThat
import org.junit.Test
+import org.junit.runner.Description
import org.junit.runner.RunWith
+import org.junit.runners.model.Statement
@RunWith(LauncherMultivalentJUnit::class)
-@LauncherMultivalentJUnit.EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"])
+@EmulatedDevices(["pixelFoldable2023"])
class TaskbarWindowSandboxContextTest {
- private val context = TaskbarWindowSandboxContext.create(getInstrumentation().targetContext)
-
@Test
- fun testCreateWindowContext_applicationContextSandboxed() {
- val windowContext = context.createWindowContext(TYPE_APPLICATION_OVERLAY, null)
- assertThat(windowContext.applicationContext).isInstanceOf(SandboxContext::class.java)
- }
+ fun testVirtualDisplay_releasedOnTeardown() {
+ val context = TaskbarWindowSandboxContext.create()
+ assertThat(context.virtualDisplay.token).isNotNull()
- @Test
- fun testCreateWindowContext_nested_applicationContextSandboxed() {
- val windowContext = context.createWindowContext(TYPE_APPLICATION_OVERLAY, null)
- val nestedContext = windowContext.createWindowContext(TYPE_APPLICATION_OVERLAY, null)
- assertThat(nestedContext.applicationContext).isInstanceOf(SandboxContext::class.java)
+ context
+ .apply(
+ object : Statement() {
+ override fun evaluate() = Unit
+ },
+ Description.createSuiteDescription(TaskbarWindowSandboxContextTest::class.java),
+ )
+ .evaluate()
+
+ assertThat(context.virtualDisplay.token).isNull()
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
index 87a7cda..0bf9886 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
@@ -16,15 +16,22 @@
package com.android.quickstep;
+import static com.android.quickstep.AbsSwipeUpHandler.STATE_HANDLER_INVALIDATED;
+
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertTrue;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.animation.ValueAnimator;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
@@ -38,14 +45,13 @@
import android.view.ViewTreeObserver;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
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;
@@ -57,6 +63,7 @@
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -65,11 +72,11 @@
import java.util.HashMap;
public abstract class AbsSwipeUpHandlerTestCase<
- 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>> {
+ STATE_TYPE extends BaseState<STATE_TYPE>,
+ RECENTS_CONTAINER extends Context & RecentsViewContainer & StatefulContainer<STATE_TYPE>,
+ RECENTS_VIEW extends RecentsView<RECENTS_CONTAINER, STATE_TYPE>,
+ SWIPE_HANDLER extends AbsSwipeUpHandler<RECENTS_CONTAINER, RECENTS_VIEW, STATE_TYPE>,
+ CONTAINER_INTERFACE extends BaseContainerInterface<STATE_TYPE, RECENTS_CONTAINER>> {
protected final Context mContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
@@ -106,13 +113,12 @@
/* minimizedHomeBounds= */ null,
new Bundle());
- protected RecentsWindowManager mRecentsWindowManager;
protected TaskAnimationManager mTaskAnimationManager;
- @Mock protected ACTIVITY_INTERFACE mActivityInterface;
+ @Mock protected CONTAINER_INTERFACE mActivityInterface;
@Mock protected ActivityInitListener<?> mActivityInitListener;
@Mock protected RecentsAnimationController mRecentsAnimationController;
- @Mock protected STATE mState;
+ @Mock protected STATE_TYPE mState;
@Mock protected ViewTreeObserver mViewTreeObserver;
@Mock protected DragLayer mDragLayer;
@Mock protected LauncherRootView mRootView;
@@ -123,16 +129,6 @@
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)
@@ -162,6 +158,7 @@
@Before
public void setUpRecentsContainer() {
+ mTaskAnimationManager = new TaskAnimationManager(mContext, getRecentsWindowManager());
RecentsViewContainer recentsContainer = getRecentsContainer();
RECENTS_VIEW recentsView = getRecentsView();
@@ -221,7 +218,7 @@
runOnMainSync(() -> {
absSwipeUpHandler.startNewTask(unused -> {});
- verify(mRecentsAnimationController).finish(anyBoolean(), any());
+ verifyRecentsAnimationFinishedAndCallCallback();
});
}
@@ -231,10 +228,57 @@
runOnMainSync(() -> {
verify(mRecentsAnimationController).detachNavigationBarFromApp(true);
- verify(mRecentsAnimationController).finish(anyBoolean(), any(), anyBoolean());
+ verifyRecentsAnimationFinishedAndCallCallback();
});
}
+ @Test
+ public void testHomeGesture_invalidatesHandlerAfterParallelAnim() {
+ ValueAnimator parallelAnim = new ValueAnimator();
+ parallelAnim.setRepeatCount(ValueAnimator.INFINITE);
+ when(mActivityInterface.getParallelAnimationToLauncher(any(), anyLong(), any()))
+ .thenReturn(parallelAnim);
+ SWIPE_HANDLER handler = createSwipeUpHandlerForGesture(GestureState.GestureEndTarget.HOME);
+ runOnMainSync(() -> {
+ parallelAnim.start();
+ verifyRecentsAnimationFinishedAndCallCallback();
+ assertFalse(handler.mStateCallback.hasStates(STATE_HANDLER_INVALIDATED));
+ parallelAnim.end();
+ assertTrue(handler.mStateCallback.hasStates(STATE_HANDLER_INVALIDATED));
+ });
+ }
+
+ @Test
+ public void testHomeGesture_invalidatesHandlerIfNoParallelAnim() {
+ when(mActivityInterface.getParallelAnimationToLauncher(any(), anyLong(), any()))
+ .thenReturn(null);
+ SWIPE_HANDLER handler = createSwipeUpHandlerForGesture(GestureState.GestureEndTarget.HOME);
+ runOnMainSync(() -> {
+ verifyRecentsAnimationFinishedAndCallCallback();
+ assertTrue(handler.mStateCallback.hasStates(STATE_HANDLER_INVALIDATED));
+ });
+ }
+
+ /**
+ * Verifies that RecentsAnimationController#finish() is called, and captures and runs any
+ * callback that was passed to it. This ensures that STATE_CURRENT_TASK_FINISHED is correctly
+ * set for example.
+ */
+ private void verifyRecentsAnimationFinishedAndCallCallback() {
+ ArgumentCaptor<Runnable> finishCallback = ArgumentCaptor.forClass(Runnable.class);
+ // Check if the 2 parameter method is called.
+ verify(mRecentsAnimationController, atLeast(0)).finish(
+ anyBoolean(), finishCallback.capture());
+ if (finishCallback.getAllValues().isEmpty()) {
+ // Check if the 3 parameter method is called.
+ verify(mRecentsAnimationController).finish(
+ anyBoolean(), finishCallback.capture(), anyBoolean());
+ }
+ if (finishCallback.getValue() != null) {
+ finishCallback.getValue().run();
+ }
+ }
+
private SWIPE_HANDLER createSwipeUpHandlerForGesture(GestureState.GestureEndTarget endTarget) {
boolean isQuickSwitch = endTarget == GestureState.GestureEndTarget.NEW_TASK;
@@ -263,13 +307,12 @@
private void onRecentsAnimationStart(SWIPE_HANDLER absSwipeUpHandler) {
when(mActivityInterface.getOverviewWindowBounds(any(), any())).thenReturn(new Rect());
- doNothing().when(mActivityInterface).setOnDeferredActivityLaunchCallback(any());
runOnMainSync(() -> absSwipeUpHandler.onRecentsAnimationStart(
mRecentsAnimationController, mRecentsAnimationTargets));
}
- private static void runOnMainSync(Runnable runnable) {
+ protected static void runOnMainSync(Runnable runnable) {
InstrumentationRegistry.getInstrumentation().runOnMainSync(runnable);
}
@@ -278,6 +321,11 @@
return createSwipeHandler(SystemClock.uptimeMillis(), false);
}
+ @Nullable
+ protected RecentsWindowManager getRecentsWindowManager() {
+ return null;
+ }
+
@NonNull
protected abstract SWIPE_HANDLER createSwipeHandler(
long touchTimeMs, boolean continuingLastGesture);
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/FallbackSwipeHandlerTestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/FallbackSwipeHandlerTestCase.java
index 8d6906f..88197e5 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/FallbackSwipeHandlerTestCase.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/FallbackSwipeHandlerTestCase.java
@@ -16,6 +16,7 @@
package com.android.quickstep;
+import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import com.android.launcher3.util.LauncherMultivalentJUnit;
@@ -28,15 +29,14 @@
@SmallTest
@RunWith(LauncherMultivalentJUnit.class)
public class FallbackSwipeHandlerTestCase extends AbsSwipeUpHandlerTestCase<
- RecentsActivity,
RecentsState,
- FallbackRecentsView<RecentsActivity>,
RecentsActivity,
- FallbackActivityInterface,
- FallbackSwipeHandler> {
+ FallbackRecentsView<RecentsActivity>,
+ FallbackSwipeHandler,
+ FallbackActivityInterface> {
@Mock private RecentsActivity mRecentsActivity;
- @Mock private FallbackRecentsView mRecentsView;
+ @Mock private FallbackRecentsView<RecentsActivity> mRecentsView;
@Override
@@ -52,13 +52,15 @@
mInputConsumerController);
}
+ @NonNull
@Override
protected RecentsActivity getRecentsContainer() {
return mRecentsActivity;
}
+ @NonNull
@Override
- protected FallbackRecentsView getRecentsView() {
+ protected FallbackRecentsView<RecentsActivity> getRecentsView() {
return mRecentsView;
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java
index 653dc01..ec1dc8b 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java
@@ -19,6 +19,7 @@
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
+import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import com.android.launcher3.Hotseat;
@@ -38,12 +39,11 @@
@SmallTest
@RunWith(LauncherMultivalentJUnit.class)
public class LauncherSwipeHandlerV2TestCase extends AbsSwipeUpHandlerTestCase<
- QuickstepLauncher,
LauncherState,
- RecentsView<QuickstepLauncher, LauncherState>,
QuickstepLauncher,
- LauncherActivityInterface,
- LauncherSwipeHandlerV2> {
+ RecentsView<QuickstepLauncher, LauncherState>,
+ LauncherSwipeHandlerV2,
+ LauncherActivityInterface> {
@Mock private QuickstepLauncher mQuickstepLauncher;
@Mock private RecentsView<QuickstepLauncher, LauncherState> mRecentsView;
@@ -67,6 +67,7 @@
when(mWorkspace.getStateTransitionAnimation()).thenReturn(mTransitionAnimation);
}
+ @NonNull
@Override
protected LauncherSwipeHandlerV2 createSwipeHandler(
long touchTimeMs, boolean continuingLastGesture) {
@@ -80,11 +81,13 @@
mInputConsumerController);
}
+ @NonNull
@Override
protected QuickstepLauncher getRecentsContainer() {
return mQuickstepLauncher;
}
+ @NonNull
@Override
protected RecentsView<QuickstepLauncher, LauncherState> getRecentsView() {
return mRecentsView;
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/OverviewCommandHelperTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/OverviewCommandHelperTest.kt
index 0ae710f..b0db737 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/OverviewCommandHelperTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/OverviewCommandHelperTest.kt
@@ -41,7 +41,6 @@
import org.junit.runner.RunWith
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.spy
-import org.mockito.Mockito.`when`
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
@@ -56,10 +55,12 @@
private val testScope = TestScope(dispatcher)
private var pendingCallbacksWithDelays = mutableListOf<Long>()
+ private lateinit var pendingCommandsToExecute: MutableList<Runnable>
@Suppress("UNCHECKED_CAST")
@Before
fun setup() {
+ pendingCommandsToExecute = mutableListOf()
setFlagsRule.setFlags(true, Flags.FLAG_ENABLE_OVERVIEW_COMMAND_HELPER_TIMEOUT)
sut =
@@ -68,7 +69,8 @@
touchInteractionService = mock(),
overviewComponentObserver = mock(),
taskAnimationManager = mock(),
- dispatcherProvider = TestDispatcherProvider(dispatcher)
+ dispatcherProvider = TestDispatcherProvider(dispatcher),
+ uiExecutor = { runnable -> pendingCommandsToExecute += runnable },
)
)
@@ -94,12 +96,21 @@
pendingCallbacksWithDelays.add(delayInMillis)
}
+ /**
+ * This function runs all the pending commands from the Executor for testing purposes. Replacing
+ * the uiExecutor allows the test to execute the command queue manually, making it possible to
+ * assert each state of the commands in the queue individually.
+ */
+ private fun executePendingCommands() = pendingCommandsToExecute.forEach { it.run() }
+
@Test
fun whenFirstCommandIsAdded_executeCommandImmediately() =
testScope.runTest {
// Add command to queue
val commandInfo: CommandInfo = sut.addCommand(CommandType.HOME)!!
assertThat(commandInfo.status).isEqualTo(CommandStatus.IDLE)
+ executePendingCommands()
+ assertThat(commandInfo.status).isEqualTo(CommandStatus.PROCESSING)
runCurrent()
assertThat(commandInfo.status).isEqualTo(CommandStatus.COMPLETED)
}
@@ -114,7 +125,7 @@
val commandInfo: CommandInfo = sut.addCommand(commandType)!!
assertThat(commandInfo.status).isEqualTo(CommandStatus.IDLE)
- runCurrent()
+ executePendingCommands()
assertThat(commandInfo.status).isEqualTo(CommandStatus.PROCESSING)
advanceTimeBy(200L)
@@ -135,12 +146,14 @@
val commandInfo2: CommandInfo = sut.addCommand(commandType2)!!
assertThat(commandInfo2.status).isEqualTo(CommandStatus.IDLE)
- runCurrent()
+ executePendingCommands()
assertThat(commandInfo1.status).isEqualTo(CommandStatus.PROCESSING)
assertThat(commandInfo2.status).isEqualTo(CommandStatus.IDLE)
advanceTimeBy(101L)
assertThat(commandInfo1.status).isEqualTo(CommandStatus.COMPLETED)
+
+ executePendingCommands()
assertThat(commandInfo2.status).isEqualTo(CommandStatus.PROCESSING)
advanceTimeBy(101L)
@@ -161,12 +174,14 @@
val commandInfo2: CommandInfo = sut.addCommand(commandType2)!!
assertThat(commandInfo2.status).isEqualTo(CommandStatus.IDLE)
- runCurrent()
+ executePendingCommands()
assertThat(commandInfo1.status).isEqualTo(CommandStatus.PROCESSING)
assertThat(commandInfo2.status).isEqualTo(CommandStatus.IDLE)
advanceTimeBy(QUEUE_TIMEOUT)
assertThat(commandInfo1.status).isEqualTo(CommandStatus.CANCELED)
+
+ executePendingCommands()
assertThat(commandInfo2.status).isEqualTo(CommandStatus.PROCESSING)
advanceTimeBy(101)
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsWindowSwipeHandlerTestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsWindowSwipeHandlerTestCase.java
new file mode 100644
index 0000000..1bdf273
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsWindowSwipeHandlerTestCase.java
@@ -0,0 +1,77 @@
+/*
+ * 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 androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.filters.SmallTest;
+
+import com.android.launcher3.util.LauncherMultivalentJUnit;
+import com.android.quickstep.fallback.FallbackRecentsView;
+import com.android.quickstep.fallback.RecentsState;
+import com.android.quickstep.fallback.window.RecentsWindowManager;
+import com.android.quickstep.fallback.window.RecentsWindowSwipeHandler;
+import com.android.quickstep.views.RecentsViewContainer;
+
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+@SmallTest
+@RunWith(LauncherMultivalentJUnit.class)
+public class RecentsWindowSwipeHandlerTestCase extends AbsSwipeUpHandlerTestCase<
+ RecentsState,
+ RecentsWindowManager,
+ FallbackRecentsView<RecentsWindowManager>,
+ RecentsWindowSwipeHandler,
+ FallbackWindowInterface> {
+
+ @Mock private RecentsWindowManager mRecentsWindowManager;
+ @Mock private FallbackRecentsView<RecentsWindowManager> mRecentsView;
+
+ @NonNull
+ @Override
+ protected RecentsWindowSwipeHandler createSwipeHandler(long touchTimeMs,
+ boolean continuingLastGesture) {
+ return new RecentsWindowSwipeHandler(
+ mContext,
+ mRecentsAnimationDeviceState,
+ mTaskAnimationManager,
+ mGestureState,
+ touchTimeMs,
+ continuingLastGesture,
+ mInputConsumerController,
+ mRecentsWindowManager);
+ }
+
+ @Nullable
+ @Override
+ protected RecentsWindowManager getRecentsWindowManager() {
+ return mRecentsWindowManager;
+ }
+
+ @NonNull
+ @Override
+ protected RecentsViewContainer getRecentsContainer() {
+ return mRecentsWindowManager;
+ }
+
+ @NonNull
+ @Override
+ protected FallbackRecentsView<RecentsWindowManager> getRecentsView() {
+ return mRecentsView;
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandlerTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandlerTest.java
new file mode 100644
index 0000000..9018775
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandlerTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.inputconsumers;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.quickstep.DeviceConfigWrapper;
+import com.android.quickstep.NavHandle;
+import com.android.quickstep.util.TestExtensions;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NavHandleLongPressHandlerTest {
+
+ private NavHandleLongPressHandler mLongPressHandler;
+ @Mock private NavHandle mNavHandle;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ mLongPressHandler = new NavHandleLongPressHandler(context);
+ }
+
+ @Test
+ public void testStartNavBarAnimation_flagDisabled() {
+ try (AutoCloseable flag = overrideAnimateLPNHFlag(false)) {
+ mLongPressHandler.startNavBarAnimation(mNavHandle);
+ verify(mNavHandle, never())
+ .animateNavBarLongPress(anyBoolean(), anyBoolean(), anyLong());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Test
+ public void testStartNavBarAnimation_flagEnabled() {
+ try (AutoCloseable flag = overrideAnimateLPNHFlag(true)) {
+ mLongPressHandler.startNavBarAnimation(mNavHandle);
+ verify(mNavHandle).animateNavBarLongPress(anyBoolean(), anyBoolean(), anyLong());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private AutoCloseable overrideAnimateLPNHFlag(boolean value) {
+ return TestExtensions.overrideNavConfigFlag(
+ "ANIMATE_LPNH", value, () -> DeviceConfigWrapper.get().getAnimateLpnh());
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
index c18f604..98a3607 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
@@ -92,14 +92,15 @@
when(mTopTaskTracker.getCachedTopTask(anyBoolean())).thenReturn(mTaskInfo);
when(mDeviceState.getSquaredTouchSlop()).thenReturn(SQUARED_TOUCH_SLOP);
when(mDelegate.allowInterceptByParent()).thenReturn(true);
- MAIN_EXECUTOR.getHandler().removeCallbacks(mLongPressRunnable);
mLongPressTriggered.set(false);
when(mNavHandleLongPressHandler.getLongPressRunnable(any())).thenReturn(mLongPressRunnable);
initializeObjectUnderTest();
}
@After
- public void tearDown() {
+ public void tearDown() throws Exception {
+ MAIN_EXECUTOR.getHandler().removeCallbacks(mLongPressRunnable);
+ MAIN_EXECUTOR.submit(() -> null).get();
mContext.onDestroy();
}
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 a87465f..d55f2e3 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
@@ -22,7 +22,6 @@
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
import com.android.quickstep.util.GroupTask
import com.android.systemui.shared.recents.model.Task
@@ -286,9 +285,14 @@
private fun createThumbnailData(): ThumbnailData {
val bitmap = mock<Bitmap>()
- whenever(bitmap.width).thenReturn(TaskThumbnailViewModelTest.THUMBNAIL_WIDTH)
- whenever(bitmap.height).thenReturn(TaskThumbnailViewModelTest.THUMBNAIL_HEIGHT)
+ whenever(bitmap.width).thenReturn(THUMBNAIL_WIDTH)
+ whenever(bitmap.height).thenReturn(THUMBNAIL_HEIGHT)
return ThumbnailData(thumbnail = bitmap)
}
+
+ companion object {
+ const val THUMBNAIL_WIDTH = 100
+ const val THUMBNAIL_HEIGHT = 200
+ }
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/viewmodel/RecentsViewModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/viewmodel/RecentsViewModelTest.kt
index 33d96a8..829987c 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/viewmodel/RecentsViewModelTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/viewmodel/RecentsViewModelTest.kt
@@ -22,7 +22,6 @@
import android.graphics.Color
import android.view.Surface
import com.android.quickstep.recents.data.FakeTasksRepository
-import com.android.quickstep.task.thumbnail.TaskThumbnailViewModelTest
import com.android.systemui.shared.recents.model.Task
import com.android.systemui.shared.recents.model.ThumbnailData
import com.google.common.truth.Truth.assertThat
@@ -126,9 +125,14 @@
private fun createThumbnailData(rotation: Int = Surface.ROTATION_0): ThumbnailData {
val bitmap = mock<Bitmap>()
- whenever(bitmap.width).thenReturn(TaskThumbnailViewModelTest.THUMBNAIL_WIDTH)
- whenever(bitmap.height).thenReturn(TaskThumbnailViewModelTest.THUMBNAIL_HEIGHT)
+ whenever(bitmap.width).thenReturn(THUMBNAIL_WIDTH)
+ whenever(bitmap.height).thenReturn(THUMBNAIL_HEIGHT)
return ThumbnailData(thumbnail = bitmap, rotation = rotation)
}
+
+ companion object {
+ const val THUMBNAIL_WIDTH = 100
+ const val THUMBNAIL_HEIGHT = 200
+ }
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt
similarity index 97%
rename from quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt
index fcf4e56..c88a3fc 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt
@@ -36,7 +36,7 @@
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.SnapshotSplash
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
import com.android.quickstep.task.viewmodel.TaskContainerData
-import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
+import com.android.quickstep.task.viewmodel.TaskThumbnailViewModelImpl
import com.android.quickstep.task.viewmodel.TaskViewData
import com.android.quickstep.views.TaskViewType
import com.android.systemui.shared.recents.model.Task
@@ -51,7 +51,7 @@
/** Test for [TaskThumbnailView] */
@RunWith(AndroidJUnit4::class)
-class TaskThumbnailViewModelTest {
+class TaskThumbnailViewModelImplTest {
private var taskViewType = TaskViewType.SINGLE
private val recentsViewData = RecentsViewData()
private val taskViewData by lazy { TaskViewData(taskViewType) }
@@ -60,7 +60,7 @@
private val mGetThumbnailPositionUseCase = mock<GetThumbnailPositionUseCase>()
private val splashAlphaUseCase: SplashAlphaUseCase = mock()
private val systemUnderTest by lazy {
- TaskThumbnailViewModel(
+ TaskThumbnailViewModelImpl(
recentsViewData,
taskViewData,
taskContainerData,
@@ -109,7 +109,7 @@
bitmap = expectedThumbnailData.thumbnail!!,
thumbnailRotation = Surface.ROTATION_0,
),
- expectedIconData.icon
+ expectedIconData.icon,
)
)
}
@@ -204,7 +204,7 @@
bitmap = expectedThumbnailData.thumbnail!!,
thumbnailRotation = Surface.ROTATION_270,
),
- expectedIconData.icon
+ expectedIconData.icon,
)
)
}
@@ -230,7 +230,7 @@
bitmap = expectedThumbnailData.thumbnail!!,
thumbnailRotation = Surface.ROTATION_0,
),
- expectedIconData.icon
+ expectedIconData.icon,
)
)
}
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/ContextualSearchInvokerTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/ContextualSearchInvokerTest.java
new file mode 100644
index 0000000..543ffe6
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/ContextualSearchInvokerTest.java
@@ -0,0 +1,253 @@
+/*
+ * 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.app.contextualsearch.ContextualSearchManager.FEATURE_CONTEXTUAL_SEARCH;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_ASSISTANT_FAILED_SERVICE_ERROR;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_OMNI_ATTEMPTED_OVER_KEYGUARD;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_OMNI_ATTEMPTED_OVER_NOTIFICATION_SHADE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_OMNI_ATTEMPTED_SPLITSCREEN;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_OMNI_FAILED_NOT_AVAILABLE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_OMNI_FAILED_SETTING_DISABLED;
+import static com.android.quickstep.util.ContextualSearchInvoker.KEYGUARD_SHOWING_SYSUI_FLAGS;
+import static com.android.quickstep.util.ContextualSearchInvoker.SHADE_EXPANDED_SYSUI_FLAGS;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.contextualsearch.ContextualSearchManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.launcher3.logging.StatsLogManager;
+import com.android.quickstep.DeviceConfigWrapper;
+import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.TopTaskTracker;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Robolectric unit tests for {@link ContextualSearchInvoker}
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ContextualSearchInvokerTest {
+
+ private static final int CONTEXTUAL_SEARCH_ENTRY_POINT = 123;
+
+ private @Mock PackageManager mMockPackageManager;
+ private @Mock ContextualSearchStateManager mMockStateManager;
+ private @Mock TopTaskTracker mMockTopTaskTracker;
+ private @Mock SystemUiProxy mMockSystemUiProxy;
+ private @Mock StatsLogManager mMockStatsLogManager;
+ private @Mock StatsLogManager.StatsLogger mMockStatsLogger;
+ private @Mock ContextualSearchHapticManager mMockContextualSearchHapticManager;
+ private @Mock ContextualSearchManager mMockContextualSearchManager;
+ private ContextualSearchInvoker mContextualSearchInvoker;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mMockPackageManager.hasSystemFeature(FEATURE_CONTEXTUAL_SEARCH)).thenReturn(true);
+ Context context = spy(getApplicationContext());
+ doReturn(mMockPackageManager).when(context).getPackageManager();
+ when(mMockSystemUiProxy.getLastSystemUiStateFlags()).thenReturn(0L);
+ when(mMockTopTaskTracker.getRunningSplitTaskIds()).thenReturn(new int[]{});
+ when(mMockStateManager.isContextualSearchIntentAvailable()).thenReturn(true);
+ when(mMockStateManager.isContextualSearchSettingEnabled()).thenReturn(true);
+ when(mMockStatsLogManager.logger()).thenReturn(mMockStatsLogger);
+
+ mContextualSearchInvoker = new ContextualSearchInvoker(context, mMockStateManager,
+ mMockTopTaskTracker, mMockSystemUiProxy, mMockStatsLogManager,
+ mMockContextualSearchHapticManager, mMockContextualSearchManager);
+ }
+
+ @Test
+ public void runContextualSearchInvocationChecksAndLogFailures_contextualSearchFeatureIsNotAvailable() {
+ when(mMockPackageManager.hasSystemFeature(FEATURE_CONTEXTUAL_SEARCH)).thenReturn(false);
+
+ assertFalse("Expected invocation to fail when feature is unavailable",
+ mContextualSearchInvoker.runContextualSearchInvocationChecksAndLogFailures());
+
+ verify(mMockStatsLogger).log(LAUNCHER_LAUNCH_ASSISTANT_FAILED_SERVICE_ERROR);
+ }
+
+ @Test
+ public void runContextualSearchInvocationChecksAndLogFailures_contextualSearchIntentIsAvailable() {
+ assertTrue("Expected invocation checks to succeed",
+ mContextualSearchInvoker.runContextualSearchInvocationChecksAndLogFailures());
+
+ verifyNoMoreInteractions(mMockStatsLogManager);
+ }
+
+ @Test
+ public void runContextualSearchInvocationChecksAndLogFailures_contextualSearchIntentIsNotAvailable() {
+ when(mMockStateManager.isContextualSearchIntentAvailable()).thenReturn(false);
+
+ assertFalse("Expected invocation to fail when feature is unavailable",
+ mContextualSearchInvoker.runContextualSearchInvocationChecksAndLogFailures());
+
+ verify(mMockStatsLogger).log(LAUNCHER_LAUNCH_OMNI_FAILED_NOT_AVAILABLE);
+ }
+
+ @Test
+ public void runContextualSearchInvocationChecksAndLogFailures_settingDisabled() {
+ when(mMockStateManager.isContextualSearchSettingEnabled()).thenReturn(false);
+
+ assertFalse("Expected invocation checks to fail when setting is disabled",
+ mContextualSearchInvoker.runContextualSearchInvocationChecksAndLogFailures());
+
+ verify(mMockStatsLogger).log(LAUNCHER_LAUNCH_OMNI_FAILED_SETTING_DISABLED);
+ }
+
+ @Test
+ public void runContextualSearchInvocationChecksAndLogFailures_notificationShadeIsShowing() {
+ when(mMockSystemUiProxy.getLastSystemUiStateFlags()).thenReturn(SHADE_EXPANDED_SYSUI_FLAGS);
+
+ assertFalse("Expected invocation checks to fail when notification shade is showing",
+ mContextualSearchInvoker.runContextualSearchInvocationChecksAndLogFailures());
+
+ verify(mMockStatsLogger).log(LAUNCHER_LAUNCH_OMNI_ATTEMPTED_OVER_NOTIFICATION_SHADE);
+ }
+
+ @Test
+ public void runContextualSearchInvocationChecksAndLogFailures_keyguardIsShowing() {
+ when(mMockSystemUiProxy.getLastSystemUiStateFlags()).thenReturn(
+ KEYGUARD_SHOWING_SYSUI_FLAGS);
+
+ assertFalse("Expected invocation checks to fail when keyguard is showing",
+ mContextualSearchInvoker.runContextualSearchInvocationChecksAndLogFailures());
+
+ verify(mMockStatsLogger).log(LAUNCHER_LAUNCH_OMNI_ATTEMPTED_OVER_KEYGUARD);
+ }
+
+ @Test
+ public void runContextualSearchInvocationChecksAndLogFailures_isInSplitScreen_disallowed() {
+ when(mMockStateManager.isInvocationAllowedInSplitscreen()).thenReturn(false);
+ when(mMockTopTaskTracker.getRunningSplitTaskIds()).thenReturn(new int[]{1, 2, 3});
+
+ assertFalse("Expected invocation checks to fail over split screen",
+ mContextualSearchInvoker.runContextualSearchInvocationChecksAndLogFailures());
+
+ // Attempt is logged regardless.
+ verify(mMockStatsLogger).log(LAUNCHER_LAUNCH_OMNI_ATTEMPTED_SPLITSCREEN);
+ }
+
+ @Test
+ public void runContextualSearchInvocationChecksAndLogFailures_isInSplitScreen_allowed() {
+ when(mMockStateManager.isInvocationAllowedInSplitscreen()).thenReturn(true);
+ when(mMockTopTaskTracker.getRunningSplitTaskIds()).thenReturn(new int[]{1, 2, 3});
+
+ assertTrue("Expected invocation checks to succeed over split screen",
+ mContextualSearchInvoker.runContextualSearchInvocationChecksAndLogFailures());
+
+ // Attempt is logged regardless.
+ verify(mMockStatsLogger).log(LAUNCHER_LAUNCH_OMNI_ATTEMPTED_SPLITSCREEN);
+ }
+
+ @Test
+ public void invokeContextualSearchUncheckedWithHaptic_cssIsAvailable_commitHapticEnabled() {
+ try (AutoCloseable flag = overrideSearchHapticCommitFlag(true)) {
+ assertTrue("Expected invocation unchecked to succeed",
+ mContextualSearchInvoker.invokeContextualSearchUncheckedWithHaptic(
+ CONTEXTUAL_SEARCH_ENTRY_POINT));
+ verify(mMockContextualSearchHapticManager).vibrateForSearch();
+ verify(mMockContextualSearchManager).startContextualSearch(
+ CONTEXTUAL_SEARCH_ENTRY_POINT);
+ verifyNoMoreInteractions(mMockStatsLogManager);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Test
+ public void invokeContextualSearchUncheckedWithHaptic_cssIsAvailable_commitHapticDisabled() {
+ try (AutoCloseable flag = overrideSearchHapticCommitFlag(false)) {
+ assertTrue("Expected invocation unchecked to succeed",
+ mContextualSearchInvoker.invokeContextualSearchUncheckedWithHaptic(
+ CONTEXTUAL_SEARCH_ENTRY_POINT));
+ verify(mMockContextualSearchHapticManager, never()).vibrateForSearch();
+ verify(mMockContextualSearchManager).startContextualSearch(
+ CONTEXTUAL_SEARCH_ENTRY_POINT);
+ verifyNoMoreInteractions(mMockStatsLogManager);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Test
+ public void invokeContextualSearchUncheckedWithHaptic_cssIsNotAvailable_commitHapticEnabled() {
+ when(mMockStateManager.isContextualSearchIntentAvailable()).thenReturn(false);
+
+ try (AutoCloseable flag = overrideSearchHapticCommitFlag(true)) {
+ // Still expect true since this method doesn't run the checks.
+ assertTrue("Expected invocation unchecked to succeed",
+ mContextualSearchInvoker.invokeContextualSearchUncheckedWithHaptic(
+ CONTEXTUAL_SEARCH_ENTRY_POINT));
+ // Still vibrate based on the flag.
+ verify(mMockContextualSearchHapticManager).vibrateForSearch();
+ verify(mMockContextualSearchManager).startContextualSearch(
+ CONTEXTUAL_SEARCH_ENTRY_POINT);
+ verifyNoMoreInteractions(mMockStatsLogManager);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ @Test
+ public void invokeContextualSearchUncheckedWithHaptic_cssIsNotAvailable_commitHapticDisabled() {
+ when(mMockStateManager.isContextualSearchIntentAvailable()).thenReturn(false);
+
+ try (AutoCloseable flag = overrideSearchHapticCommitFlag(false)) {
+ // Still expect true since this method doesn't run the checks.
+ assertTrue("Expected ContextualSearch invocation unchecked to succeed",
+ mContextualSearchInvoker.invokeContextualSearchUncheckedWithHaptic(
+ CONTEXTUAL_SEARCH_ENTRY_POINT));
+ // Still don't vibrate based on the flag.
+ verify(mMockContextualSearchHapticManager, never()).vibrateForSearch();
+ verify(mMockContextualSearchManager).startContextualSearch(
+ CONTEXTUAL_SEARCH_ENTRY_POINT);
+ verifyNoMoreInteractions(mMockStatsLogManager);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private AutoCloseable overrideSearchHapticCommitFlag(boolean value) {
+ return TestExtensions.overrideNavConfigFlag(
+ "ENABLE_SEARCH_HAPTIC_COMMIT",
+ value,
+ () -> DeviceConfigWrapper.get().getEnableSearchHapticCommit());
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt
index 7b1c066..108cfb5 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt
@@ -66,7 +66,7 @@
Rect(),
1,
2,
- SplitScreenConstants.SNAP_TO_50_50
+ SplitScreenConstants.SNAP_TO_2_50_50
)
val task1 = GroupTask(createTask(1), createTask(2), splitBounds, TaskViewType.GROUPED)
val task2 = GroupTask(createTask(1), createTask(2), splitBounds, TaskViewType.GROUPED)
@@ -81,7 +81,7 @@
Rect(),
1,
2,
- SplitScreenConstants.SNAP_TO_50_50
+ SplitScreenConstants.SNAP_TO_2_50_50
)
val splitBounds2 =
SplitConfigurationOptions.SplitBounds(
@@ -89,7 +89,7 @@
Rect(),
1,
2,
- SplitScreenConstants.SNAP_TO_30_70
+ SplitScreenConstants.SNAP_TO_2_33_66
)
val task1 = GroupTask(createTask(1), createTask(2), splitBounds1, TaskViewType.GROUPED)
val task2 = GroupTask(createTask(1), createTask(2), splitBounds2, TaskViewType.GROUPED)
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
index 936e996..cb70694 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
@@ -36,9 +36,10 @@
import com.android.quickstep.RecentsModel
import com.android.quickstep.SystemUiProxy
import com.android.quickstep.util.SplitSelectStateController.SplitFromDesktopController
+import com.android.quickstep.views.RecentsView
import com.android.quickstep.views.RecentsViewContainer
import com.android.systemui.shared.recents.model.Task
-import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50
+import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
@@ -50,6 +51,7 @@
import org.mockito.Mockito.`when`
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import java.util.function.Consumer
@@ -66,6 +68,7 @@
private val recentsModel: RecentsModel = mock()
private val pendingIntent: PendingIntent = mock()
private val splitFromDesktopController: SplitFromDesktopController = mock()
+ private val recentsView: RecentsView<*, *> = mock()
private lateinit var splitSelectStateController: SplitSelectStateController
@@ -73,6 +76,7 @@
private val nonPrimaryUserHandle = UserHandle(ActivityManager.RunningTaskInfo().userId + 10)
private var taskIdCounter = 0
+
private fun getUniqueId(): Int {
return ++taskIdCounter
}
@@ -90,7 +94,7 @@
statsLogManager,
systemUiProxy,
recentsModel,
- null /*activityBackCallback*/
+ null, /*activityBackCallback*/
)
}
@@ -100,12 +104,12 @@
val groupTask1 =
generateGroupTask(
ComponentName("pomegranate", "juice"),
- ComponentName("pumpkin", "pie")
+ ComponentName("pumpkin", "pie"),
)
val groupTask2 =
generateGroupTask(
ComponentName("hotdog", "juice"),
- ComponentName("personal", "computer")
+ ComponentName("personal", "computer"),
)
val tasks: ArrayList<GroupTask> = ArrayList()
tasks.add(groupTask1)
@@ -122,7 +126,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(nonMatchingComponent),
false /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -141,12 +145,12 @@
val groupTask1 =
generateGroupTask(
ComponentName(matchingPackage, matchingClass),
- ComponentName("pomegranate", "juice")
+ ComponentName("pomegranate", "juice"),
)
val groupTask2 =
generateGroupTask(
ComponentName("pumpkin", "pie"),
- ComponentName("personal", "computer")
+ ComponentName("personal", "computer"),
)
val tasks: ArrayList<GroupTask> = ArrayList()
tasks.add(groupTask1)
@@ -159,12 +163,12 @@
assertEquals(
"ComponentName package mismatched",
it[0].key.baseIntent.component?.packageName,
- matchingPackage
+ matchingPackage,
)
assertEquals(
"ComponentName class mismatched",
it[0].key.baseIntent.component?.className,
- matchingClass
+ matchingClass,
)
assertEquals(it[0], groupTask1.task1)
}
@@ -175,7 +179,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(matchingComponent),
false /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -194,12 +198,12 @@
val groupTask1 =
generateGroupTask(
ComponentName(matchingPackage, matchingClass),
- ComponentName("pomegranate", "juice")
+ ComponentName("pomegranate", "juice"),
)
val groupTask2 =
generateGroupTask(
ComponentName("pumpkin", "pie"),
- ComponentName("personal", "computer")
+ ComponentName("personal", "computer"),
)
val tasks: ArrayList<GroupTask> = ArrayList()
tasks.add(groupTask1)
@@ -216,7 +220,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(nonPrimaryUserComponent),
false /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -237,12 +241,12 @@
ComponentName(matchingPackage, matchingClass),
nonPrimaryUserHandle,
ComponentName("pomegranate", "juice"),
- nonPrimaryUserHandle
+ nonPrimaryUserHandle,
)
val groupTask2 =
generateGroupTask(
ComponentName("pumpkin", "pie"),
- ComponentName("personal", "computer")
+ ComponentName("personal", "computer"),
)
val tasks: ArrayList<GroupTask> = ArrayList()
tasks.add(groupTask1)
@@ -255,12 +259,12 @@
assertEquals(
"ComponentName package mismatched",
it[0].key.baseIntent.component?.packageName,
- matchingPackage
+ matchingPackage,
)
assertEquals(
"ComponentName class mismatched",
it[0].key.baseIntent.component?.className,
- matchingClass
+ matchingClass,
)
assertEquals("userId mismatched", it[0].key.userId, nonPrimaryUserHandle.identifier)
assertEquals(it[0], groupTask1.task1)
@@ -272,7 +276,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(nonPrimaryUserComponent),
false /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -291,12 +295,12 @@
val groupTask1 =
generateGroupTask(
ComponentName(matchingPackage, matchingClass),
- ComponentName("pumpkin", "pie")
+ ComponentName("pumpkin", "pie"),
)
val groupTask2 =
generateGroupTask(
ComponentName("pomegranate", "juice"),
- ComponentName(matchingPackage, matchingClass)
+ ComponentName(matchingPackage, matchingClass),
)
val tasks: ArrayList<GroupTask> = ArrayList()
tasks.add(groupTask2)
@@ -309,12 +313,12 @@
assertEquals(
"ComponentName package mismatched",
it[0].key.baseIntent.component?.packageName,
- matchingPackage
+ matchingPackage,
)
assertEquals(
"ComponentName class mismatched",
it[0].key.baseIntent.component?.className,
- matchingClass
+ matchingClass,
)
assertEquals(it[0], groupTask1.task1)
}
@@ -325,7 +329,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(matchingComponent),
false /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -348,7 +352,7 @@
val groupTask2 =
generateGroupTask(
ComponentName("pomegranate", "juice"),
- ComponentName(matchingPackage, matchingClass)
+ ComponentName(matchingPackage, matchingClass),
)
val tasks: ArrayList<GroupTask> = ArrayList()
tasks.add(groupTask2)
@@ -363,12 +367,12 @@
assertEquals(
"ComponentName package mismatched",
it[1].key.baseIntent.component?.packageName,
- matchingPackage
+ matchingPackage,
)
assertEquals(
"ComponentName class mismatched",
it[1].key.baseIntent.component?.className,
- matchingClass
+ matchingClass,
)
assertEquals(it[1], groupTask2.task2)
}
@@ -379,7 +383,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(nonMatchingComponent, matchingComponent),
false /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -401,7 +405,7 @@
val groupTask2 =
generateGroupTask(
ComponentName("pomegranate", "juice"),
- ComponentName(matchingPackage, matchingClass)
+ ComponentName(matchingPackage, matchingClass),
)
val tasks: ArrayList<GroupTask> = ArrayList()
tasks.add(groupTask2)
@@ -415,12 +419,12 @@
assertEquals(
"ComponentName package mismatched",
it[0].key.baseIntent.component?.packageName,
- matchingPackage
+ matchingPackage,
)
assertEquals(
"ComponentName class mismatched",
it[0].key.baseIntent.component?.className,
- matchingClass
+ matchingClass,
)
assertEquals(it[0], groupTask2.task2)
assertNull("No tasks should have matched", it[1] /*task*/)
@@ -432,7 +436,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(matchingComponent, matchingComponent),
false /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -452,12 +456,12 @@
val groupTask1 =
generateGroupTask(
ComponentName(matchingPackage, matchingClass),
- ComponentName("pumpkin", "pie")
+ ComponentName("pumpkin", "pie"),
)
val groupTask2 =
generateGroupTask(
ComponentName("pomegranate", "juice"),
- ComponentName(matchingPackage, matchingClass)
+ ComponentName(matchingPackage, matchingClass),
)
val tasks: ArrayList<GroupTask> = ArrayList()
tasks.add(groupTask2)
@@ -471,23 +475,23 @@
assertEquals(
"ComponentName package mismatched",
it[0].key.baseIntent.component?.packageName,
- matchingPackage
+ matchingPackage,
)
assertEquals(
"ComponentName class mismatched",
it[0].key.baseIntent.component?.className,
- matchingClass
+ matchingClass,
)
assertEquals(it[0], groupTask1.task1)
assertEquals(
"ComponentName package mismatched",
it[1].key.baseIntent.component?.packageName,
- matchingPackage
+ matchingPackage,
)
assertEquals(
"ComponentName class mismatched",
it[1].key.baseIntent.component?.className,
- matchingClass
+ matchingClass,
)
assertEquals(it[1], groupTask2.task2)
}
@@ -498,7 +502,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(matchingComponent, matchingComponent),
false /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -524,12 +528,12 @@
val groupTask2 =
generateGroupTask(
ComponentName(matchingPackage2, matchingClass2),
- ComponentName(matchingPackage, matchingClass)
+ ComponentName(matchingPackage, matchingClass),
)
val groupTask3 =
generateGroupTask(
ComponentName("hotdog", "pie"),
- ComponentName(matchingPackage, matchingClass)
+ ComponentName(matchingPackage, matchingClass),
)
val tasks: ArrayList<GroupTask> = ArrayList()
tasks.add(groupTask3)
@@ -550,7 +554,7 @@
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(matchingComponent2, matchingComponent),
true /* findExactPairMatch */,
- taskConsumer
+ taskConsumer,
)
verify(recentsModel).getTasks(capture())
}
@@ -567,7 +571,7 @@
-1 /*stagePosition*/,
ItemInfo(),
null /*splitEvent*/,
- 10 /*alreadyRunningTask*/
+ 10, /*alreadyRunningTask*/
)
assertTrue(splitSelectStateController.isSplitSelectActive)
}
@@ -579,21 +583,23 @@
-1 /*stagePosition*/,
ItemInfo(),
null /*splitEvent*/,
- -1 /*alreadyRunningTask*/
+ -1, /*alreadyRunningTask*/
)
assertTrue(splitSelectStateController.isSplitSelectActive)
}
@Test
fun resetAfterInitial() {
+ whenever(context.getOverviewPanel<RecentsView<*, *>>()).thenReturn(recentsView)
splitSelectStateController.setInitialTaskSelect(
Intent() /*intent*/,
-1 /*stagePosition*/,
ItemInfo(),
null /*splitEvent*/,
- -1
+ -1,
)
splitSelectStateController.resetState()
+ verify(recentsView, times(1)).resetDesktopTaskFromSplitSelectState()
assertFalse(splitSelectStateController.isSplitSelectActive)
}
@@ -622,7 +628,7 @@
// Generate GroupTask with default userId.
private fun generateGroupTask(
task1ComponentName: ComponentName,
- task2ComponentName: ComponentName
+ task2ComponentName: ComponentName,
): GroupTask {
val task1 = Task()
var taskInfo = ActivityManager.RunningTaskInfo()
@@ -642,7 +648,7 @@
return GroupTask(
task1,
task2,
- SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1, SNAP_TO_50_50)
+ SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1, SNAP_TO_2_50_50),
)
}
@@ -651,7 +657,7 @@
task1ComponentName: ComponentName,
userHandle1: UserHandle,
task2ComponentName: ComponentName,
- userHandle2: UserHandle
+ userHandle2: UserHandle,
): GroupTask {
val task1 = Task()
var taskInfo = ActivityManager.RunningTaskInfo()
@@ -674,7 +680,7 @@
return GroupTask(
task1,
task2,
- SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1, SNAP_TO_50_50)
+ SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1, SNAP_TO_2_50_50),
)
}
}
diff --git a/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt b/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt
new file mode 100644
index 0000000..8968b9c
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt
@@ -0,0 +1,193 @@
+/*
+ * 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.content.ComponentName
+import android.content.Intent
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+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
+import com.android.launcher3.uioverrides.QuickstepLauncher
+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
+import com.android.quickstep.views.TaskView
+import com.android.quickstep.views.TaskViewIcon
+import com.android.systemui.shared.recents.model.Task
+import com.android.systemui.shared.recents.model.Task.TaskKey
+import com.android.window.flags.Flags
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+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.verify
+import org.mockito.kotlin.whenever
+import org.mockito.quality.Strictness
+
+/** Test for ExternalDisplaySystemShortcut */
+class ExternalDisplaySystemShortcutTest {
+
+ @get:Rule val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT)
+
+ private val launcher: QuickstepLauncher = mock()
+ private val statsLogManager: StatsLogManager = mock()
+ private val statsLogger: StatsLogManager.StatsLogger = mock()
+ private val recentsView: LauncherRecentsView = mock()
+ private val taskView: TaskView = mock()
+ private val workspaceItemInfo: WorkspaceItemInfo = mock()
+ private val abstractFloatingViewHelper: AbstractFloatingViewHelper = mock()
+ private val iconView: TaskViewIcon = mock()
+ private val transformingTouchDelegate: TransformingTouchDelegate = mock()
+ private val factory: TaskShortcutFactory =
+ ExternalDisplaySystemShortcut.createFactory(abstractFloatingViewHelper)
+ private val overlayFactory: TaskOverlayFactory = mock()
+ private val overlay: TaskOverlay<*> = mock()
+
+ private lateinit var mockitoSession: StaticMockitoSession
+
+ @Before
+ fun setUp() {
+ mockitoSession =
+ mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .spyStatic(DesktopModeStatus::class.java)
+ .startMocking()
+ ExtendedMockito.doReturn(true).`when` { DesktopModeStatus.enforceDeviceRestrictions() }
+ ExtendedMockito.doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ whenever(overlayFactory.createOverlay(any())).thenReturn(overlay)
+ }
+
+ @After
+ fun tearDown() {
+ mockitoSession.finishMocking()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @EnableFlags(Flags.FLAG_MOVE_TO_EXTERNAL_DISPLAY_SHORTCUT)
+ fun createExternalDisplayTaskShortcut_desktopModeDisabled() {
+ val task = createTask()
+ val taskContainer = createTaskContainer(task)
+
+ val shortcuts = factory.getShortcuts(launcher, taskContainer)
+ assertThat(shortcuts).isNull()
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_MOVE_TO_EXTERNAL_DISPLAY_SHORTCUT,
+ )
+ fun createExternalDisplayTaskShortcut_desktopModeEnabled_deviceNotSupported() {
+ ExtendedMockito.doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+
+ val taskContainer = createTaskContainer(createTask())
+
+ val shortcuts = factory.getShortcuts(launcher, taskContainer)
+ assertThat(shortcuts).isNull()
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_MOVE_TO_EXTERNAL_DISPLAY_SHORTCUT,
+ )
+ fun createExternalDisplayTaskShortcut_desktopModeEnabled_deviceNotSupported_overrideEnabled() {
+ ExtendedMockito.doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ ExtendedMockito.doReturn(false).`when` { DesktopModeStatus.enforceDeviceRestrictions() }
+
+ val taskContainer = spy(createTaskContainer(createTask()))
+ doReturn(workspaceItemInfo).whenever(taskContainer).itemInfo
+
+ val shortcuts = factory.getShortcuts(launcher, taskContainer)
+ assertThat(shortcuts).isNotNull()
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_MOVE_TO_EXTERNAL_DISPLAY_SHORTCUT,
+ )
+ fun externalDisplaySystemShortcutClicked() {
+ val task = createTask()
+ val taskContainer = spy(createTaskContainer(task))
+
+ whenever(launcher.getOverviewPanel<LauncherRecentsView>()).thenReturn(recentsView)
+ whenever(launcher.statsLogManager).thenReturn(statsLogManager)
+ whenever(statsLogManager.logger()).thenReturn(statsLogger)
+ whenever(statsLogger.withItemInfo(any())).thenReturn(statsLogger)
+ whenever(recentsView.moveTaskToExternalDisplay(any(), any())).thenAnswer {
+ val successCallback = it.getArgument<Runnable>(1)
+ successCallback.run()
+ }
+ doReturn(workspaceItemInfo).whenever(taskContainer).itemInfo
+
+ val shortcuts = factory.getShortcuts(launcher, taskContainer)
+ assertThat(shortcuts).hasSize(1)
+ assertThat(shortcuts!!.first()).isInstanceOf(ExternalDisplaySystemShortcut::class.java)
+
+ val externalDisplayShortcut = shortcuts.first() as ExternalDisplaySystemShortcut
+
+ externalDisplayShortcut.onClick(taskView)
+
+ val allTypesExceptRebindSafe =
+ AbstractFloatingView.TYPE_ALL and AbstractFloatingView.TYPE_REBIND_SAFE.inv()
+ verify(abstractFloatingViewHelper).closeOpenViews(launcher, true, allTypesExceptRebindSafe)
+ verify(recentsView).moveTaskToExternalDisplay(eq(taskContainer), any())
+ verify(statsLogger).withItemInfo(workspaceItemInfo)
+ verify(statsLogger).log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_EXTERNAL_DISPLAY_TAP)
+ }
+
+ private fun createTask(): Task = Task(TaskKey(1, 0, Intent(), ComponentName("", ""), 0, 2000))
+
+ private fun createTaskContainer(task: Task): TaskContainer {
+ val snapshotView =
+ if (enableRefactorTaskThumbnail()) mock<TaskThumbnailView>()
+ else mock<TaskThumbnailViewDeprecated>()
+ return TaskContainer(
+ taskView,
+ task,
+ snapshotView,
+ iconView,
+ transformingTouchDelegate,
+ SplitConfigurationOptions.STAGE_POSITION_UNDEFINED,
+ digitalWellBeingToast = null,
+ showWindowsView = null,
+ overlayFactory,
+ )
+ }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/TaskAnimationManagerTest.java b/quickstep/tests/src/com/android/quickstep/TaskAnimationManagerTest.java
index ec07b93..633a575 100644
--- a/quickstep/tests/src/com/android/quickstep/TaskAnimationManagerTest.java
+++ b/quickstep/tests/src/com/android/quickstep/TaskAnimationManagerTest.java
@@ -17,8 +17,8 @@
package com.android.quickstep;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -73,7 +73,8 @@
final ArgumentCaptor<ActivityOptions> optionsCaptor =
ArgumentCaptor.forClass(ActivityOptions.class);
- verify(mSystemUiProxy).startRecentsActivity(any(), optionsCaptor.capture(), any());
+ verify(mSystemUiProxy)
+ .startRecentsActivity(any(), optionsCaptor.capture(), any(), anyBoolean());
assertEquals(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS,
optionsCaptor.getValue().getPendingIntentBackgroundActivityStartMode());
}
diff --git a/res/drawable/all_apps_tabs_background.xml b/res/drawable/all_apps_tabs_background.xml
index 62927af..d200b9f 100644
--- a/res/drawable/all_apps_tabs_background.xml
+++ b/res/drawable/all_apps_tabs_background.xml
@@ -13,36 +13,25 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
- android:color="@color/accent_ripple_color">
-
- <item android:id="@android:id/mask">
- <shape android:shape="rectangle">
- <corners android:radius="@dimen/all_apps_header_pill_corner_radius" />
- <solid android:color="@color/accent_ripple_color" />
- </shape>
- </item>
-
- <item>
- <selector android:enterFadeDuration="100">
- <item
- android:id="@+id/unselected"
- android:state_selected="false">
- <shape android:shape="rectangle">
- <corners android:radius="@dimen/all_apps_header_pill_corner_radius" />
- <solid android:color="?attr/materialColorSurfaceBright" />
- </shape>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/all_apps_tabs_background_unselected_focused" android:state_focused="true" android:state_selected="false" />
+ <item android:drawable="@drawable/all_apps_tabs_background_selected_focused" android:state_focused="true" android:state_selected="true" />
+ <item android:id="@+id/unselected" android:state_focused="false" android:state_selected="false">
+ <ripple android:color="@color/accent_ripple_color">
+ <item>
+ <selector android:enterFadeDuration="100">
+ <item android:drawable="@drawable/all_apps_tabs_background_unselected" />
+ </selector>
</item>
-
- <item
- android:id="@+id/selected"
- android:state_selected="true">
- <shape android:shape="rectangle">
- <corners android:radius="@dimen/all_apps_header_pill_corner_radius" />
- <solid android:color="?attr/materialColorPrimary" />
- </shape>
- </item>
- </selector>
+ </ripple>
</item>
-
-</ripple>
\ No newline at end of file
+ <item android:id="@+id/selected" android:state_focused="false" android:state_selected="true">
+ <ripple android:color="@color/accent_ripple_color">
+ <item>
+ <selector android:enterFadeDuration="100">
+ <item android:drawable="@drawable/all_apps_tabs_background_selected" />
+ </selector>
+ </item>
+ </ripple>
+ </item>
+</selector>
\ No newline at end of file
diff --git a/res/drawable/all_apps_tabs_background_selected.xml b/res/drawable/all_apps_tabs_background_selected.xml
new file mode 100644
index 0000000..47f95dd
--- /dev/null
+++ b/res/drawable/all_apps_tabs_background_selected.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:bottom="@dimen/all_apps_tabs_focus_width"
+ android:end="@dimen/all_apps_tabs_focus_width"
+ android:start="@dimen/all_apps_tabs_focus_width"
+ android:top="@dimen/all_apps_tabs_focus_width">
+ <shape android:shape="rectangle">
+ <corners android:radius="@dimen/all_apps_header_pill_corner_radius" />
+ <solid android:color="?attr/materialColorPrimary" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/res/drawable/all_apps_tabs_background_selected_focused.xml b/res/drawable/all_apps_tabs_background_selected_focused.xml
new file mode 100644
index 0000000..e3d86c0
--- /dev/null
+++ b/res/drawable/all_apps_tabs_background_selected_focused.xml
@@ -0,0 +1,37 @@
+<?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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape>
+ <corners android:radius="16dp" />
+ <solid android:color="?attr/materialColorPrimary" />
+ </shape>
+ </item>
+
+ <item
+ android:bottom="@dimen/all_apps_tabs_focus_border"
+ android:end="@dimen/all_apps_tabs_focus_border"
+ android:start="@dimen/all_apps_tabs_focus_border"
+ android:top="@dimen/all_apps_tabs_focus_border">
+ <shape android:shape="rectangle">
+ <corners android:radius="13dp" />
+ <solid android:color="?attr/materialColorPrimary" />
+ <stroke
+ android:width="@dimen/all_apps_tabs_focus_padding"
+ android:color="?attr/materialColorSurfaceDim" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/res/drawable/all_apps_tabs_background_unselected.xml b/res/drawable/all_apps_tabs_background_unselected.xml
new file mode 100644
index 0000000..ab592a8
--- /dev/null
+++ b/res/drawable/all_apps_tabs_background_unselected.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:bottom="@dimen/all_apps_tabs_focus_width"
+ android:end="@dimen/all_apps_tabs_focus_width"
+ android:start="@dimen/all_apps_tabs_focus_width"
+ android:top="@dimen/all_apps_tabs_focus_width">
+ <shape android:shape="rectangle">
+ <corners android:radius="@dimen/all_apps_header_pill_corner_radius" />
+ <solid android:color="?attr/materialColorSurfaceBright" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/res/drawable/all_apps_tabs_background_unselected_focused.xml b/res/drawable/all_apps_tabs_background_unselected_focused.xml
new file mode 100644
index 0000000..0016102
--- /dev/null
+++ b/res/drawable/all_apps_tabs_background_unselected_focused.xml
@@ -0,0 +1,37 @@
+<?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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape>
+ <corners android:radius="16dp" />
+ <solid android:color="?attr/materialColorPrimary" />
+ </shape>
+ </item>
+
+ <item
+ android:bottom="@dimen/all_apps_tabs_focus_border"
+ android:end="@dimen/all_apps_tabs_focus_border"
+ android:start="@dimen/all_apps_tabs_focus_border"
+ android:top="@dimen/all_apps_tabs_focus_border">
+ <shape android:shape="rectangle">
+ <corners android:radius="13dp" />
+ <solid android:color="?attr/materialColorSurfaceBright" />
+ <stroke
+ android:width="@dimen/all_apps_tabs_focus_padding"
+ android:color="?attr/materialColorSurfaceDim" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/res/drawable/ic_private_profile_divider_badge.xml b/res/drawable/ic_private_profile_divider_badge.xml
new file mode 100644
index 0000000..07c740d
--- /dev/null
+++ b/res/drawable/ic_private_profile_divider_badge.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
+ <group>
+ <path
+ android:pathData="M5,9L15,9A1,1 0,0 1,16 10L16,10A1,1 0,0 1,15 11L5,11A1,1 0,0 1,4 10L4,10A1,1 0,0 1,5 9z"
+ android:fillColor="?attr/materialColorOnSurface"/>
+ </group>
+</vector>
diff --git a/res/drawable/ic_private_profile_letter_list_fast_scroller_badge.xml b/res/drawable/ic_private_profile_letter_list_fast_scroller_badge.xml
new file mode 100644
index 0000000..8d12598
--- /dev/null
+++ b/res/drawable/ic_private_profile_letter_list_fast_scroller_badge.xml
@@ -0,0 +1,28 @@
+<!--
+ ~ 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="12dp"
+ android:height="15dp"
+ android:viewportWidth="12"
+ android:viewportHeight="15">
+ <path
+ android:pathData="M5.952,0.911L0.645,2.902V6.942C0.645,10.292 2.907,13.417 5.952,14.18C8.997,13.417 11.26,10.292 11.26,6.942V2.902L5.952,0.911ZM7.943,9.536V10.863H6.616V11.526H5.289V8.103C4.333,7.818 3.63,6.942 3.63,5.887C3.63,4.607 4.672,3.565 5.952,3.565C7.233,3.565 8.274,4.607 8.274,5.887C8.274,6.935 7.571,7.818 6.616,8.103V9.536H7.943Z"
+ android:fillColor="#3C4043"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M5.952,6.882C6.502,6.882 6.947,6.436 6.947,5.887C6.947,5.337 6.502,4.892 5.952,4.892C5.403,4.892 4.957,5.337 4.957,5.887C4.957,6.436 5.403,6.882 5.952,6.882Z"
+ android:fillColor="#3C4043"/>
+</vector>
diff --git a/res/layout/all_apps_personal_work_tabs.xml b/res/layout/all_apps_personal_work_tabs.xml
index e04b207..ecc5a14 100644
--- a/res/layout/all_apps_personal_work_tabs.xml
+++ b/res/layout/all_apps_personal_work_tabs.xml
@@ -21,8 +21,8 @@
android:layout_width="match_parent"
android:layout_height="@dimen/all_apps_header_pill_height"
android:layout_gravity="center_horizontal"
- android:paddingTop="@dimen/all_apps_tabs_vertical_padding"
- android:paddingBottom="@dimen/all_apps_tabs_vertical_padding"
+ android:paddingTop="@dimen/all_apps_tabs_vertical_padding_focus"
+ android:paddingBottom="@dimen/all_apps_tabs_vertical_padding_focus"
android:layout_marginTop="@dimen/all_apps_tabs_margin_top"
android:orientation="horizontal"
style="@style/TextHeadline"
diff --git a/res/layout/user_folder_icon_normalized.xml b/res/layout/user_folder_icon_normalized.xml
index 43a8aac..002e7b7 100644
--- a/res/layout/user_folder_icon_normalized.xml
+++ b/res/layout/user_folder_icon_normalized.xml
@@ -40,12 +40,11 @@
<com.android.launcher3.folder.FolderNameEditText
android:id="@+id/folder_name"
android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
+ android:layout_height="match_parent"
style="@style/TextHeadline"
android:layout_weight="1"
android:background="@android:color/transparent"
- android:gravity="center_horizontal"
+ android:gravity="center"
android:hint="@string/folder_hint_text"
android:imeOptions="flagNoExtractUi"
android:importantForAutofill="no"
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 4f62bda..e5c1b61 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -31,8 +31,7 @@
<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="new_window_option_taskbar" msgid="6448780542727767211">"Nuwe venster"</string>
<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 be91c0b..53dc4ba 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"የተከፈለ ማያ ገፅ"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"የመተግበሪያ መረጃ ለ%1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"የ%1$s የአጠቃቀም ቅንብሮች"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"አዲስ መስኮት"</string>
<string name="save_app_pair" msgid="5647523853662686243">"የመተግበሪያ ጥምረትን ያስቀምጡ"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"ይህ የመተግበሪያ ጥምረት በዚህ መሣሪያ ላይ አይደገፍም"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 04c2f2f..b15d525 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"تقسيم الشاشة"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"معلومات تطبيق %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"إعدادات استخدام \"%1$s\""</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"نافذة جديدة"</string>
<string name="save_app_pair" msgid="5647523853662686243">"حفظ استخدام التطبيقين معًا"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"لا يمكن استخدام هذين التطبيقَين في الوقت نفسه على هذا الجهاز"</string>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index 4d377c2..d8783a6 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"বিভাজিত স্ক্ৰীন"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$sৰ বাবে এপৰ তথ্য"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$sৰ বাবে ব্যৱহাৰৰ ছেটিং"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"নতুন ৱিণ্ড’"</string>
<string name="save_app_pair" msgid="5647523853662686243">"এপৰ পেয়াৰ ছেভ কৰক"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"এই ডিভাইচটোত এই এপ্ পেয়াৰ কৰাৰ সুবিধাটো সমৰ্থিত নহয়"</string>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index 4c2ba9b..5b86fac 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -31,8 +31,7 @@
<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="new_window_option_taskbar" msgid="6448780542727767211">"Yeni Pəncərə"</string>
<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 35ba183..002c800 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Podeljeni ekran"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacije o aplikaciji za: %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Podešavanja potrošnje za %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Novi prozor"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Sačuvaj par aplikacija"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Ovaj par aplikacija nije podržan na ovom uređaju"</string>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 1a2bcc1..0984f32 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Падзелены экран"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Інфармацыя пра праграму для: %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s: налады выкарыстання"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Новае акно"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Захаваць спалучэнне праграм"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Дадзенае спалучэнне праграм не падтрымліваецца на гэтай прыладзе"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 270374d..b321b42 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Разделен екран"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Информация за приложението за %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Настройки за използването на %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Нов прозорец"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Запазване на двойката приложения"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Тази двойка приложения не се поддържа на устройството"</string>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index 5097784..0722e84 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"স্প্লিট স্ক্রিন"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s-এর জন্য অ্যাপ সম্পর্কিত তথ্য"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s-এর জন্য ব্যবহারের সেটিংস"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"নতুন উইন্ডো"</string>
<string name="save_app_pair" msgid="5647523853662686243">"অ্যাপ পেয়ার সেভ করুন"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"এই ডিভাইসে এই অ্যাপ পেয়ারটি কাজ করে না"</string>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index e4bbd30..2b168f6 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Podijeljeni ekran"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacije o aplikaciji %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Postavke korištenja za: %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Novi prozor"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Sačuvaj par aplikacija"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Par aplikacija nije podržan na uređaju"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 77642f0..6d30ec1 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantalla dividida"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informació 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="new_window_option_taskbar" msgid="6448780542727767211">"Finestra nova"</string>
<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 08747e0..732343d 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -31,8 +31,7 @@
<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="new_window_option_taskbar" msgid="6448780542727767211">"Nové okno"</string>
<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 0bf8514..0d78d90 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -31,8 +31,7 @@
<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="new_window_option_taskbar" msgid="6448780542727767211">"Nyt vindue"</string>
<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>
@@ -175,7 +174,7 @@
<string name="action_deep_shortcut" msgid="2864038805849372848">"Genveje"</string>
<string name="action_dismiss_notification" msgid="5909461085055959187">"Afvis"</string>
<string name="accessibility_close" msgid="2277148124685870734">"Luk"</string>
- <string name="all_apps_personal_tab" msgid="4190252696685155002">"Personlige"</string>
+ <string name="all_apps_personal_tab" msgid="4190252696685155002">"Personlig"</string>
<string name="all_apps_work_tab" msgid="4884822796154055118">"Arbejde"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Arbejdsprofil"</string>
<string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Arbejdsapps har badges og kan ses af din it-administrator"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index c3628ea..c90cf84 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -31,8 +31,7 @@
<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="new_window_option_taskbar" msgid="6448780542727767211">"Neues Fenster"</string>
<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 85c6a31..cd1a1e0 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Διαχωρισμός οθόνης"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Πληροφορίες εφαρμογής για %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Ρυθμίσεις χρήσης για %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Νέο παράθυρο"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Αποθήκευση ζεύγους εφαρμογών"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Αυτό το ζεύγος εφαρμογών δεν υποστηρίζεται σε αυτή τη συσκευή"</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index aee49be..f7b04a3 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Split screen"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"App info for %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Usage settings for %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"New window"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Save app pair"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"This app pair isn\'t supported on this device"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index aee49be..f7b04a3 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Split screen"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"App info for %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Usage settings for %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"New window"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Save app pair"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"This app pair isn\'t supported on this device"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index aee49be..f7b04a3 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Split screen"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"App info for %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Usage settings for %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"New window"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Save app pair"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"This app pair isn\'t supported on this device"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index e051843..0125ae5 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantalla dividida"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Información de la 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="new_window_option_taskbar" msgid="6448780542727767211">"Ventana nueva"</string>
<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 169afe5..ddcee65 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantalla dividida"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Información de la aplicación %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Ajustes de uso para %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Ventana nueva"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Guardar apps emparejadas"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"El dispositivo no admite esta aplicación emparejada"</string>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 844a0c7..289b9d9 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -31,8 +31,7 @@
<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="new_window_option_taskbar" msgid="6448780542727767211">"Uus aken"</string>
<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 8c9375a..db99806 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -31,11 +31,10 @@
<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="new_window_option_taskbar" msgid="6448780542727767211">"Leiho berria"</string>
<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>
+ <string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Aplikazio pare hori ez da gailu honekin bateragarria"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Zabaldu gailua aplikazio pare hau erabiltzeko"</string>
<string name="app_pair_not_available" msgid="3556767440808032031">"Aplikazio parea ez dago erabilgarri"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Eduki sakatuta widget bat mugitzeko."</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index d841770..7c6e373 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"صفحهٔ دونیمه"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"اطلاعات برنامه %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"تنظیمات مصرف برای %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"پنجره جدید"</string>
<string name="save_app_pair" msgid="5647523853662686243">"ذخیره جفت برنامه"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"از این جفت برنامه در این دستگاه پشتیبانی نمیشود"</string>
@@ -132,7 +131,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 567a1ea..c61c85a 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -31,8 +31,7 @@
<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="new_window_option_taskbar" msgid="6448780542727767211">"Uusi ikkuna"</string>
<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 a886420..cae77dc 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -31,8 +31,7 @@
<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="new_window_option_taskbar" msgid="6448780542727767211">"Nouvelle fenêtre"</string>
<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 d96bd98..6cbc921 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -31,8 +31,7 @@
<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="new_window_option_taskbar" msgid="6448780542727767211">"Nouvelle fenêtre"</string>
<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 c224a19..02389c4 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantalla dividida"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Información 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="new_window_option_taskbar" msgid="6448780542727767211">"Ventá nova"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Gardar parella de apps"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"O dispositivo non admite este emparellamento de aplicacións"</string>
@@ -121,7 +120,7 @@
<string name="app_pair_name_format" msgid="8134106404716224054">"Emparellamento de aplicacións: <xliff:g id="APP1">%1$s</xliff:g> e <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Estilo e fondo de pantalla"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Editar pantalla de inicio"</string>
- <string name="settings_button_text" msgid="8873672322605444408">"Axustes de Inicio"</string>
+ <string name="settings_button_text" msgid="8873672322605444408">"Configuración da pantalla de inicio"</string>
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"Función desactivada polo administrador"</string>
<string name="allow_rotation_title" msgid="7222049633713050106">"Permitir xirar a pantalla de inicio"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Ao xirar o teléfono"</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 732a8e4..aca3054 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"સ્ક્રીનને વિભાજિત કરો"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s માટે ઍપ માહિતી"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$sના વપરાશ સંબંધિત સેટિંગ"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"નવી વિન્ડો"</string>
<string name="save_app_pair" msgid="5647523853662686243">"ઍપની જોડી સાચવો"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"આ ડિવાઇસ પર, આ ઍપની જોડીને સપોર્ટ આપવામાં આવતો નથી"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index d4fb0ca..00726ea 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"स्प्लिट स्क्रीन"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s के लिए ऐप्लिकेशन की जानकारी"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s के लिए खर्च की सेटिंग"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"नई विंडो"</string>
<string name="save_app_pair" msgid="5647523853662686243">"ऐप पेयर सेव करें"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"साथ में इस्तेमाल किए जा सकने वाले ये ऐप्लिकेशन, इस डिवाइस पर काम नहीं कर सकते"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 3d5380a..a9fd14e 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Podijeljeni zaslon"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacije o aplikaciji %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Postavke upotrebe za %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Novi prozor"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Spremi par aplikacija"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Taj par aplikacija nije podržan na ovom uređaju"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 1a38777..a0c089e 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -31,8 +31,7 @@
<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="new_window_option_taskbar" msgid="6448780542727767211">"Új ablak"</string>
<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 eaaf435..ba2edb1 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Տրոհել էկրանը"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Տեղեկություններ %1$s հավելվածի մասին"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Օգտագործման կարգավորումներ (%1$s)"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Նոր պատուհան"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Պահել հավելվ. զույգը"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Հավելվածների զույգը չի աջակցվում այս սարքում"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 62e9d9d..40f2e57 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -31,8 +31,7 @@
<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="new_window_option_taskbar" msgid="6448780542727767211">"Jendela Baru"</string>
<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 7a87b26..0014317 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Skipta skjá"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Upplýsingar um forrit fyrir %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Notkunarstillingar fyrir %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Nýr gluggi"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Vista forritapar"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Þetta forritapar er ekki stutt í þessu tæki"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 100da6c..731f839 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -31,8 +31,7 @@
<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="new_window_option_taskbar" msgid="6448780542727767211">"Nuova finestra"</string>
<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 f85e571..82eb5f8 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"מסך מפוצל"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"פרטים על האפליקציה %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"הגדרות שימוש ב-%1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"חלון חדש"</string>
<string name="save_app_pair" msgid="5647523853662686243">"שמירת צמד אפליקציות"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"צמד האפליקציות הזה לא נתמך במכשיר הזה"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index cea0a08..fc89041 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"分割画面"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s のアプリ情報"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s の使用設定"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"新しいウィンドウ"</string>
<string name="save_app_pair" msgid="5647523853662686243">"アプリのペア設定を保存"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"このデバイスは、このアプリのペア設定に対応していません"</string>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index 2211a7d..f099bcd 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"ეკრანის გაყოფა"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s-ის აპის ინფო"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"გამოყენების პარამეტრები %1$s-ისთვის"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"ახალი ფანჯარა"</string>
<string name="save_app_pair" msgid="5647523853662686243">"აპთა წყვილის შენახვა"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"ამ მოწყობილობაზე აღნიშნული აპთა წყვილი არ არის მხარდაჭერილი"</string>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index ca26ea4..ebaacc9 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Экранды бөлу"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s қолданбасы туралы ақпарат"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s пайдалану параметрлері"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Жаңа терезе"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Қолданбаларды жұптау әрекетін сақтау"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Бұл құрылғы қолданбаларды жұптау функциясын қолдамайды."</string>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index dc4fd80..b05eeb0 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"មុខងារបំបែកអេក្រង់"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"ព័ត៌មានកម្មវិធីសម្រាប់ %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"ការកំណត់ការប្រើប្រាស់សម្រាប់ %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"វិនដូថ្មី"</string>
<string name="save_app_pair" msgid="5647523853662686243">"រក្សាទុកគូកម្មវិធី"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"មិនអាចប្រើគូកម្មវិធីនេះនៅលើឧបករណ៍នេះបានទេ"</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index eda9fb8..786cea1 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -21,7 +21,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
<string name="work_folder_name" msgid="3753320833950115786">"ಕೆಲಸ"</string>
- <string name="activity_not_found" msgid="8071924732094499514">"ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಸ್ಥಾಪಿಸಲಾಗಿಲ್ಲ"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"ಆ್ಯಪ್ ಅನ್ನು ಸ್ಥಾಪಿಸಲಾಗಿಲ್ಲ"</string>
<string name="activity_not_available" msgid="7456344436509528827">"ಅಪ್ಲಿಕೇಶನ್ ಲಭ್ಯವಿಲ್ಲ"</string>
<string name="safemode_shortcut_error" msgid="9160126848219158407">"ಡೌನ್ಲೋಡ್ ಮಾಡಲಾದ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಸುರಕ್ಷಿತ ಮೋಡ್ನಲ್ಲಿ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"ಸುರಕ್ಷಿತ ಮೋಡ್ನಲ್ಲಿ ವಿಜೆಟ್ಗಳನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s ಗಾಗಿ ಆ್ಯಪ್ ಮಾಹಿತಿ"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s ಗೆ ಸಂಬಂಧಿಸಿದ ಬಳಕೆಯ ಸೆಟ್ಟಿಂಗ್ಗಳು"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"ಹೊಸ ವಿಂಡೋ"</string>
<string name="save_app_pair" msgid="5647523853662686243">"ಆ್ಯಪ್ ಪೇರ್ ಸೇವ್ ಮಾಡಿ"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"ಈ ಆ್ಯಪ್ ಜೋಡಿಯು ಈ ಸಾಧನದಲ್ಲಿ ಬೆಂಬಲಿತವಾಗಿಲ್ಲ"</string>
@@ -104,7 +103,7 @@
<string name="gadget_error_text" msgid="740356548025791839">"ವಿಜೆಟ್ ಅನ್ನು ಲೋಡ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
<string name="gadget_setup_text" msgid="8348374825537681407">"ವಿಜೆಟ್ ಸೆಟ್ಟಿಂಗ್ಗಳು"</string>
<string name="gadget_complete_setup_text" msgid="309040266978007925">"ಸೆಟಪ್ ಪೂರ್ಣಗೊಳಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
- <string name="uninstall_system_app_text" msgid="4172046090762920660">"ಇದೊಂದು ಅಪ್ಲಿಕೇಶನ್ ಆಗಿದೆ ಮತ್ತು ಅಸ್ಥಾಪಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"ಇದೊಂದು ಆ್ಯಪ್ ಆಗಿದೆ ಮತ್ತು ಅಸ್ಥಾಪಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
<string name="folder_hint_text" msgid="5174843001373488816">"ಹೆಸರನ್ನು ಎಡಿಟ್ ಮಾಡಿ"</string>
<string name="disabled_app_label" msgid="6673129024321402780">"<xliff:g id="APP_NAME">%1$s</xliff:g> ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
<string name="dotted_app_label" msgid="1865617679843363410">"{count,plural, =1{{app_name} ಆ್ಯಪ್ # ಅಧಿಸೂಚನೆಯನ್ನು ಹೊಂದಿದೆ}one{{app_name} ಆ್ಯಪ್ # ಅಧಿಸೂಚನೆಗಳನ್ನು ಹೊಂದಿದೆ}other{{app_name} ಆ್ಯಪ್ # ಅಧಿಸೂಚನೆಗಳನ್ನು ಹೊಂದಿದೆ}}"</string>
@@ -121,7 +120,7 @@
<string name="app_pair_name_format" msgid="8134106404716224054">"ಆ್ಯಪ್ ಜೋಡಿ: <xliff:g id="APP1">%1$s</xliff:g> ಮತ್ತು <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"ವಾಲ್ಪೇಪರ್ ಮತ್ತು ಶೈಲಿ"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"ಹೋಮ್ ಸ್ಕ್ರೀನ್ ಅನ್ನು ಎಡಿಟ್ ಮಾಡಿ"</string>
- <string name="settings_button_text" msgid="8873672322605444408">"ಮುಖಪುಟ ಸೆಟ್ಟಿಂಗ್ಗಳು"</string>
+ <string name="settings_button_text" msgid="8873672322605444408">"ಹೋಮ್ ಸೆಟ್ಟಿಂಗ್ಗಳು"</string>
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"ನಿಮ್ಮ ನಿರ್ವಾಹಕರು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿದ್ದಾರೆ"</string>
<string name="allow_rotation_title" msgid="7222049633713050106">"ಹೋಮ್ ಸ್ಕ್ರೀನ್ ತಿರುಗುವಿಕೆಯನ್ನು ಅನುಮತಿಸಿ"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"ಫೋನ್ ತಿರುಗಿಸಿದಾಗ"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 94ebd15..0e7ef7b 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"화면 분할"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s 앱 정보"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s의 사용량 설정"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"새 창"</string>
<string name="save_app_pair" msgid="5647523853662686243">"앱 페어링 저장"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"이 앱 페어링은 이 기기에서 지원되지 않습니다"</string>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index b326181..753d2dd 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Экранды бөлүү"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s колдонмосу жөнүндө маалымат"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s колдонмосун пайдалануу параметрлери"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Жаңы терезе"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Колдонмолорду сактап коюу"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Бул эки колдонмону бул түзмөктө бир маалда пайдаланууга болбойт"</string>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index 741556a..9ddf0b3 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"ແບ່ງໜ້າຈໍ"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"ຂໍ້ມູນແອັບສຳລັບ %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"ການຕັ້ງຄ່າການນຳໃຊ້ສຳລັບ %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"ໜ້າຈໍໃໝ່"</string>
<string name="save_app_pair" msgid="5647523853662686243">"ບັນທຶກຈັບຄູ່ແອັບ"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"ການຈັບຄູ່ແອັບນີ້ບໍ່ຮອງຮັບຢູ່ອຸປະກອນນີ້"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index a613cb1..6879862 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Išskaidyto ekrano režimas"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Programos „%1$s“ informacija"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"„%1$s“ naudojimo nustatymai"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Naujas langas"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Išsaugoti programų porą"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Ši programų pora šiame įrenginyje nepalaikoma"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index de00e4d..8166bce 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -31,8 +31,7 @@
<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="new_window_option_taskbar" msgid="6448780542727767211">"Jauns logs"</string>
<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 91b30c9..0511c3f 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Поделен екран"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Податоци за апликација за %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Поставки за користење за %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Нов прозорец"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Зачувај го парот апликации"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Паров апликации не е поддржан на уредов"</string>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 2eb1b2b..b9e68e0 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"സ്ക്രീൻ വിഭജന മോഡ്"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s എന്നതിന്റെ ആപ്പ് വിവരങ്ങൾ"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s എന്നതിനുള്ള ഉപയോഗ ക്രമീകരണം"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"പുതിയ വിന്ഡോ"</string>
<string name="save_app_pair" msgid="5647523853662686243">"ആപ്പ് ജോടി സംരക്ഷിക്കുക"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"ഈ ഉപകരണത്തിൽ ഈ ആപ്പ് ജോടിക്ക് പിന്തുണയില്ല"</string>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index bba7e16..28a1438 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Дэлгэцийг хуваах"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s-н аппын мэдээлэл"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s-н ашиглалтын тохиргоо"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Шинэ цонх"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Апп хослуулалтыг хадгалах"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Энэ апп хослуулалтыг уг төхөөрөмж дээр дэмждэггүй"</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index 548e764..0190f1f 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"स्प्लिट स्क्रीन"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s साठी ॲपशी संबंधित माहिती"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s साठी वापरासंबंधित सेटिंग्ज"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"नवीन विंडो"</string>
<string name="save_app_pair" msgid="5647523853662686243">"ॲपची जोडी सेव्ह करा"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"या ॲपची जोडीला या डिव्हाइसवर सपोर्ट नाही"</string>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index b548e67..5ddf8a2 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Skrin pisah"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Maklumat apl untuk %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Tetapan penggunaan sebanyak %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Tetingkap Baharu"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Simpan gandingan apl"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Gandingan apl ini tidak disokong pada peranti ini"</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index e94dd8e..948f2eb 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"မျက်နှာပြင် ခွဲ၍ပြသခြင်း"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s အတွက် အက်ပ်အချက်အလက်"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s အတွက် အသုံးပြုမှုဆက်တင်များ"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"ဝင်းဒိုးအသစ်"</string>
<string name="save_app_pair" msgid="5647523853662686243">"အက်ပ်တွဲချိတ်ခြင်း သိမ်းရန်"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"ဤအက်ပ်တွဲချိတ်ခြင်းကို ဤစက်တွင် ပံ့ပိုးမထားပါ"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 38003eb..0dc6160 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -31,8 +31,7 @@
<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="new_window_option_taskbar" msgid="6448780542727767211">"Nytt vindu"</string>
<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 93c4339..4fae68b 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"स्प्लिट स्क्रिन"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s का हकमा एपसम्बन्धी जानकारी"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s को प्रयोगसम्बन्धी सेटिङ"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"नयाँ विन्डो"</string>
<string name="save_app_pair" msgid="5647523853662686243">"एपको पेयर सेभ गर्नुहोस्"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"यस डिभाइसमा यो एप पेयर प्रयोग गर्न मिल्दैन"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 1c2bdc8..35f8d65 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -31,8 +31,7 @@
<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="new_window_option_taskbar" msgid="6448780542727767211">"Nieuw venster"</string>
<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 9a03926..abc0787 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"ସ୍କ୍ରିନକୁ ସ୍ପ୍ଲିଟ କରନ୍ତୁ"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s ପାଇଁ ଆପ ସୂଚନା"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s ପାଇଁ ବ୍ୟବହାର ସେଟିଂସ"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"ନୂଆ ୱିଣ୍ଡୋ"</string>
<string name="save_app_pair" msgid="5647523853662686243">"ଆପ ପେୟାର ସେଭ କରନ୍ତୁ"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"ଏହି ଆପ ପେୟାର ଏ ଡିଭାଇସରେ ସମର୍ଥିତ ନୁହେଁ"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 461513c..715f61b 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s ਲਈ ਐਪ ਜਾਣਕਾਰੀ"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s ਲਈ ਵਰਤੋਂ ਸੈਟਿੰਗਾਂ"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"ਨਵੀਂ ਵਿੰਡੋ"</string>
<string name="save_app_pair" msgid="5647523853662686243">"ਐਪ ਜੋੜਾਬੱਧ ਰੱਖਿਅਤ ਕਰੋ"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"ਇਸ ਐਪ ਜੋੜਾਬੱਧ ਦਾ ਇਸ ਡੀਵਾਈਸ \'ਤੇ ਸਮਰਥਨ ਨਹੀਂ ਕੀਤਾ ਜਾਂਦਾ"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index eeb9eb3..47f8a51 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -31,8 +31,7 @@
<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="new_window_option_taskbar" msgid="6448780542727767211">"Nowe okno"</string>
<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 c86c58d..d2b214e 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Ecrã dividido"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informações da app para %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Definições de utilização para %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Nova janela"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Guardar par de apps"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Este par de apps não é suportado neste dispositivo"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index f10b93a..f864b3e 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Tela dividida"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informações do app %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Configurações de uso de %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Nova janela"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Salvar par de apps"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Este Par de apps não está disponível no dispositivo"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index fbab69c..30e33dc 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -31,8 +31,7 @@
<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="new_window_option_taskbar" msgid="6448780542727767211">"Fereastră nouă"</string>
<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 aea3d2b..126e3ad 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Разделить экран"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Сведения о приложении \"%1$s\""</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Настройки использования приложения \"%1$s\""</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Новое окно"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Сохранить приложения"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Одновременно использовать эти два приложения на устройстве нельзя."</string>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index a3b9a71..328f2f5 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"බෙදුම් තිරය"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s සඳහා යෙදුම් තතු"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s සඳහා භාවිත සැකසීම්"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"නව කවුළුව"</string>
<string name="save_app_pair" msgid="5647523853662686243">"යෙදුම් යුගල සුරකින්න"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"මෙම යෙදුම් යුගලය මෙම උපාංගයෙහි සහාය නොදක්වයි"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index c3b3422..88cb1b1 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -31,8 +31,7 @@
<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="new_window_option_taskbar" msgid="6448780542727767211">"Nové okno"</string>
<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 51b0c7b..37a02e4 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Razdeljen zaslon"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Podatki o aplikaciji za: %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Nastavitve uporabe za »%1$s«"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Novo okno"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Shrani par aplikacij"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Ta par aplikacij ni podprt v tej napravi"</string>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index d3bfce6..5be991a 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -31,8 +31,7 @@
<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="new_window_option_taskbar" msgid="6448780542727767211">"Dritare e re"</string>
<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 c93519f..afaeb61 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Подељени екран"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Информације о апликацији за: %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Подешавања потрошње за %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Нови прозор"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Сачувај пар апликација"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Овај пар апликација није подржан на овом уређају"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index c92d6f0..547f60e 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Delad skärm"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Appinformation för %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Användningsinställningar för %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Nytt fönster"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Spara app-par"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"De här apparna som ska användas tillsammans stöds inte på den här enheten"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 7d2768b..71f8d8b 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -31,8 +31,7 @@
<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="new_window_option_taskbar" msgid="6448780542727767211">"Dirisha Jipya"</string>
<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 2ae4c30..e87e2eb 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"திரைப் பிரிப்பு"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$sக்கான ஆப்ஸ் தகவல்கள்"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$sக்கான உபயோக அமைப்புகள்"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"புதிய சாளரம்"</string>
<string name="save_app_pair" msgid="5647523853662686243">"ஆப்ஸ் ஜோடியைச் சேமி"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"இந்தச் சாதனத்தில் இந்த ஆப்ஸ் ஜோடி ஆதரிக்கப்படவில்லை"</string>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 5348a22..75dc7f0 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"స్ప్లిట్ స్క్రీన్"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s కోసం యాప్ సమాచారం"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$sకు సంబంధించిన వినియోగ సెట్టింగ్లు"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"కొత్త విండో"</string>
<string name="save_app_pair" msgid="5647523853662686243">"యాప్ పెయిర్ను సేవ్ చేయండి"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"ఈ పరికరంలో ఈ యాప్ పెయిర్ సపోర్ట్ చేయదు"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index a6d3b7e..50a0e52 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"แยกหน้าจอ"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"ข้อมูลแอปสำหรับ %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"การตั้งค่าการใช้งานสำหรับ %1$s"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"หน้าต่างใหม่"</string>
<string name="save_app_pair" msgid="5647523853662686243">"บันทึกคู่แอป"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"ไม่รองรับคู่แอปนี้ในอุปกรณ์เครื่องนี้"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 417962d..86ad330 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Split screen"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"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="new_window_option_taskbar" msgid="6448780542727767211">"Bagong Window"</string>
<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 0caa1f9..9955bbf 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -31,8 +31,7 @@
<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="new_window_option_taskbar" msgid="6448780542727767211">"Yeni Pencere"</string>
<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 e980d22..e777262 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Розділити екран"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Інформація про додаток для %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Параметри використання (%1$s)"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Нове вікно"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Зберегти пару додатків"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Ці два додатки не можна одночасно використовувати на цьому пристрої"</string>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 4d59e17..9fb85b2 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"اسپلٹ اسکرین"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s کے لیے ایپ کی معلومات"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s کیلئے استعمال کی ترتیبات"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"نئی ونڈو"</string>
<string name="save_app_pair" msgid="5647523853662686243">"ایپس کے جوڑے کو محفوظ کریں"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"ایپس کا یہ جوڑا اس آلے پر تعاون یافتہ نہیں ہے"</string>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index d2225c7..83cabc9 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -31,8 +31,7 @@
<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="new_window_option_taskbar" msgid="6448780542727767211">"Yangi oyna"</string>
<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 4c14574..d67f661 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -31,8 +31,7 @@
<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="new_window_option_taskbar" msgid="6448780542727767211">"Cửa sổ mới"</string>
<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 92591fb..1097e57 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"分屏"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s 的应用信息"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s的使用设置"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"新窗口"</string>
<string name="save_app_pair" msgid="5647523853662686243">"保存应用组合"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"在该设备上无法使用此应用对"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index 05f3320..6471a9a 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"分割螢幕"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s 的應用程式資料"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"「%1$s」的用量設定"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"新視窗"</string>
<string name="save_app_pair" msgid="5647523853662686243">"儲存應用程式配對"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"此裝置不支援此應用程式配對"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 6ee3e99..0a9ffa2 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -31,8 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"分割畫面"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"「%1$s」的應用程式資訊"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"「%1$s」的用量設定"</string>
- <!-- no translation found for new_window_option_taskbar (6448780542727767211) -->
- <skip />
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"新視窗"</string>
<string name="save_app_pair" msgid="5647523853662686243">"儲存應用程式配對"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"這部裝置不支援這組應用程式配對"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 8d2e2f3..59c99c4 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -31,8 +31,7 @@
<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="new_window_option_taskbar" msgid="6448780542727767211">"Iwindi Elisha"</string>
<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/dimens.xml b/res/values/dimens.xml
index f8c075f..d1e905d 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -126,6 +126,10 @@
<dimen name="all_apps_work_profile_tab_footer_bottom_padding">20dp</dimen>
<dimen name="all_apps_tabs_button_horizontal_padding">4dp</dimen>
<dimen name="all_apps_tabs_vertical_padding">6dp</dimen>
+ <dimen name="all_apps_tabs_vertical_padding_focus">1dp</dimen>
+ <dimen name="all_apps_tabs_focus_width">5dp</dimen>
+ <dimen name="all_apps_tabs_focus_border">3dp</dimen>
+ <dimen name="all_apps_tabs_focus_padding">2dp</dimen>
<dimen name="all_apps_tabs_margin_top">8dp</dimen>
<dimen name="all_apps_divider_height">2dp</dimen>
<dimen name="all_apps_divider_width">128dp</dimen>
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 2e75261..3774ae3 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -306,6 +306,10 @@
removeActivityFlags(ACTIVITY_STATE_RESUMED | ACTIVITY_STATE_DEFERRED_RESUMED);
}
+ public boolean isPaused() {
+ return !hasBeenResumed() && (mActivityFlags & ACTIVITY_STATE_DEFERRED_RESUMED) == 0;
+ }
+
/**
* Sets the activity to appear as resumed.
*/
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 8121e53..76dc770 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -220,7 +220,7 @@
private CancellableTask mIconLoadRequest;
- private boolean mEnableIconUpdateAnimation = false;
+ private boolean mHighResUpdateInProgress = false;
public BubbleTextView(Context context) {
this(context, null, 0);
@@ -1195,10 +1195,6 @@
}
}
- protected boolean iconUpdateAnimationEnabled() {
- return mEnableIconUpdateAnimation;
- }
-
protected void applyCompoundDrawables(Drawable icon) {
if (icon == null) {
// Icon can be null when we use the BubbleTextView for text only.
@@ -1216,7 +1212,7 @@
// If the current icon is a placeholder color, animate its update.
if (mIcon != null
&& mIcon instanceof PlaceHolderIconDrawable
- && iconUpdateAnimationEnabled()) {
+ && mHighResUpdateInProgress) {
((PlaceHolderIconDrawable) mIcon).animateIconUpdate(icon);
}
@@ -1238,7 +1234,7 @@
if (getTag() == info) {
mIconLoadRequest = null;
mDisableRelayout = true;
- mEnableIconUpdateAnimation = true;
+ mHighResUpdateInProgress = true;
// Optimization: Starting in N, pre-uploads the bitmap to RenderThread.
info.bitmap.icon.prepareToDraw();
@@ -1253,7 +1249,7 @@
}
mDisableRelayout = false;
- mEnableIconUpdateAnimation = false;
+ mHighResUpdateInProgress = false;
}
}
@@ -1265,7 +1261,7 @@
mIconLoadRequest.cancel();
mIconLoadRequest = null;
}
- if (getTag() instanceof ItemInfoWithIcon) {
+ if (getTag() instanceof ItemInfoWithIcon && !mHighResUpdateInProgress) {
ItemInfoWithIcon info = (ItemInfoWithIcon) getTag();
if (info.usingLowResIcon()) {
mIconLoadRequest = LauncherAppState.getInstance(getContext()).getIconCache()
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 483f5f8..afe0ee1 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,6 +66,7 @@
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.ResourceHelper;
@@ -219,6 +222,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 +238,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 +696,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 =
@@ -1830,19 +1829,14 @@
* Returns the new border space that should be used between hotseat icons after adjusting it to
* the bubble bar.
*
+ * <p>Does not check for visible bubbles persistence, so caller should call
+ * {@link #shouldAdjustHotseatForBubbleBar} first.
+ *
* <p>If there's no adjustment needed, this method returns {@code 0}.
+ * @see #shouldAdjustHotseatForBubbleBar(Context, boolean)
*/
public float getHotseatAdjustedBorderSpaceForBubbleBar(Context context) {
- // only need to adjust when QSB is on top of the hotseat.
- if (isQsbInline) {
- return 0;
- }
-
- // no need to adjust if there's enough space for the bubble bar to the right of the hotseat.
- if (getHotseatLayoutPadding(context).right > mBubbleBarSpaceThresholdPx) {
- return 0;
- }
-
+ if (!shouldAdjustHotseatForBubbleBar(context)) return 0;
// The adjustment is shrinking the hotseat's width by 1 icon on either side.
int iconsWidth =
iconSizePx * numShownHotseatIcons + hotseatBorderSpace * (numShownHotseatIcons - 1);
@@ -1852,6 +1846,33 @@
}
/**
+ * Returns the hotseat icon translation X for the cellX index.
+ *
+ * <p>Does not check for visible bubbles persistence, so caller should call
+ * {@link #shouldAdjustHotseatForBubbleBar} first.
+ *
+ * <p>If there's no adjustment needed, this method returns {@code 0}.
+ * @see #shouldAdjustHotseatForBubbleBar(Context, boolean)
+ */
+ public float getHotseatAdjustedTranslation(Context context, int cellX) {
+ if (!shouldAdjustHotseatForBubbleBar(context)) return 0;
+ float borderSpace = getHotseatAdjustedBorderSpaceForBubbleBar(context);
+ float borderSpaceDelta = borderSpace - hotseatBorderSpace;
+ return iconSizePx + cellX * borderSpaceDelta;
+ }
+
+ /** Returns whether hotseat should be adjusted for the bubble bar. */
+ public boolean shouldAdjustHotseatForBubbleBar(Context context, boolean hasBubbles) {
+ return hasBubbles && shouldAdjustHotseatForBubbleBar(context);
+ }
+
+ private boolean shouldAdjustHotseatForBubbleBar(Context context) {
+ // only need to adjust if bubble bar is enabled, when QSB is on top of the hotseat and
+ // there's not enough space for the bubble bar to the right of the hotseat.
+ return !isQsbInline && getHotseatLayoutPadding(context).right <= mBubbleBarSpaceThresholdPx;
+ }
+
+ /**
* Returns the padding for hotseat view
*/
public Rect getHotseatLayoutPadding(Context context) {
@@ -2214,6 +2235,10 @@
mHotseatBarEdgePaddingPx));
writer.println(prefix + pxToDpStr("mHotseatBarWorkspaceSpacePx",
mHotseatBarWorkspaceSpacePx));
+ writer.println(prefix
+ + pxToDpStr("inlineNavButtonsEndSpacingPx", inlineNavButtonsEndSpacingPx));
+ writer.println(prefix
+ + pxToDpStr("navButtonsLayoutWidthPx", navButtonsLayoutWidthPx));
writer.println(prefix + pxToDpStr("hotseatBarEndOffset", hotseatBarEndOffset));
writer.println(prefix + pxToDpStr("hotseatQsbSpace", hotseatQsbSpace));
writer.println(prefix + pxToDpStr("hotseatQsbHeight", hotseatQsbHeight));
@@ -2328,6 +2353,24 @@
}
/**
+ * Returns whether Taskbar and Hotseat should adjust horizontally on bubble bar location update.
+ */
+ public boolean shouldAdjustHotseatOnBubblesLocationUpdate(Context context) {
+ return enableBubbleBar()
+ && enableBubbleBarInPersistentTaskBar()
+ && !DisplayController.getNavigationMode(context).hasGestures;
+ }
+
+ /** Returns hotseat translation X for the bubble bar position. */
+ public int getHotseatTranslationXForBubbleBar(boolean isNavbarOnRight, boolean isRtl) {
+ if (isNavbarOnRight) {
+ return isRtl ? -navButtonsLayoutWidthPx : 0;
+ } else {
+ return isRtl ? 0 : navButtonsLayoutWidthPx;
+ }
+ }
+
+ /**
* Callback when a component changes the DeviceProfile associated with it, as a result of
* configuration change
*/
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 024dde4..6b478be 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
*/
@@ -118,12 +149,9 @@
DeviceProfile dp = mActivity.getDeviceProfile();
if (bubbleBarEnabled) {
- float adjustedBorderSpace = dp.getHotseatAdjustedBorderSpaceForBubbleBar(getContext());
- if (hasBubbles && Float.compare(adjustedBorderSpace, 0f) != 0) {
- getShortcutsAndWidgets().setTranslationProvider(cellX -> {
- float borderSpaceDelta = adjustedBorderSpace - dp.hotseatBorderSpace;
- return dp.iconSizePx + cellX * borderSpaceDelta;
- });
+ if (dp.shouldAdjustHotseatForBubbleBar(getContext(), hasBubbles)) {
+ getShortcutsAndWidgets().setTranslationProvider(
+ cellX -> dp.getHotseatAdjustedTranslation(getContext(), cellX));
if (mQsb instanceof HorizontalInsettableView) {
HorizontalInsettableView insettableQsb = (HorizontalInsettableView) mQsb;
final float insetFraction = (float) dp.iconSizePx / dp.hotseatQsbWidth;
@@ -158,25 +186,24 @@
*/
public void adjustForBubbleBar(boolean isBubbleBarVisible) {
DeviceProfile dp = mActivity.getDeviceProfile();
- float adjustedBorderSpace = dp.getHotseatAdjustedBorderSpaceForBubbleBar(getContext());
- if (Float.compare(adjustedBorderSpace, 0f) == 0) {
+ if (!dp.shouldAdjustHotseatForBubbleBar(getContext(), isBubbleBarVisible)) {
return;
}
ShortcutAndWidgetContainer icons = getShortcutsAndWidgets();
AnimatorSet animatorSet = new AnimatorSet();
- float borderSpaceDelta = adjustedBorderSpace - dp.hotseatBorderSpace;
// update the translation provider for future layout passes of hotseat icons.
if (isBubbleBarVisible) {
- icons.setTranslationProvider(cellX -> dp.iconSizePx + cellX * borderSpaceDelta);
+ icons.setTranslationProvider(
+ cellX -> dp.getHotseatAdjustedTranslation(getContext(), cellX));
} else {
icons.setTranslationProvider(null);
}
for (int i = 0; i < icons.getChildCount(); i++) {
View child = icons.getChildAt(i);
- float tx = isBubbleBarVisible ? dp.iconSizePx + i * borderSpaceDelta : 0;
+ float tx = isBubbleBarVisible ? dp.getHotseatAdjustedTranslation(getContext(), i) : 0;
if (child instanceof Reorderable) {
MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate();
animatorSet.play(
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index b0ec9b0..a8840fe 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -412,6 +412,7 @@
private final List<BackPressHandler> mBackPressedHandlers = new ArrayList<>();
private boolean mIsColdStartupAfterReboot;
+ private boolean mForceConfigUpdate;
private boolean mIsNaturalScrollingEnabled;
@@ -756,7 +757,7 @@
protected void onHandleConfigurationChanged() {
Trace.beginSection("Launcher#onHandleconfigurationChanged");
try {
- if (!initDeviceProfile(mDeviceProfile.inv)) {
+ if (!initDeviceProfile(mDeviceProfile.inv) && !mForceConfigUpdate) {
return;
}
@@ -770,6 +771,7 @@
mModel.rebindCallbacks();
updateDisallowBack();
} finally {
+ mForceConfigUpdate = false;
Trace.endSection();
}
}
@@ -1872,13 +1874,18 @@
}
}
- // Exit spring loaded mode if necessary after adding the widget
- Runnable onComplete = MULTI_SELECT_EDIT_MODE.get() ? null
- : () -> mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
+ // Exit spring loaded mode if necessary after adding the widget; unless config activity was
+ // started.
+ Runnable onComplete = MULTI_SELECT_EDIT_MODE.get() ? null : () -> mStateManager.goToState(
+ NORMAL, SPRING_LOADED_EXIT_DELAY);
completeAddAppWidget(appWidgetId, info, boundWidget,
addFlowHandler.getProviderInfo(this), addFlowHandler.needsConfigure(),
false, widgetPreviewBitmap);
- mWorkspace.removeExtraEmptyScreenDelayed(delay, false, onComplete);
+ // Remove extra screen if widget drop concluded. If a config activity was started, extra
+ // screen will be removed when we get back its result.
+ if (!isActivityStarted) {
+ mWorkspace.removeExtraEmptyScreenDelayed(delay, false, onComplete);
+ }
}
public void addPendingItem(PendingAddItemInfo info, int container, int screenId,
@@ -3146,6 +3153,13 @@
return mAnimationCoordinator;
}
+ /**
+ * Set to force config update when set to true next time onHandleConfigurationChanged is called.
+ */
+ public void setForceConfigUpdate(boolean forceConfigUpdate) {
+ mForceConfigUpdate = forceConfigUpdate;
+ }
+
@Override
public View.OnLongClickListener getAllAppsItemLongClickListener() {
return ItemLongClickListener.INSTANCE_ALL_APPS;
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 15641ab..42a28d6 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -176,7 +176,7 @@
() -> LauncherPrefs.get(mContext).removeListener(observer, THEMED_ICONS));
InstallSessionTracker installSessionTracker =
- InstallSessionHelper.INSTANCE.get(context).registerInstallTracker(mModel);
+ InstallSessionHelper.INSTANCE.get(context).registerInstallTracker(callbacks);
mOnTerminateCallback.add(installSessionTracker::unregister);
});
@@ -266,7 +266,7 @@
}
private class IconObserver
- implements IconProvider.IconChangeListener, OnSharedPreferenceChangeListener {
+ implements IconProvider.IconChangeListener, LauncherPrefChangeListener {
@Override
public void onAppIconChanged(String packageName, UserHandle user) {
@@ -288,7 +288,7 @@
}
@Override
- public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+ public void onPrefChanged(String key) {
if (Themes.KEY_THEMED_ICONS.equals(key)) {
mIconProvider.setIconThemeSupported(Themes.isThemedIconEnabled(mContext));
verifyIconChanged();
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
deleted file mode 100644
index 7ad17d9..0000000
--- a/src/com/android/launcher3/LauncherModel.java
+++ /dev/null
@@ -1,692 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
-
-import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD;
-import static com.android.launcher3.LauncherPrefs.WORK_EDU_STEP;
-import static com.android.launcher3.config.FeatureFlags.IS_STUDIO_BUILD;
-import static com.android.launcher3.icons.cache.BaseIconCache.EMPTY_CLASS_NAME;
-import static com.android.launcher3.model.PackageUpdatedTask.OP_UPDATE;
-import static com.android.launcher3.pm.UserCache.ACTION_PROFILE_AVAILABLE;
-import static com.android.launcher3.pm.UserCache.ACTION_PROFILE_UNAVAILABLE;
-import static com.android.launcher3.testing.shared.TestProtocol.sDebugTracing;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageInstaller;
-import android.content.pm.ShortcutInfo;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Pair;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.WorkerThread;
-
-import com.android.launcher3.celllayout.CellPosMapper;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.model.AddWorkspaceItemsTask;
-import com.android.launcher3.model.AllAppsList;
-import com.android.launcher3.model.BaseLauncherBinder;
-import com.android.launcher3.model.BgDataModel;
-import com.android.launcher3.model.BgDataModel.Callbacks;
-import com.android.launcher3.model.CacheDataUpdatedTask;
-import com.android.launcher3.model.ItemInstallQueue;
-import com.android.launcher3.model.LoaderTask;
-import com.android.launcher3.model.ModelDbController;
-import com.android.launcher3.model.ModelDelegate;
-import com.android.launcher3.model.ModelLauncherCallbacks;
-import com.android.launcher3.model.ModelTaskController;
-import com.android.launcher3.model.ModelWriter;
-import com.android.launcher3.model.PackageInstallStateChangedTask;
-import com.android.launcher3.model.PackageUpdatedTask;
-import com.android.launcher3.model.ReloadStringCacheTask;
-import com.android.launcher3.model.ShortcutsChangedTask;
-import com.android.launcher3.model.UserLockStateChangedTask;
-import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.pm.InstallSessionTracker;
-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;
-import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.util.Preconditions;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.concurrent.CancellationException;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-
-/**
- * Maintains in-memory state of the Launcher. It is expected that there should be only one
- * LauncherModel object held in a static. Also provide APIs for updating the database state
- * for the Launcher.
- */
-public class LauncherModel implements InstallSessionTracker.Callback {
- private static final boolean DEBUG_RECEIVER = false;
-
- static final String TAG = "Launcher.Model";
-
- @NonNull
- private final LauncherAppState mApp;
- @NonNull
- private final PackageManagerHelper mPmHelper;
- @NonNull
- private final ModelDbController mModelDbController;
- @NonNull
- private final Object mLock = new Object();
- @Nullable
- private LoaderTask mLoaderTask;
- private boolean mIsLoaderTaskRunning;
-
- // only allow this once per reboot to reload work apps
- private boolean mShouldReloadWorkProfile = true;
-
- // Indicates whether the current model data is valid or not.
- // We start off with everything not loaded. After that, we assume that
- // our monitoring of the package manager provides all updates and we never
- // need to do a requery. This is only ever touched from the loader thread.
- private boolean mModelLoaded;
- private boolean mModelDestroyed = false;
- public boolean isModelLoaded() {
- synchronized (mLock) {
- return mModelLoaded && mLoaderTask == null && !mModelDestroyed;
- }
- }
-
- @NonNull
- private final ArrayList<Callbacks> mCallbacksList = new ArrayList<>(1);
-
- // < only access in worker thread >
- @NonNull
- private final AllAppsList mBgAllAppsList;
-
- /**
- * All the static data should be accessed on the background thread, A lock should be acquired
- * on this object when accessing any data from this model.
- */
- @NonNull
- private final BgDataModel mBgDataModel = new BgDataModel();
-
- @NonNull
- private final ModelDelegate mModelDelegate;
-
- private int mLastLoadId = -1;
-
- // Runnable to check if the shortcuts permission has changed.
- @NonNull
- private final Runnable mDataValidationCheck = new Runnable() {
- @Override
- public void run() {
- if (mModelLoaded) {
- mModelDelegate.validateData();
- }
- }
- };
-
- LauncherModel(@NonNull final Context context, @NonNull final LauncherAppState app,
- @NonNull final IconCache iconCache, @NonNull final AppFilter appFilter,
- @NonNull final PackageManagerHelper pmHelper, final boolean isPrimaryInstance) {
- mApp = app;
- mPmHelper = pmHelper;
- mModelDbController = new ModelDbController(context);
- mBgAllAppsList = new AllAppsList(iconCache, appFilter);
- mModelDelegate = ModelDelegate.newInstance(context, app, mPmHelper, mBgAllAppsList,
- mBgDataModel, isPrimaryInstance);
- }
-
- @NonNull
- public ModelDelegate getModelDelegate() {
- return mModelDelegate;
- }
-
- public ModelDbController getModelDbController() {
- return mModelDbController;
- }
-
- public ModelLauncherCallbacks newModelCallbacks() {
- return new ModelLauncherCallbacks(this::enqueueModelUpdateTask);
- }
-
- /**
- * Adds the provided items to the workspace.
- */
- public void addAndBindAddedWorkspaceItems(
- @NonNull final List<Pair<ItemInfo, Object>> itemList) {
- for (Callbacks cb : getCallbacks()) {
- cb.preAddApps();
- }
- enqueueModelUpdateTask(new AddWorkspaceItemsTask(itemList));
- }
-
- @NonNull
- public ModelWriter getWriter(final boolean verifyChanges, CellPosMapper cellPosMapper,
- @Nullable final Callbacks owner) {
- return new ModelWriter(mApp.getContext(), this, mBgDataModel, verifyChanges, cellPosMapper,
- owner);
- }
-
- /**
- * Called when the icon for an app changes, outside of package event
- */
- @WorkerThread
- public void onAppIconChanged(@NonNull final String packageName,
- @NonNull final UserHandle user) {
- // Update the icon for the calendar package
- Context context = mApp.getContext();
- enqueueModelUpdateTask(new PackageUpdatedTask(OP_UPDATE, user, packageName));
-
- List<ShortcutInfo> pinnedShortcuts = new ShortcutRequest(context, user)
- .forPackage(packageName).query(ShortcutRequest.PINNED);
- if (!pinnedShortcuts.isEmpty()) {
- enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, pinnedShortcuts, user,
- false));
- }
- }
-
- /**
- * Called when the workspace items have drastically changed
- */
- public void onWorkspaceUiChanged() {
- MODEL_EXECUTOR.execute(mModelDelegate::workspaceLoadComplete);
- }
-
- /**
- * Called when the model is destroyed
- */
- public void destroy() {
- mModelDestroyed = true;
- MODEL_EXECUTOR.execute(mModelDelegate::destroy);
- }
-
- public void onBroadcastIntent(@NonNull final Intent intent) {
- if (DEBUG_RECEIVER || sDebugTracing) Log.d(TAG, "onReceive intent=" + intent);
- final String action = intent.getAction();
- if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
- // If we have changed locale we need to clear out the labels in all apps/workspace.
- forceReload();
- } else if (ACTION_DEVICE_POLICY_RESOURCE_UPDATED.equals(action)) {
- enqueueModelUpdateTask(new ReloadStringCacheTask(mModelDelegate));
- } else if (IS_STUDIO_BUILD && ACTION_FORCE_ROLOAD.equals(action)) {
- for (Callbacks cb : getCallbacks()) {
- if (cb instanceof Launcher) {
- ((Launcher) cb).recreate();
- }
- }
- }
- }
-
- /**
- * Called then there use a user event
- * @see UserCache#addUserEventListener
- */
- public void onUserEvent(UserHandle user, String action) {
- if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action)
- && mShouldReloadWorkProfile) {
- mShouldReloadWorkProfile = false;
- forceReload();
- } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action)
- || Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
- mShouldReloadWorkProfile = false;
- enqueueModelUpdateTask(new PackageUpdatedTask(
- PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user));
- } else if (UserCache.ACTION_PROFILE_LOCKED.equals(action)
- || UserCache.ACTION_PROFILE_UNLOCKED.equals(action)) {
- enqueueModelUpdateTask(new UserLockStateChangedTask(
- user, UserCache.ACTION_PROFILE_UNLOCKED.equals(action)));
- } else if (UserCache.ACTION_PROFILE_ADDED.equals(action)
- || UserCache.ACTION_PROFILE_REMOVED.equals(action)) {
- forceReload();
- } else if (ACTION_PROFILE_AVAILABLE.equals(action)
- || ACTION_PROFILE_UNAVAILABLE.equals(action)) {
- /*
- * This broadcast is only available when android.os.Flags.allowPrivateProfile() is set.
- * For Work-profile this broadcast will be sent in addition to
- * ACTION_MANAGED_PROFILE_AVAILABLE/UNAVAILABLE.
- * So effectively, this if block only handles the non-work profile case.
- */
- enqueueModelUpdateTask(new PackageUpdatedTask(
- PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user));
- }
- if (Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
- LauncherPrefs.get(mApp.getContext()).put(WORK_EDU_STEP, 0);
- }
- }
-
- /**
- * Reloads the workspace items from the DB and re-binds the workspace. This should generally
- * not be called as DB updates are automatically followed by UI update
- */
- public void forceReload() {
- synchronized (mLock) {
- // Stop any existing loaders first, so they don't set mModelLoaded to true later
- stopLoader();
- mModelLoaded = false;
- }
-
- // Start the loader if launcher is already running, otherwise the loader will run,
- // the next time launcher starts
- if (hasCallbacks()) {
- startLoader();
- }
- }
-
- /**
- * Rebinds all existing callbacks with already loaded model
- */
- public void rebindCallbacks() {
- if (hasCallbacks()) {
- startLoader();
- }
- }
-
- /**
- * Removes an existing callback
- */
- public void removeCallbacks(@NonNull final Callbacks callbacks) {
- synchronized (mCallbacksList) {
- Preconditions.assertUIThread();
- if (mCallbacksList.remove(callbacks)) {
- if (stopLoader()) {
- // Rebind existing callbacks
- startLoader();
- }
- }
- }
- }
-
- /**
- * Adds a callbacks to receive model updates
- * @return true if workspace load was performed synchronously
- */
- public boolean addCallbacksAndLoad(@NonNull final Callbacks callbacks) {
- synchronized (mLock) {
- addCallbacks(callbacks);
- return startLoader(new Callbacks[] { callbacks });
-
- }
- }
-
- /**
- * Adds a callbacks to receive model updates
- */
- public void addCallbacks(@NonNull final Callbacks callbacks) {
- Preconditions.assertUIThread();
- synchronized (mCallbacksList) {
- mCallbacksList.add(callbacks);
- }
- }
-
- /**
- * Starts the loader. Tries to bind {@params synchronousBindPage} synchronously if possible.
- * @return true if the page could be bound synchronously.
- */
- public boolean startLoader() {
- return startLoader(new Callbacks[0]);
- }
-
- private boolean startLoader(@NonNull final Callbacks[] newCallbacks) {
- // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
- ItemInstallQueue.INSTANCE.get(mApp.getContext())
- .pauseModelPush(ItemInstallQueue.FLAG_LOADER_RUNNING);
- synchronized (mLock) {
- // If there is already one running, tell it to stop.
- boolean wasRunning = stopLoader();
- boolean bindDirectly = mModelLoaded && !mIsLoaderTaskRunning;
- boolean bindAllCallbacks = wasRunning || !bindDirectly || newCallbacks.length == 0;
- final Callbacks[] callbacksList = bindAllCallbacks ? getCallbacks() : newCallbacks;
-
- if (callbacksList.length > 0) {
- // Clear any pending bind-runnables from the synchronized load process.
- for (Callbacks cb : callbacksList) {
- MAIN_EXECUTOR.execute(cb::clearPendingBinds);
- }
-
- BaseLauncherBinder launcherBinder = new BaseLauncherBinder(
- mApp, mBgDataModel, mBgAllAppsList, callbacksList);
- if (bindDirectly) {
- // Divide the set of loaded items into those that we are binding synchronously,
- // and everything else that is to be bound normally (asynchronously).
- launcherBinder.bindWorkspace(bindAllCallbacks, /* isBindSync= */ true);
- // For now, continue posting the binding of AllApps as there are other
- // issues that arise from that.
- launcherBinder.bindAllApps();
- launcherBinder.bindDeepShortcuts();
- launcherBinder.bindWidgets();
- if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
- mModelDelegate.bindAllModelExtras(callbacksList);
- }
- return true;
- } else {
- stopLoader();
- mLoaderTask = new LoaderTask(
- mApp, mBgAllAppsList, mBgDataModel, mModelDelegate, launcherBinder);
-
- // Always post the loader task, instead of running directly
- // (even on same thread) so that we exit any nested synchronized blocks
- MODEL_EXECUTOR.post(mLoaderTask);
- }
- }
- }
- return false;
- }
-
- /**
- * If there is already a loader task running, tell it to stop.
- * @return true if an existing loader was stopped.
- */
- private boolean stopLoader() {
- synchronized (mLock) {
- LoaderTask oldTask = mLoaderTask;
- mLoaderTask = null;
- if (oldTask != null) {
- oldTask.stopLocked();
- return true;
- }
- return false;
- }
- }
-
- /**
- * Loads the model if not loaded
- * @param callback called with the data model upon successful load or null on model thread.
- */
- public void loadAsync(@NonNull final Consumer<BgDataModel> callback) {
- synchronized (mLock) {
- if (!mModelLoaded && !mIsLoaderTaskRunning) {
- startLoader();
- }
- }
- MODEL_EXECUTOR.post(() -> callback.accept(isModelLoaded() ? mBgDataModel : null));
- }
-
- @Override
- public void onInstallSessionCreated(@NonNull final PackageInstallInfo sessionInfo) {
- if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
- enqueueModelUpdateTask((taskController, dataModel, apps) -> {
- apps.addPromiseApp(mApp.getContext(), sessionInfo);
- taskController.bindApplicationsIfNeeded();
- });
- }
- }
-
- @Override
- public void onSessionFailure(@NonNull final String packageName,
- @NonNull final UserHandle user) {
- enqueueModelUpdateTask((taskController, dataModel, apps) -> {
- IconCache iconCache = mApp.getIconCache();
- final IntSet removedIds = new IntSet();
- HashSet<WorkspaceItemInfo> archivedWorkspaceItemsToCacheRefresh = new HashSet<>();
- 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
- // failure.
- mApp.getIconCache().remove(
- new ComponentName(packageName, packageName + EMPTY_CLASS_NAME),
- user);
- }
-
- for (ItemInfo info : dataModel.itemsIdMap) {
- if (info instanceof WorkspaceItemInfo
- && ((WorkspaceItemInfo) info).hasPromiseIconUi()
- && user.equals(info.user)
- && info.getIntent() != null) {
- if (TextUtils.equals(packageName, info.getIntent().getPackage())) {
- removedIds.add(info.id);
- }
- if (((WorkspaceItemInfo) info).isArchived()) {
- WorkspaceItemInfo workspaceItem = (WorkspaceItemInfo) info;
- // Refresh icons on the workspace for archived apps.
- iconCache.getTitleAndIcon(workspaceItem,
- workspaceItem.usingLowResIcon());
- archivedWorkspaceItemsToCacheRefresh.add(workspaceItem);
- }
- }
- }
-
- if (isAppArchived) {
- apps.updateIconsAndLabels(new HashSet<>(List.of(packageName)), user);
- }
- }
-
- if (!removedIds.isEmpty() && !isAppArchived) {
- taskController.deleteAndBindComponentsRemoved(
- ItemInfoMatcher.ofItemIds(removedIds),
- "removed because install session failed");
- }
- if (!archivedWorkspaceItemsToCacheRefresh.isEmpty()) {
- taskController.bindUpdatedWorkspaceItems(
- archivedWorkspaceItemsToCacheRefresh.stream().toList());
- }
- if (isAppArchived) {
- taskController.bindApplicationsIfNeeded();
- }
- });
- }
-
- @Override
- public void onPackageStateChanged(@NonNull final PackageInstallInfo installInfo) {
- enqueueModelUpdateTask(new PackageInstallStateChangedTask(installInfo));
- }
-
- /**
- * Updates the icons and label of all pending icons for the provided package name.
- */
- @Override
- public void onUpdateSessionDisplay(@NonNull final PackageUserKey key,
- @NonNull final PackageInstaller.SessionInfo info) {
- mApp.getIconCache().updateSessionCache(key, info);
-
- HashSet<String> packages = new HashSet<>();
- packages.add(key.mPackageName);
- enqueueModelUpdateTask(new CacheDataUpdatedTask(
- CacheDataUpdatedTask.OP_SESSION_UPDATE, key.mUser, packages));
- }
-
- public class LoaderTransaction implements AutoCloseable {
-
- @NonNull
- private final LoaderTask mTask;
-
- private LoaderTransaction(@NonNull final LoaderTask task) throws CancellationException {
- synchronized (mLock) {
- if (mLoaderTask != task) {
- throw new CancellationException("Loader already stopped");
- }
- mLastLoadId++;
- mTask = task;
- mIsLoaderTaskRunning = true;
- mModelLoaded = false;
- }
- }
-
- public void commit() {
- synchronized (mLock) {
- // Everything loaded bind the data.
- mModelLoaded = true;
- }
- }
-
- @Override
- public void close() {
- synchronized (mLock) {
- // If we are still the last one to be scheduled, remove ourselves.
- if (mLoaderTask == mTask) {
- mLoaderTask = null;
- }
- mIsLoaderTaskRunning = false;
- }
- }
- }
-
- public LoaderTransaction beginLoader(@NonNull final LoaderTask task)
- throws CancellationException {
- return new LoaderTransaction(task);
- }
-
- /**
- * Refreshes the cached shortcuts if the shortcut permission has changed.
- * Current implementation simply reloads the workspace, but it can be optimized to
- * use partial updates similar to {@link UserCache}
- */
- public void validateModelDataOnResume() {
- MODEL_EXECUTOR.getHandler().removeCallbacks(mDataValidationCheck);
- MODEL_EXECUTOR.post(mDataValidationCheck);
- }
-
- /**
- * Called when the icons for packages have been updated in the icon cache.
- */
- public void onPackageIconsUpdated(@NonNull final HashSet<String> updatedPackages,
- @NonNull final UserHandle user) {
- // If any package icon has changed (app was updated while launcher was dead),
- // update the corresponding shortcuts.
- enqueueModelUpdateTask(new CacheDataUpdatedTask(
- CacheDataUpdatedTask.OP_CACHE_UPDATE, user, updatedPackages));
- }
-
- /**
- * Called when the labels for the widgets has updated in the icon cache.
- */
- public void onWidgetLabelsUpdated(@NonNull final HashSet<String> updatedPackages,
- @NonNull final UserHandle user) {
- enqueueModelUpdateTask((taskController, dataModel, apps) -> {
- dataModel.widgetsModel.onPackageIconsUpdated(updatedPackages, user, mApp);
- taskController.bindUpdatedWidgets(dataModel);
- });
- }
-
- public void enqueueModelUpdateTask(@NonNull final ModelUpdateTask task) {
- if (mModelDestroyed) {
- return;
- }
- MODEL_EXECUTOR.execute(() -> {
- if (!isModelLoaded()) {
- // Loader has not yet run.
- return;
- }
- ModelTaskController controller = new ModelTaskController(
- mApp, mBgDataModel, mBgAllAppsList, this, MAIN_EXECUTOR);
- task.execute(controller, mBgDataModel, mBgAllAppsList);
- });
- }
-
- /**
- * A task to be executed on the current callbacks on the UI thread.
- * If there is no current callbacks, the task is ignored.
- */
- public interface CallbackTask {
-
- void execute(@NonNull Callbacks callbacks);
- }
-
- public interface ModelUpdateTask {
-
- void execute(@NonNull ModelTaskController taskController,
- @NonNull BgDataModel dataModel, @NonNull AllAppsList apps);
- }
-
- public void updateAndBindWorkspaceItem(@NonNull final WorkspaceItemInfo si,
- @NonNull final ShortcutInfo info) {
- updateAndBindWorkspaceItem(() -> {
- si.updateFromDeepShortcutInfo(info, mApp.getContext());
- mApp.getIconCache().getShortcutIcon(si, info);
- return si;
- });
- }
-
- /**
- * Utility method to update a shortcut on the background thread.
- */
- public void updateAndBindWorkspaceItem(
- @NonNull final Supplier<WorkspaceItemInfo> itemProvider) {
- enqueueModelUpdateTask((taskController, dataModel, apps) -> {
- WorkspaceItemInfo info = itemProvider.get();
- taskController.getModelWriter().updateItemInDatabase(info);
- ArrayList<WorkspaceItemInfo> update = new ArrayList<>();
- update.add(info);
- taskController.bindUpdatedWorkspaceItems(update);
- });
- }
-
- public void refreshAndBindWidgetsAndShortcuts(@Nullable final PackageUserKey packageUser) {
- enqueueModelUpdateTask((taskController, dataModel, apps) -> {
- dataModel.widgetsModel.update(taskController.getApp(), packageUser);
- taskController.bindUpdatedWidgets(dataModel);
- });
- }
-
- public void dumpState(@Nullable final String prefix, @Nullable final FileDescriptor fd,
- @NonNull final PrintWriter writer, @NonNull final String[] args) {
- if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
- writer.println(prefix + "All apps list: size=" + mBgAllAppsList.data.size());
- for (AppInfo info : mBgAllAppsList.data) {
- writer.println(prefix + " title=\"" + info.title
- + "\" bitmapIcon=" + info.bitmap.icon
- + " componentName=" + info.componentName.getPackageName());
- }
- writer.println();
- }
- mModelDelegate.dump(prefix, fd, writer, args);
- mBgDataModel.dump(prefix, fd, writer, args);
- }
-
- /**
- * Returns true if there are any callbacks attached to the model
- */
- public boolean hasCallbacks() {
- synchronized (mCallbacksList) {
- return !mCallbacksList.isEmpty();
- }
- }
-
- /**
- * Returns an array of currently attached callbacks
- */
- @NonNull
- public Callbacks[] getCallbacks() {
- synchronized (mCallbacksList) {
- return mCallbacksList.toArray(new Callbacks[mCallbacksList.size()]);
- }
- }
-
- /**
- * Returns the ID for the last model load. If the load ID doesn't match for a transaction, the
- * transaction should be ignored.
- */
- public int getLastLoadId() {
- return mLastLoadId;
- }
-}
diff --git a/src/com/android/launcher3/LauncherModel.kt b/src/com/android/launcher3/LauncherModel.kt
new file mode 100644
index 0000000..a013eaa
--- /dev/null
+++ b/src/com/android/launcher3/LauncherModel.kt
@@ -0,0 +1,499 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3
+
+import android.app.admin.DevicePolicyManager
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ShortcutInfo
+import android.os.UserHandle
+import android.text.TextUtils
+import android.util.Log
+import android.util.Pair
+import androidx.annotation.WorkerThread
+import com.android.launcher3.celllayout.CellPosMapper
+import com.android.launcher3.config.FeatureFlags
+import com.android.launcher3.icons.IconCache
+import com.android.launcher3.model.AddWorkspaceItemsTask
+import com.android.launcher3.model.AllAppsList
+import com.android.launcher3.model.BaseLauncherBinder
+import com.android.launcher3.model.BgDataModel
+import com.android.launcher3.model.CacheDataUpdatedTask
+import com.android.launcher3.model.ItemInstallQueue
+import com.android.launcher3.model.LoaderTask
+import com.android.launcher3.model.ModelDbController
+import com.android.launcher3.model.ModelDelegate
+import com.android.launcher3.model.ModelLauncherCallbacks
+import com.android.launcher3.model.ModelTaskController
+import com.android.launcher3.model.ModelWriter
+import com.android.launcher3.model.PackageUpdatedTask
+import com.android.launcher3.model.ReloadStringCacheTask
+import com.android.launcher3.model.ShortcutsChangedTask
+import com.android.launcher3.model.UserLockStateChangedTask
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.pm.UserCache
+import com.android.launcher3.shortcuts.ShortcutRequest
+import com.android.launcher3.testing.shared.TestProtocol.sDebugTracing
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.Executors.MODEL_EXECUTOR
+import com.android.launcher3.util.PackageManagerHelper
+import com.android.launcher3.util.PackageUserKey
+import com.android.launcher3.util.Preconditions
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import java.util.concurrent.CancellationException
+import java.util.function.Consumer
+
+/**
+ * Maintains in-memory state of the Launcher. It is expected that there should be only one
+ * LauncherModel object held in a static. Also provide APIs for updating the database state for the
+ * Launcher.
+ */
+class LauncherModel(
+ private val context: Context,
+ private val mApp: LauncherAppState,
+ private val iconCache: IconCache,
+ private val appFilter: AppFilter,
+ private val mPmHelper: PackageManagerHelper,
+ isPrimaryInstance: Boolean,
+) {
+
+ private val mCallbacksList = ArrayList<BgDataModel.Callbacks>(1)
+
+ // < only access in worker thread >
+ private val mBgAllAppsList = AllAppsList(iconCache, appFilter)
+
+ /**
+ * All the static data should be accessed on the background thread, A lock should be acquired on
+ * this object when accessing any data from this model.
+ */
+ private val mBgDataModel = BgDataModel()
+
+ val modelDelegate: ModelDelegate =
+ ModelDelegate.newInstance(
+ context,
+ mApp,
+ mPmHelper,
+ mBgAllAppsList,
+ mBgDataModel,
+ isPrimaryInstance,
+ )
+
+ val modelDbController = ModelDbController(context)
+
+ private val mLock = Any()
+
+ private var mLoaderTask: LoaderTask? = null
+ private var mIsLoaderTaskRunning = false
+
+ // only allow this once per reboot to reload work apps
+ private var mShouldReloadWorkProfile = true
+
+ // Indicates whether the current model data is valid or not.
+ // We start off with everything not loaded. After that, we assume that
+ // our monitoring of the package manager provides all updates and we never
+ // need to do a requery. This is only ever touched from the loader thread.
+ private var mModelLoaded = false
+ private var mModelDestroyed = false
+
+ fun isModelLoaded() =
+ synchronized(mLock) { mModelLoaded && mLoaderTask == null && !mModelDestroyed }
+
+ /**
+ * Returns the ID for the last model load. If the load ID doesn't match for a transaction, the
+ * transaction should be ignored.
+ */
+ var lastLoadId: Int = -1
+ private set
+
+ // Runnable to check if the shortcuts permission has changed.
+ private val mDataValidationCheck = Runnable {
+ if (mModelLoaded) {
+ modelDelegate.validateData()
+ }
+ }
+
+ fun newModelCallbacks() = ModelLauncherCallbacks(this::enqueueModelUpdateTask)
+
+ /** Adds the provided items to the workspace. */
+ fun addAndBindAddedWorkspaceItems(itemList: List<Pair<ItemInfo?, Any?>?>) {
+ callbacks.forEach { it.preAddApps() }
+ enqueueModelUpdateTask(AddWorkspaceItemsTask(itemList))
+ }
+
+ fun getWriter(
+ verifyChanges: Boolean,
+ cellPosMapper: CellPosMapper?,
+ owner: BgDataModel.Callbacks?,
+ ) = ModelWriter(mApp.context, this, mBgDataModel, verifyChanges, cellPosMapper, owner)
+
+ /** Called when the icon for an app changes, outside of package event */
+ @WorkerThread
+ fun onAppIconChanged(packageName: String, user: UserHandle) {
+ // Update the icon for the calendar package
+ enqueueModelUpdateTask(PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, user, packageName))
+ ShortcutRequest(context, user).forPackage(packageName).query(ShortcutRequest.PINNED).let {
+ if (it.isNotEmpty()) {
+ enqueueModelUpdateTask(ShortcutsChangedTask(packageName, it, user, false))
+ }
+ }
+ }
+
+ /** Called when the workspace items have drastically changed */
+ fun onWorkspaceUiChanged() {
+ MODEL_EXECUTOR.execute(modelDelegate::workspaceLoadComplete)
+ }
+
+ /** Called when the model is destroyed */
+ fun destroy() {
+ mModelDestroyed = true
+ MODEL_EXECUTOR.execute(modelDelegate::destroy)
+ }
+
+ fun onBroadcastIntent(intent: Intent) {
+ if (DEBUG_RECEIVER || sDebugTracing) Log.d(TAG, "onReceive intent=$intent")
+ when (intent.action) {
+ Intent.ACTION_LOCALE_CHANGED,
+ LauncherAppState.ACTION_FORCE_ROLOAD ->
+ // If we have changed locale we need to clear out the labels in all apps/workspace.
+ forceReload()
+ DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED ->
+ enqueueModelUpdateTask(ReloadStringCacheTask(this.modelDelegate))
+ }
+ }
+
+ /**
+ * Called then there use a user event
+ *
+ * @see UserCache.addUserEventListener
+ */
+ fun onUserEvent(user: UserHandle, action: String) {
+ when (action) {
+ Intent.ACTION_MANAGED_PROFILE_AVAILABLE -> {
+ if (mShouldReloadWorkProfile) {
+ forceReload()
+ } else {
+ enqueueModelUpdateTask(
+ PackageUpdatedTask(PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user)
+ )
+ }
+ mShouldReloadWorkProfile = false
+ }
+ Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE -> {
+ mShouldReloadWorkProfile = false
+ enqueueModelUpdateTask(
+ PackageUpdatedTask(PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user)
+ )
+ }
+ UserCache.ACTION_PROFILE_LOCKED ->
+ enqueueModelUpdateTask(UserLockStateChangedTask(user, false))
+ UserCache.ACTION_PROFILE_UNLOCKED ->
+ enqueueModelUpdateTask(UserLockStateChangedTask(user, true))
+ Intent.ACTION_MANAGED_PROFILE_REMOVED -> {
+ LauncherPrefs.get(mApp.context).put(LauncherPrefs.WORK_EDU_STEP, 0)
+ forceReload()
+ }
+ UserCache.ACTION_PROFILE_ADDED,
+ UserCache.ACTION_PROFILE_REMOVED -> forceReload()
+ UserCache.ACTION_PROFILE_AVAILABLE,
+ UserCache.ACTION_PROFILE_UNAVAILABLE -> {
+ // This broadcast is only available when android.os.Flags.allowPrivateProfile() is
+ // set. For Work-profile this broadcast will be sent in addition to
+ // ACTION_MANAGED_PROFILE_AVAILABLE/UNAVAILABLE. So effectively, this if block only
+ // handles the non-work profile case.
+ enqueueModelUpdateTask(
+ PackageUpdatedTask(PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user)
+ )
+ }
+ }
+ }
+
+ /**
+ * Reloads the workspace items from the DB and re-binds the workspace. This should generally not
+ * be called as DB updates are automatically followed by UI update
+ */
+ fun forceReload() {
+ synchronized(mLock) {
+ // Stop any existing loaders first, so they don't set mModelLoaded to true later
+ stopLoader()
+ mModelLoaded = false
+ }
+ rebindCallbacks()
+ }
+
+ /** Rebinds all existing callbacks with already loaded model */
+ fun rebindCallbacks() {
+ if (hasCallbacks()) {
+ startLoader()
+ }
+ }
+
+ /** Removes an existing callback */
+ fun removeCallbacks(callbacks: BgDataModel.Callbacks) {
+ synchronized(mCallbacksList) {
+ Preconditions.assertUIThread()
+ if (mCallbacksList.remove(callbacks)) {
+ if (stopLoader()) {
+ // Rebind existing callbacks
+ startLoader()
+ }
+ }
+ }
+ }
+
+ /**
+ * Adds a callbacks to receive model updates
+ *
+ * @return true if workspace load was performed synchronously
+ */
+ fun addCallbacksAndLoad(callbacks: BgDataModel.Callbacks): Boolean {
+ synchronized(mLock) {
+ addCallbacks(callbacks)
+ return startLoader(arrayOf(callbacks))
+ }
+ }
+
+ /** Adds a callbacks to receive model updates */
+ fun addCallbacks(callbacks: BgDataModel.Callbacks) {
+ Preconditions.assertUIThread()
+ synchronized(mCallbacksList) { mCallbacksList.add(callbacks) }
+ }
+
+ /**
+ * Starts the loader. Tries to bind {@params synchronousBindPage} synchronously if possible.
+ *
+ * @return true if the page could be bound synchronously.
+ */
+ fun startLoader() = startLoader(arrayOf())
+
+ private fun startLoader(newCallbacks: Array<BgDataModel.Callbacks>): Boolean {
+ // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
+ ItemInstallQueue.INSTANCE.get(context).pauseModelPush(ItemInstallQueue.FLAG_LOADER_RUNNING)
+ synchronized(mLock) {
+ // If there is already one running, tell it to stop.
+ val wasRunning = stopLoader()
+ val bindDirectly = mModelLoaded && !mIsLoaderTaskRunning
+ val bindAllCallbacks = wasRunning || !bindDirectly || newCallbacks.isEmpty()
+ val callbacksList = if (bindAllCallbacks) callbacks else newCallbacks
+ if (callbacksList.isNotEmpty()) {
+ // Clear any pending bind-runnables from the synchronized load process.
+ callbacksList.forEach { MAIN_EXECUTOR.execute(it::clearPendingBinds) }
+
+ val launcherBinder =
+ BaseLauncherBinder(mApp, mBgDataModel, mBgAllAppsList, callbacksList)
+ if (bindDirectly) {
+ // Divide the set of loaded items into those that we are binding synchronously,
+ // and everything else that is to be bound normally (asynchronously).
+ launcherBinder.bindWorkspace(bindAllCallbacks, /* isBindSync= */ true)
+ // For now, continue posting the binding of AllApps as there are other
+ // issues that arise from that.
+ launcherBinder.bindAllApps()
+ launcherBinder.bindDeepShortcuts()
+ launcherBinder.bindWidgets()
+ if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
+ this.modelDelegate.bindAllModelExtras(callbacksList)
+ }
+ return true
+ } else {
+ mLoaderTask =
+ LoaderTask(
+ mApp,
+ mBgAllAppsList,
+ mBgDataModel,
+ this.modelDelegate,
+ launcherBinder,
+ )
+
+ // Always post the loader task, instead of running directly
+ // (even on same thread) so that we exit any nested synchronized blocks
+ MODEL_EXECUTOR.post(mLoaderTask)
+ }
+ }
+ }
+ return false
+ }
+
+ /**
+ * If there is already a loader task running, tell it to stop.
+ *
+ * @return true if an existing loader was stopped.
+ */
+ private fun stopLoader(): Boolean {
+ synchronized(mLock) {
+ val oldTask: LoaderTask? = mLoaderTask
+ mLoaderTask = null
+ if (oldTask != null) {
+ oldTask.stopLocked()
+ return true
+ }
+ return false
+ }
+ }
+
+ /**
+ * Loads the model if not loaded
+ *
+ * @param callback called with the data model upon successful load or null on model thread.
+ */
+ fun loadAsync(callback: Consumer<BgDataModel?>) {
+ synchronized(mLock) {
+ if (!mModelLoaded && !mIsLoaderTaskRunning) {
+ startLoader()
+ }
+ }
+ MODEL_EXECUTOR.post { callback.accept(if (isModelLoaded()) mBgDataModel else null) }
+ }
+
+ inner class LoaderTransaction(task: LoaderTask) : AutoCloseable {
+ private var mTask: LoaderTask? = null
+
+ init {
+ synchronized(mLock) {
+ if (mLoaderTask !== task) {
+ throw CancellationException("Loader already stopped")
+ }
+ this@LauncherModel.lastLoadId++
+ mTask = task
+ mIsLoaderTaskRunning = true
+ mModelLoaded = false
+ }
+ }
+
+ fun commit() {
+ synchronized(mLock) {
+ // Everything loaded bind the data.
+ mModelLoaded = true
+ }
+ }
+
+ override fun close() {
+ synchronized(mLock) {
+ // If we are still the last one to be scheduled, remove ourselves.
+ if (mLoaderTask === mTask) {
+ mLoaderTask = null
+ }
+ mIsLoaderTaskRunning = false
+ }
+ }
+ }
+
+ @Throws(CancellationException::class)
+ fun beginLoader(task: LoaderTask) = LoaderTransaction(task)
+
+ /**
+ * Refreshes the cached shortcuts if the shortcut permission has changed. Current implementation
+ * simply reloads the workspace, but it can be optimized to use partial updates similar to
+ * [UserCache]
+ */
+ fun validateModelDataOnResume() {
+ MODEL_EXECUTOR.handler.removeCallbacks(mDataValidationCheck)
+ MODEL_EXECUTOR.post(mDataValidationCheck)
+ }
+
+ /** Called when the icons for packages have been updated in the icon cache. */
+ fun onPackageIconsUpdated(updatedPackages: HashSet<String?>, user: UserHandle) {
+ // If any package icon has changed (app was updated while launcher was dead),
+ // update the corresponding shortcuts.
+ enqueueModelUpdateTask(
+ CacheDataUpdatedTask(CacheDataUpdatedTask.OP_CACHE_UPDATE, user, updatedPackages)
+ )
+ }
+
+ /** Called when the labels for the widgets has updated in the icon cache. */
+ fun onWidgetLabelsUpdated(updatedPackages: HashSet<String?>, user: UserHandle) {
+ enqueueModelUpdateTask { taskController, dataModel, _ ->
+ dataModel.widgetsModel.onPackageIconsUpdated(updatedPackages, user, mApp)
+ taskController.bindUpdatedWidgets(dataModel)
+ }
+ }
+
+ fun enqueueModelUpdateTask(task: ModelUpdateTask) {
+ if (mModelDestroyed) {
+ return
+ }
+ MODEL_EXECUTOR.execute {
+ if (!isModelLoaded()) {
+ // Loader has not yet run.
+ return@execute
+ }
+ task.execute(
+ ModelTaskController(mApp, mBgDataModel, mBgAllAppsList, this, MAIN_EXECUTOR),
+ mBgDataModel,
+ mBgAllAppsList,
+ )
+ }
+ }
+
+ /**
+ * A task to be executed on the current callbacks on the UI thread. If there is no current
+ * callbacks, the task is ignored.
+ */
+ fun interface CallbackTask {
+ fun execute(callbacks: BgDataModel.Callbacks)
+ }
+
+ fun interface ModelUpdateTask {
+ fun execute(taskController: ModelTaskController, dataModel: BgDataModel, apps: AllAppsList)
+ }
+
+ fun updateAndBindWorkspaceItem(si: WorkspaceItemInfo, info: ShortcutInfo) {
+ enqueueModelUpdateTask { taskController, _, _ ->
+ si.updateFromDeepShortcutInfo(info, context)
+ iconCache.getShortcutIcon(si, info)
+ taskController.getModelWriter().updateItemInDatabase(si)
+ taskController.bindUpdatedWorkspaceItems(listOf(si))
+ }
+ }
+
+ fun refreshAndBindWidgetsAndShortcuts(packageUser: PackageUserKey?) {
+ enqueueModelUpdateTask { taskController, dataModel, _ ->
+ dataModel.widgetsModel.update(taskController.app, packageUser)
+ taskController.bindUpdatedWidgets(dataModel)
+ }
+ }
+
+ fun dumpState(prefix: String?, fd: FileDescriptor?, writer: PrintWriter, args: Array<String?>) {
+ if (args.isNotEmpty() && TextUtils.equals(args[0], "--all")) {
+ writer.println(prefix + "All apps list: size=" + mBgAllAppsList.data.size)
+ for (info in mBgAllAppsList.data) {
+ writer.println(
+ "$prefix title=\"${info.title}\" bitmapIcon=${info.bitmap.icon} componentName=${info.targetPackage}"
+ )
+ }
+ writer.println()
+ }
+ modelDelegate.dump(prefix, fd, writer, args)
+ mBgDataModel.dump(prefix, fd, writer, args)
+ }
+
+ /** Returns true if there are any callbacks attached to the model */
+ fun hasCallbacks() = synchronized(mCallbacksList) { mCallbacksList.isNotEmpty() }
+
+ /** Returns an array of currently attached callbacks */
+ val callbacks: Array<BgDataModel.Callbacks>
+ get() {
+ synchronized(mCallbacksList) {
+ return mCallbacksList.toTypedArray<BgDataModel.Callbacks>()
+ }
+ }
+
+ companion object {
+ private const val DEBUG_RECEIVER = false
+
+ const val TAG = "Launcher.Model"
+ }
+}
diff --git a/src/com/android/launcher3/LauncherPrefChangeListener.java b/src/com/android/launcher3/LauncherPrefChangeListener.java
new file mode 100644
index 0000000..3e9a846
--- /dev/null
+++ b/src/com/android/launcher3/LauncherPrefChangeListener.java
@@ -0,0 +1,38 @@
+/*
+ * 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 android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+
+/**
+ * Listener for changes in [LauncherPrefs].
+ * <p>
+ * The listener also serves as an [OnSharedPreferenceChangeListener] where
+ * [onSharedPreferenceChanged] delegates to [onPrefChanged]. Overriding [onSharedPreferenceChanged]
+ * breaks compatibility with [SharedPreferences].
+ */
+public interface LauncherPrefChangeListener extends OnSharedPreferenceChangeListener {
+
+ /** Callback invoked when the preference for [key] has changed. */
+ void onPrefChanged(String key);
+
+ @Override
+ default void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ onPrefChanged(key);
+ }
+}
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index 13181e8..7ebfc18 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -18,7 +18,6 @@
import android.content.Context
import android.content.Context.MODE_PRIVATE
import android.content.SharedPreferences
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import androidx.annotation.VisibleForTesting
import com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN
import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY
@@ -34,11 +33,177 @@
import com.android.launcher3.util.Themes
/**
- * Use same context for shared preferences, so that we use a single cached instance
+ * Manages Launcher [SharedPreferences] through [Item] instances.
*
* TODO(b/262721340): Replace all direct SharedPreference refs with LauncherPrefs / Item methods.
*/
-class LauncherPrefs(private val encryptedContext: Context) : SafeCloseable {
+abstract class LauncherPrefs : SafeCloseable {
+
+ /** Returns the value with type [T] for [item]. */
+ abstract fun <T> get(item: ContextualItem<T>): T
+
+ /** Returns the value with type [T] for [item]. */
+ abstract fun <T> get(item: ConstantItem<T>): T
+
+ /** Stores the values for each item in preferences. */
+ abstract fun put(vararg itemsToValues: Pair<Item, Any>)
+
+ /** Stores the [value] with type [T] for [item] in preferences. */
+ abstract fun <T : Any> put(item: Item, value: T)
+
+ /** Synchronous version of [put]. */
+ abstract fun putSync(vararg itemsToValues: Pair<Item, Any>)
+
+ /** Registers [listener] for [items]. */
+ abstract fun addListener(listener: LauncherPrefChangeListener, vararg items: Item)
+
+ /** Unregisters [listener] for [items]. */
+ abstract fun removeListener(listener: LauncherPrefChangeListener, vararg items: Item)
+
+ /** Returns `true` iff all [items] have a value. */
+ abstract fun has(vararg items: Item): Boolean
+
+ /** Removes the value for each item in [items]. */
+ abstract fun remove(vararg items: Item)
+
+ /** Synchronous version of [remove]. */
+ abstract fun removeSync(vararg items: Item)
+
+ companion object {
+ @VisibleForTesting const val BOOT_AWARE_PREFS_KEY = "boot_aware_prefs"
+
+ @JvmField
+ var INSTANCE = MainThreadInitializedObject<LauncherPrefs> { LauncherPrefsImpl(it) }
+
+ @JvmStatic fun get(context: Context): LauncherPrefs = INSTANCE.get(context)
+
+ const val TASKBAR_PINNING_KEY = "TASKBAR_PINNING_KEY"
+ const val TASKBAR_PINNING_DESKTOP_MODE_KEY = "TASKBAR_PINNING_DESKTOP_MODE_KEY"
+ const val SHOULD_SHOW_SMARTSPACE_KEY = "SHOULD_SHOW_SMARTSPACE_KEY"
+ @JvmField
+ val ICON_STATE = nonRestorableItem("pref_icon_shape_path", "", EncryptionType.ENCRYPTED)
+
+ @JvmField
+ val ENABLE_TWOLINE_ALLAPPS_TOGGLE = backedUpItem("pref_enable_two_line_toggle", false)
+ @JvmField
+ val THEMED_ICONS = backedUpItem(Themes.KEY_THEMED_ICONS, false, EncryptionType.ENCRYPTED)
+ @JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "")
+ @JvmField val WORK_EDU_STEP = backedUpItem("showed_work_profile_edu", 0)
+ @JvmField
+ val WORKSPACE_SIZE =
+ backedUpItem(DeviceGridState.KEY_WORKSPACE_SIZE, "", EncryptionType.ENCRYPTED)
+ @JvmField
+ val HOTSEAT_COUNT =
+ backedUpItem(DeviceGridState.KEY_HOTSEAT_COUNT, -1, EncryptionType.ENCRYPTED)
+ @JvmField
+ val TASKBAR_PINNING =
+ backedUpItem(TASKBAR_PINNING_KEY, false, EncryptionType.DEVICE_PROTECTED)
+ @JvmField
+ val TASKBAR_PINNING_IN_DESKTOP_MODE =
+ backedUpItem(TASKBAR_PINNING_DESKTOP_MODE_KEY, true, EncryptionType.DEVICE_PROTECTED)
+
+ @JvmField
+ val DEVICE_TYPE =
+ backedUpItem(
+ DeviceGridState.KEY_DEVICE_TYPE,
+ InvariantDeviceProfile.TYPE_PHONE,
+ EncryptionType.ENCRYPTED,
+ )
+ @JvmField
+ val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "", EncryptionType.ENCRYPTED)
+ @JvmField
+ val SHOULD_SHOW_SMARTSPACE =
+ backedUpItem(
+ SHOULD_SHOW_SMARTSPACE_KEY,
+ WIDGET_ON_FIRST_SCREEN,
+ EncryptionType.DEVICE_PROTECTED,
+ )
+ @JvmField
+ val RESTORE_DEVICE =
+ backedUpItem(
+ RestoreDbTask.RESTORED_DEVICE_TYPE,
+ InvariantDeviceProfile.TYPE_PHONE,
+ EncryptionType.ENCRYPTED,
+ )
+ @JvmField
+ val IS_FIRST_LOAD_AFTER_RESTORE =
+ nonRestorableItem(FIRST_LOAD_AFTER_RESTORE_KEY, false, EncryptionType.ENCRYPTED)
+ @JvmField val APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_IDS, "")
+ @JvmField val OLD_APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_OLD_IDS, "")
+ @JvmField
+ val GRID_NAME =
+ ConstantItem(
+ "idp_grid_name",
+ isBackedUp = true,
+ defaultValue = null,
+ encryptionType = EncryptionType.ENCRYPTED,
+ type = String::class.java,
+ )
+ @JvmField
+ val ALLOW_ROTATION =
+ backedUpItem(RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY, Boolean::class.java) {
+ RotationHelper.getAllowRotationDefaultValue(DisplayController.INSTANCE.get(it).info)
+ }
+
+ // Preferences for widget configurations
+ @JvmField
+ val RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN =
+ backedUpItem("launcher.reconfigurable_widget_education_tip_seen", false)
+
+ @JvmStatic
+ fun <T> backedUpItem(
+ sharedPrefKey: String,
+ defaultValue: T,
+ encryptionType: EncryptionType = EncryptionType.ENCRYPTED,
+ ): ConstantItem<T> =
+ ConstantItem(sharedPrefKey, isBackedUp = true, defaultValue, encryptionType)
+
+ @JvmStatic
+ fun <T> backedUpItem(
+ sharedPrefKey: String,
+ type: Class<out T>,
+ encryptionType: EncryptionType = EncryptionType.ENCRYPTED,
+ defaultValueFromContext: (c: Context) -> T,
+ ): ContextualItem<T> =
+ ContextualItem(
+ sharedPrefKey,
+ isBackedUp = true,
+ defaultValueFromContext,
+ encryptionType,
+ type,
+ )
+
+ @JvmStatic
+ fun <T> nonRestorableItem(
+ sharedPrefKey: String,
+ defaultValue: T,
+ encryptionType: EncryptionType = EncryptionType.ENCRYPTED,
+ ): ConstantItem<T> =
+ ConstantItem(sharedPrefKey, isBackedUp = false, defaultValue, encryptionType)
+
+ @Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
+ @JvmStatic
+ fun getPrefs(context: Context): SharedPreferences {
+ // Use application context for shared preferences, so we use single cached instance
+ return context.applicationContext.getSharedPreferences(
+ SHARED_PREFERENCES_KEY,
+ MODE_PRIVATE,
+ )
+ }
+
+ @Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
+ @JvmStatic
+ fun getDevicePrefs(context: Context): SharedPreferences {
+ // Use application context for shared preferences, so we use a single cached instance
+ return context.applicationContext.getSharedPreferences(
+ DEVICE_PREFERENCES_KEY,
+ MODE_PRIVATE,
+ )
+ }
+ }
+}
+
+private class LauncherPrefsImpl(private val encryptedContext: Context) : LauncherPrefs() {
private val deviceProtectedStorageContext =
encryptedContext.createDeviceProtectedStorageContext()
@@ -54,11 +219,11 @@
else item.encryptedPrefs
/** Wrapper around `getInner` for a `ContextualItem` */
- fun <T> get(item: ContextualItem<T>): T =
+ override fun <T> get(item: ContextualItem<T>): T =
getInner(item, item.defaultValueFromContext(encryptedContext))
/** Wrapper around `getInner` for an `Item` */
- fun <T> get(item: ConstantItem<T>): T = getInner(item, item.defaultValue)
+ override fun <T> get(item: ConstantItem<T>): T = getInner(item, item.defaultValue)
/**
* Retrieves the value for an [Item] from [SharedPreferences]. It handles method typing via the
@@ -97,17 +262,17 @@
* prepareToPutValue(itemsToValues) for every distinct `SharedPreferences` file present in the
* provided item configurations.
*/
- fun put(vararg itemsToValues: Pair<Item, Any>): Unit =
+ override fun put(vararg itemsToValues: Pair<Item, Any>): Unit =
prepareToPutValues(itemsToValues).forEach { it.apply() }
/** See referenced `put` method above. */
- fun <T : Any> put(item: Item, value: T): Unit = put(item.to(value))
+ override fun <T : Any> put(item: Item, value: T): Unit = put(item.to(value))
/**
* Synchronously stores all the values provided according to their associated Item
* configuration.
*/
- fun putSync(vararg itemsToValues: Pair<Item, Any>): Unit =
+ override fun putSync(vararg itemsToValues: Pair<Item, Any>): Unit =
prepareToPutValues(itemsToValues).forEach { it.commit() }
/**
@@ -152,7 +317,7 @@
@Suppress("UNCHECKED_CAST")
private fun SharedPreferences.Editor.putValue(
item: Item,
- value: Any?
+ value: Any?,
): SharedPreferences.Editor =
when (item.type) {
String::class.java -> putString(item.sharedPrefKey, value as? String)
@@ -176,7 +341,7 @@
* `SharedPreferences` files associated with the provided list of items. The listener will need
* to filter update notifications so they don't activate for non-relevant updates.
*/
- fun addListener(listener: OnSharedPreferenceChangeListener, vararg items: Item) {
+ override fun addListener(listener: LauncherPrefChangeListener, vararg items: Item) {
items
.map { chooseSharedPreferences(it) }
.distinct()
@@ -187,7 +352,7 @@
* Stops the listener from getting notified of any more updates to any of the
* `SharedPreferences` files associated with any of the provided list of [Item].
*/
- fun removeListener(listener: OnSharedPreferenceChangeListener, vararg items: Item) {
+ override fun removeListener(listener: LauncherPrefChangeListener, vararg items: Item) {
// If a listener is not registered to a SharedPreference, unregistering it does nothing
items
.map { chooseSharedPreferences(it) }
@@ -199,7 +364,7 @@
* Checks if all the provided [Item] have values stored in their corresponding
* `SharedPreferences` files.
*/
- fun has(vararg items: Item): Boolean {
+ override fun has(vararg items: Item): Boolean {
items
.groupBy { chooseSharedPreferences(it) }
.forEach { (prefs, itemsSublist) ->
@@ -211,10 +376,10 @@
/**
* Asynchronously removes the [Item]'s value from its corresponding `SharedPreferences` file.
*/
- fun remove(vararg items: Item) = prepareToRemove(items).forEach { it.apply() }
+ override fun remove(vararg items: Item) = prepareToRemove(items).forEach { it.apply() }
/** Synchronously removes the [Item]'s value from its corresponding `SharedPreferences` file. */
- fun removeSync(vararg items: Item) = prepareToRemove(items).forEach { it.commit() }
+ override fun removeSync(vararg items: Item) = prepareToRemove(items).forEach { it.commit() }
/**
* Removes the key value pairs stored in `SharedPreferences` for each corresponding Item. If the
@@ -244,138 +409,6 @@
}
override fun close() {}
-
- companion object {
- @VisibleForTesting const val BOOT_AWARE_PREFS_KEY = "boot_aware_prefs"
-
- @JvmField var INSTANCE = MainThreadInitializedObject { LauncherPrefs(it) }
-
- @JvmStatic fun get(context: Context): LauncherPrefs = INSTANCE.get(context)
-
- const val TASKBAR_PINNING_KEY = "TASKBAR_PINNING_KEY"
- const val TASKBAR_PINNING_DESKTOP_MODE_KEY = "TASKBAR_PINNING_DESKTOP_MODE_KEY"
- const val SHOULD_SHOW_SMARTSPACE_KEY = "SHOULD_SHOW_SMARTSPACE_KEY"
- @JvmField
- val ICON_STATE = nonRestorableItem("pref_icon_shape_path", "", EncryptionType.ENCRYPTED)
-
- @JvmField
- val ENABLE_TWOLINE_ALLAPPS_TOGGLE = backedUpItem("pref_enable_two_line_toggle", false)
- @JvmField
- val THEMED_ICONS = backedUpItem(Themes.KEY_THEMED_ICONS, false, EncryptionType.ENCRYPTED)
- @JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "")
- @JvmField val WORK_EDU_STEP = backedUpItem("showed_work_profile_edu", 0)
- @JvmField
- val WORKSPACE_SIZE =
- backedUpItem(DeviceGridState.KEY_WORKSPACE_SIZE, "", EncryptionType.ENCRYPTED)
- @JvmField
- val HOTSEAT_COUNT =
- backedUpItem(DeviceGridState.KEY_HOTSEAT_COUNT, -1, EncryptionType.ENCRYPTED)
- @JvmField
- val TASKBAR_PINNING =
- backedUpItem(TASKBAR_PINNING_KEY, false, EncryptionType.DEVICE_PROTECTED)
- @JvmField
- val TASKBAR_PINNING_IN_DESKTOP_MODE =
- backedUpItem(TASKBAR_PINNING_DESKTOP_MODE_KEY, true, EncryptionType.DEVICE_PROTECTED)
-
- @JvmField
- val DEVICE_TYPE =
- backedUpItem(
- DeviceGridState.KEY_DEVICE_TYPE,
- InvariantDeviceProfile.TYPE_PHONE,
- EncryptionType.ENCRYPTED
- )
- @JvmField
- val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "", EncryptionType.ENCRYPTED)
- @JvmField
- val SHOULD_SHOW_SMARTSPACE =
- backedUpItem(
- SHOULD_SHOW_SMARTSPACE_KEY,
- WIDGET_ON_FIRST_SCREEN,
- EncryptionType.DEVICE_PROTECTED
- )
- @JvmField
- val RESTORE_DEVICE =
- backedUpItem(
- RestoreDbTask.RESTORED_DEVICE_TYPE,
- InvariantDeviceProfile.TYPE_PHONE,
- EncryptionType.ENCRYPTED
- )
- @JvmField
- val IS_FIRST_LOAD_AFTER_RESTORE =
- nonRestorableItem(FIRST_LOAD_AFTER_RESTORE_KEY, false, EncryptionType.ENCRYPTED)
- @JvmField val APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_IDS, "")
- @JvmField val OLD_APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_OLD_IDS, "")
- @JvmField
- val GRID_NAME =
- ConstantItem(
- "idp_grid_name",
- isBackedUp = true,
- defaultValue = null,
- encryptionType = EncryptionType.ENCRYPTED,
- type = String::class.java
- )
- @JvmField
- val ALLOW_ROTATION =
- backedUpItem(RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY, Boolean::class.java) {
- RotationHelper.getAllowRotationDefaultValue(DisplayController.INSTANCE.get(it).info)
- }
-
- // Preferences for widget configurations
- @JvmField
- val RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN =
- backedUpItem("launcher.reconfigurable_widget_education_tip_seen", false)
-
- @JvmStatic
- fun <T> backedUpItem(
- sharedPrefKey: String,
- defaultValue: T,
- encryptionType: EncryptionType = EncryptionType.ENCRYPTED
- ): ConstantItem<T> =
- ConstantItem(sharedPrefKey, isBackedUp = true, defaultValue, encryptionType)
-
- @JvmStatic
- fun <T> backedUpItem(
- sharedPrefKey: String,
- type: Class<out T>,
- encryptionType: EncryptionType = EncryptionType.ENCRYPTED,
- defaultValueFromContext: (c: Context) -> T
- ): ContextualItem<T> =
- ContextualItem(
- sharedPrefKey,
- isBackedUp = true,
- defaultValueFromContext,
- encryptionType,
- type
- )
-
- @JvmStatic
- fun <T> nonRestorableItem(
- sharedPrefKey: String,
- defaultValue: T,
- encryptionType: EncryptionType = EncryptionType.ENCRYPTED
- ): ConstantItem<T> =
- ConstantItem(sharedPrefKey, isBackedUp = false, defaultValue, encryptionType)
-
- @Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
- @JvmStatic
- fun getPrefs(context: Context): SharedPreferences {
- // Use application context for shared preferences, so we use single cached instance
- return context.applicationContext.getSharedPreferences(
- SHARED_PREFERENCES_KEY,
- MODE_PRIVATE
- )
- }
-
- @Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
- @JvmStatic
- fun getDevicePrefs(context: Context): SharedPreferences {
- // Use application context for shared preferences, so we use a single cached instance
- return context.applicationContext.getSharedPreferences(
- DEVICE_PREFERENCES_KEY,
- MODE_PRIVATE
- )
- }
- }
}
abstract class Item {
@@ -395,7 +428,7 @@
val defaultValue: T,
override val encryptionType: EncryptionType,
// The default value can be null. If so, the type needs to be explicitly stated, or else NPE
- override val type: Class<out T> = defaultValue!!::class.java
+ override val type: Class<out T> = defaultValue!!::class.java,
) : Item() {
fun get(c: Context): T = LauncherPrefs.get(c).get(this)
@@ -406,7 +439,7 @@
override val isBackedUp: Boolean,
private val defaultSupplier: (c: Context) -> T,
override val encryptionType: EncryptionType,
- override val type: Class<out T>
+ override val type: Class<out T>,
) : Item() {
private var default: T? = null
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 87ac193..1d2d161 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -16,8 +16,12 @@
package com.android.launcher3;
+import static android.util.Base64.NO_PADDING;
+import static android.util.Base64.NO_WRAP;
+
import android.database.sqlite.SQLiteDatabase;
import android.provider.BaseColumns;
+import android.util.Base64;
import androidx.annotation.NonNull;
@@ -354,8 +358,17 @@
* Launcher settings
*/
public static final class Settings {
- public static final String LAYOUT_DIGEST_KEY = "launcher3.layout.provider.blob";
+ public static final String LAYOUT_PROVIDER_KEY = "launcher3.layout.provider";
public static final String LAYOUT_DIGEST_LABEL = "launcher-layout";
public static final String LAYOUT_DIGEST_TAG = "ignore";
+ public static final String BLOB_KEY_PREFIX = "blob://";
+
+ /**
+ * Creates a key to be used for {@link #LAYOUT_PROVIDER_KEY}
+ * @param digest byte[] representing the message digest for the blob handle
+ */
+ public static String createBlobProviderKey(byte[] digest) {
+ return BLOB_KEY_PREFIX + Base64.encodeToString(digest, NO_WRAP | NO_PADDING);
+ }
}
}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index f8ac48a..9192e13 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -83,8 +83,8 @@
import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
import com.android.launcher3.graphics.TintedDrawableSpan;
import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.CacheableShortcutInfo;
import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.icons.ShortcutCachingLogic;
import com.android.launcher3.icons.ThemedIconDrawable;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
@@ -420,6 +420,25 @@
return mapRange(interpolator.getInterpolation(progress), toMin, toMax);
}
+ /**
+ * Maps t from one range to another range.
+ * @param t The value to map.
+ * @param fromMin The lower bound of the range that t is being mapped from.
+ * @param fromMax The upper bound of the range that t is being mapped from.
+ * @param toMin The lower bound of the range that t is being mapped to.
+ * @param toMax The upper bound of the range that t is being mapped to.
+ * @return The mapped value of t.
+ */
+ public static int mapToRange(int t, int fromMin, int fromMax, int toMin, int toMax,
+ Interpolator interpolator) {
+ if (fromMin == fromMax || toMin == toMax) {
+ Log.e(TAG, "mapToRange: range has 0 length");
+ return toMin;
+ }
+ float progress = getProgress(t, fromMin, fromMax);
+ return (int) mapRange(interpolator.getInterpolation(progress), toMin, toMax);
+ }
+
/** Bounds t between a lower and upper bound and maps the result to a range. */
public static float mapBoundToRange(float t, float lowerBound, float upperBound,
float toMin, float toMax, Interpolator interpolator) {
@@ -626,8 +645,7 @@
if (activityInfo == null) {
return null;
}
- mainIcon = appState.getIconProvider().getIcon(
- activityInfo, appState.getInvariantDeviceProfile().fillResIconDpi);
+ mainIcon = appState.getIconCache().getFullResIcon(activityInfo.getActivityInfo());
} else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
List<ShortcutInfo> siList = ShortcutKey.fromItemInfo(info)
.buildRequest(context)
@@ -636,7 +654,7 @@
return null;
} else {
ShortcutInfo si = siList.get(0);
- mainIcon = ShortcutCachingLogic.getIcon(context, si,
+ mainIcon = CacheableShortcutInfo.getIcon(context, si,
appState.getInvariantDeviceProfile().fillResIconDpi);
// Only fetch badge if the icon is on workspace
if (info.id != ItemInfo.NO_ID && badge == null) {
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 4e1e950..e705d94 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -346,17 +346,12 @@
(LetterListTextView) LayoutInflater.from(context).inflate(
R.layout.fast_scroller_letter_list_text_view, mLetterList, false);
int viewId = View.generateViewId();
- textView.setId(viewId);
+ textView.apply(sectionInfo /* FastScrollSectionInfo */, viewId /* viewId */);
sectionInfo.setId(viewId);
- textView.setText(sectionInfo.sectionName);
if (i == fastScrollSections.size() - 1) {
// The last section info is just a duplicate so that user can scroll to the bottom.
textView.setVisibility(INVISIBLE);
}
- ConstraintLayout.LayoutParams lp = new ConstraintLayout.LayoutParams(
- MATCH_CONSTRAINT, WRAP_CONTENT);
- lp.dimensionRatio = "v,1:1";
- textView.setLayoutParams(lp);
textViews.add(textView);
mLetterList.addView(textView);
}
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 8e44d65..709b52a 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -106,6 +106,7 @@
// The of ordered component names as a result of a search query
private final ArrayList<AdapterItem> mSearchResults = new ArrayList<>();
private final SpannableString mPrivateProfileAppScrollerBadge;
+ private final SpannableString mPrivateProfileDividerBadge;
private BaseAllAppsAdapter<T> mAdapter;
private AppInfoComparator mAppNameComparator;
private int mNumAppsPerRowAllApps;
@@ -124,9 +125,14 @@
mAllAppsStore.addUpdateListener(this);
}
mPrivateProfileAppScrollerBadge = new SpannableString(" ");
- mPrivateProfileAppScrollerBadge.setSpan(new ImageSpan(context,
+ mPrivateProfileAppScrollerBadge.setSpan(new ImageSpan(context, Flags.letterFastScroller()
+ ? R.drawable.ic_private_profile_letter_list_fast_scroller_badge :
R.drawable.ic_private_profile_app_scroller_badge, ImageSpan.ALIGN_CENTER),
0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ mPrivateProfileDividerBadge = new SpannableString(" ");
+ mPrivateProfileDividerBadge.setSpan(new ImageSpan(context,
+ R.drawable.ic_private_profile_divider_badge, ImageSpan.ALIGN_CENTER),
+ 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
/** Set the number of apps per row when device profile changes. */
@@ -404,6 +410,11 @@
// Add system apps separator.
if (Flags.privateSpaceSysAppsSeparation()) {
position = mPrivateProviderManager.addSystemAppsDivider(mAdapterItems);
+ if (Flags.letterFastScroller()) {
+ FastScrollSectionInfo sectionInfo =
+ new FastScrollSectionInfo(mPrivateProfileDividerBadge, position);
+ mFastScrollerSections.add(sectionInfo);
+ }
}
// Add system apps.
position = addAppsWithSections(split.get(false), position);
@@ -437,8 +448,11 @@
Log.d(TAG, "addAppsWithSections: adding sectionName: " + sectionName
+ " with appInfoTitle: " + info.title);
lastSectionName = sectionName;
- mFastScrollerSections.add(new FastScrollSectionInfo(hasPrivateApps ?
- mPrivateProfileAppScrollerBadge : sectionName, position));
+ boolean usePrivateAppScrollerBadge = !Flags.letterFastScroller() && hasPrivateApps;
+ FastScrollSectionInfo sectionInfo = new FastScrollSectionInfo(
+ usePrivateAppScrollerBadge ?
+ mPrivateProfileAppScrollerBadge : sectionName, position);
+ mFastScrollerSections.add(sectionInfo);
}
position++;
}
diff --git a/src/com/android/launcher3/allapps/LetterListTextView.java b/src/com/android/launcher3/allapps/LetterListTextView.java
index 9326d79..433a7f2 100644
--- a/src/com/android/launcher3/allapps/LetterListTextView.java
+++ b/src/com/android/launcher3/allapps/LetterListTextView.java
@@ -16,6 +16,9 @@
package com.android.launcher3.allapps;
+import static androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT;
+import static androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT;
+
import android.content.Context;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
@@ -23,6 +26,7 @@
import android.util.AttributeSet;
import android.widget.TextView;
+import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.graphics.ColorUtils;
import com.android.launcher3.R;
@@ -71,6 +75,20 @@
}
/**
+ * Applies a viewId to the letter list text view and sets the background and text based on the
+ * sectionInfo.
+ */
+ public void apply(AlphabeticalAppsList.FastScrollSectionInfo fastScrollSectionInfo,
+ int viewId) {
+ setId(viewId);
+ setText(fastScrollSectionInfo.sectionName);
+ ConstraintLayout.LayoutParams lp = new ConstraintLayout.LayoutParams(
+ MATCH_CONSTRAINT, WRAP_CONTENT);
+ lp.dimensionRatio = "v,1:1";
+ setLayoutParams(lp);
+ }
+
+ /**
* Animates the letter list text view based on the current finger position.
*
* @param currentFingerY The Y position of where the finger is placed on the fastScroller in
diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java
index e7881c4..9c36dc2 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;
@@ -700,7 +701,9 @@
mAllApps.mAH.get(MAIN).mRecyclerView.removeItemDecoration(
mPrivateAppsSectionDecorator);
// Call onAppsUpdated() because it may be canceled when this animation occurs.
- mAllApps.getPersonalAppList().onAppsUpdated();
+ if (!Utilities.isRunningInTestHarness()) {
+ mAllApps.getPersonalAppList().onAppsUpdated();
+ }
if (isPrivateSpaceHidden()) {
// TODO (b/325455879): Figure out if we can avoid this.
getMainRecyclerView().getAdapter().notifyDataSetChanged();
diff --git a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
index 0a50e8b..c3508b7 100644
--- a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
+++ b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
@@ -18,7 +18,11 @@
import android.content.Context;
+import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.util.DaggerSingletonTracker;
+import com.android.launcher3.util.ScreenOnTracker;
+import com.android.launcher3.util.SettingsCache;
+import com.android.launcher3.widget.custom.CustomWidgetManager;
import dagger.BindsInstance;
@@ -32,6 +36,11 @@
*/
public interface LauncherBaseAppComponent {
DaggerSingletonTracker getDaggerSingletonTracker();
+ InstallSessionHelper getInstallSessionHelper();
+ ScreenOnTracker getScreenOnTracker();
+ SettingsCache getSettingsCache();
+ CustomWidgetManager getCustomWidgetManager();
+
/** 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 a3cfe5c..25de479 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -281,7 +281,7 @@
new PinShortcutRequestActivityInfo(mRequest, this);
mWidgetCell.getWidgetView().setTag(new PendingAddShortcutInfo(shortcutInfo));
applyWidgetItemAsync(
- () -> new WidgetItem(shortcutInfo, mApp.getIconCache(), getPackageManager()));
+ () -> new WidgetItem(shortcutInfo, mApp.getIconCache()));
return new PackageItemInfo(mRequest.getShortcutInfo().getPackage(),
mRequest.getShortcutInfo().getUserHandle());
}
diff --git a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
index 0f3cad6..a6a50d7 100644
--- a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
+++ b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
@@ -30,7 +30,6 @@
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.content.pm.LauncherApps.PinItemRequest;
-import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.graphics.drawable.Drawable;
import android.os.Build;
@@ -40,7 +39,7 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
-import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.icons.cache.BaseIconCache;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pm.PinRequestHelper;
import com.android.launcher3.pm.ShortcutConfigActivityInfo;
@@ -69,7 +68,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;
@@ -81,12 +81,12 @@
}
@Override
- public CharSequence getLabel(PackageManager pm) {
+ public CharSequence getLabel() {
return mInfo.getShortLabel();
}
@Override
- public Drawable getFullResIcon(IconCache cache) {
+ public Drawable getFullResIcon(BaseIconCache cache) {
Drawable d = mContext.getSystemService(LauncherApps.class)
.getShortcutIconDrawable(mInfo, LauncherAppState.getIDP(mContext).fillResIconDpi);
if (d == null) {
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 7bec768..5defef3 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -317,10 +317,7 @@
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
| InputType.TYPE_TEXT_FLAG_CAP_WORDS);
mFolderName.forceDisableSuggestions(true);
- mFolderName.setPadding(mFolderName.getPaddingLeft(),
- (getFooterHeight() - mFolderName.getLineHeight()) / 2,
- mFolderName.getPaddingRight(),
- (getFooterHeight() - mFolderName.getLineHeight()) / 2);
+
mKeyboardInsetAnimationCallback = new KeyboardInsetAnimationCallback(this);
setWindowInsetsAnimationCallback(mKeyboardInsetAnimationCallback);
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 9dc2d24..fe26194 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -373,8 +373,9 @@
// Update footer
mPageIndicator.setVisibility(getPageCount() > 1 ? View.VISIBLE : View.GONE);
// Set the gravity as LEFT or RIGHT instead of START, as START depends on the actual text.
- mFolder.getFolderName().setGravity(getPageCount() > 1
- ? (mIsRtl ? Gravity.RIGHT : Gravity.LEFT) : Gravity.CENTER_HORIZONTAL);
+ int horizontalGravity = getPageCount() > 1
+ ? (mIsRtl ? Gravity.RIGHT : Gravity.LEFT) : Gravity.CENTER_HORIZONTAL;
+ mFolder.getFolderName().setGravity(horizontalGravity | Gravity.CENTER_VERTICAL);
}
public int getDesiredWidth() {
diff --git a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
index 27ec838..259e543 100644
--- a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
+++ b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
@@ -95,6 +95,7 @@
private static final int MESSAGE_ID_UPDATE_PREVIEW = 1337;
private static final int MESSAGE_ID_UPDATE_GRID = 7414;
+ private static final int MESSAGE_ID_UPDATE_COLOR = 856;
// Set of all active previews used to track duplicate memory allocations
private final Set<PreviewLifecycleObserver> mActivePreviews =
@@ -289,6 +290,11 @@
renderer.updateGrid(gridName);
}
break;
+ case MESSAGE_ID_UPDATE_COLOR:
+ if (Flags.newCustomizationPickerUi()) {
+ renderer.previewColor(message.getData());
+ }
+ break;
default:
// Unknown command, destroy lifecycle
Log.d(TAG, "Unknown preview command: " + message.what + ", destroying preview");
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 40c0cc6..f0e4fc4 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -98,6 +98,7 @@
import com.android.launcher3.widget.LauncherWidgetHolder;
import com.android.launcher3.widget.LocalColorExtractor;
import com.android.launcher3.widget.util.WidgetSizes;
+import com.android.systemui.shared.Flags;
import java.util.ArrayList;
import java.util.Collections;
@@ -150,6 +151,14 @@
InvariantDeviceProfile idp,
WallpaperColors wallpaperColorsOverride,
@Nullable final SparseArray<Size> launcherWidgetSpanInfo) {
+ this(context, idp, null, wallpaperColorsOverride, launcherWidgetSpanInfo);
+ }
+
+ public LauncherPreviewRenderer(Context context,
+ InvariantDeviceProfile idp,
+ SparseIntArray previewColorOverride,
+ WallpaperColors wallpaperColorsOverride,
+ @Nullable final SparseArray<Size> launcherWidgetSpanInfo) {
super(context);
mUiHandler = new Handler(Looper.getMainLooper());
@@ -206,12 +215,29 @@
mWorkspaceScreens.put(Workspace.SECOND_SCREEN_ID, rightPanel);
}
- WallpaperColors wallpaperColors = wallpaperColorsOverride != null
- ? wallpaperColorsOverride
- : WallpaperManager.getInstance(context).getWallpaperColors(FLAG_SYSTEM);
- mWallpaperColorResources = wallpaperColors != null
- ? LocalColorExtractor.newInstance(context).generateColorsOverride(wallpaperColors)
- : null;
+ if (Flags.newCustomizationPickerUi()) {
+ if (previewColorOverride != null) {
+ mWallpaperColorResources = previewColorOverride;
+ } else if (wallpaperColorsOverride != null) {
+ mWallpaperColorResources = LocalColorExtractor.newInstance(
+ context).generateColorsOverride(wallpaperColorsOverride);
+ } else {
+ WallpaperColors wallpaperColors = WallpaperManager.getInstance(
+ context).getWallpaperColors(FLAG_SYSTEM);
+ mWallpaperColorResources = wallpaperColors != null
+ ? LocalColorExtractor.newInstance(context).generateColorsOverride(
+ wallpaperColors)
+ : null;
+ }
+ } else {
+ 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 1b23d75..e3c2d36 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -32,6 +32,7 @@
import android.util.Log;
import android.util.Size;
import android.util.SparseArray;
+import android.util.SparseIntArray;
import android.view.ContextThemeWrapper;
import android.view.Display;
import android.view.SurfaceControlViewHost;
@@ -81,6 +82,8 @@
private static final String KEY_VIEW_HEIGHT = "height";
private static final String KEY_DISPLAY_ID = "display_id";
private static final String KEY_COLORS = "wallpaper_colors";
+ private static final String KEY_COLOR_RESOURCE_IDS = "color_resource_ids";
+ private static final String KEY_COLOR_VALUES = "color_values";
private Context mContext;
private final IBinder mHostToken;
@@ -91,6 +94,7 @@
private final int mDisplayId;
private final Display mDisplay;
private final WallpaperColors mWallpaperColors;
+ private SparseIntArray mPreviewColorOverride;
private final RunnableList mLifeCycleTracker;
private final SurfaceControlViewHost mSurfaceControlViewHost;
@@ -110,6 +114,9 @@
mGridName = InvariantDeviceProfile.getCurrentGridName(context);
}
mWallpaperColors = bundle.getParcelable(KEY_COLORS);
+ if (Flags.newCustomizationPickerUi()) {
+ updateColorOverrides(bundle);
+ }
mHideQsb = bundle.getBoolean(GridCustomizationsProvider.KEY_HIDE_BOTTOM_ROW);
mHostToken = bundle.getBinder(KEY_HOST_TOKEN);
@@ -217,20 +224,60 @@
}
}
+ /**
+ * Updates the colors of the preview.
+ *
+ * @param bundle Bundle with an int array of color ids and an int array of overriding colors.
+ */
+ public void previewColor(Bundle bundle) {
+ updateColorOverrides(bundle);
+ loadAsync();
+ }
+
+ private void updateColorOverrides(Bundle bundle) {
+ int[] ids = bundle.getIntArray(KEY_COLOR_RESOURCE_IDS);
+ int[] colors = bundle.getIntArray(KEY_COLOR_VALUES);
+ if (ids != null && colors != null) {
+ mPreviewColorOverride = new SparseIntArray();
+ for (int i = 0; i < ids.length; i++) {
+ mPreviewColorOverride.put(ids[i], colors[i]);
+ }
+ } else {
+ mPreviewColorOverride = null;
+ }
+ }
+
/***
* Generates a new context overriding the theme color and the display size without affecting the
* main application context
*/
private Context getPreviewContext() {
Context context = mContext.createDisplayContext(mDisplay);
- if (mWallpaperColors == null) {
+ if (Flags.newCustomizationPickerUi()) {
+ if (mPreviewColorOverride != null) {
+ LocalColorExtractor.newInstance(context)
+ .applyColorsOverride(context, mPreviewColorOverride);
+ } else if (mWallpaperColors != null) {
+ LocalColorExtractor.newInstance(context)
+ .applyColorsOverride(context, mWallpaperColors);
+ }
+ if (mWallpaperColors != null) {
+ return new ContextThemeWrapper(context,
+ Themes.getActivityThemeRes(context, mWallpaperColors.getColorHints()));
+ } else {
+ return new ContextThemeWrapper(context,
+ Themes.getActivityThemeRes(context));
+ }
+ } else {
+ if (mWallpaperColors == null) {
+ return new ContextThemeWrapper(context,
+ Themes.getActivityThemeRes(context));
+ }
+ LocalColorExtractor.newInstance(context)
+ .applyColorsOverride(context, mWallpaperColors);
return new ContextThemeWrapper(context,
- Themes.getActivityThemeRes(context));
+ Themes.getActivityThemeRes(context, mWallpaperColors.getColorHints()));
}
- LocalColorExtractor.newInstance(context)
- .applyColorsOverride(context, mWallpaperColors);
- return new ContextThemeWrapper(context,
- Themes.getActivityThemeRes(context, mWallpaperColors.getColorHints()));
}
@WorkerThread
@@ -300,8 +347,13 @@
if (mDestroyed) {
return;
}
- mRenderer = new LauncherPreviewRenderer(inflationContext, idp,
- mWallpaperColors, launcherWidgetSpanInfo);
+ if (Flags.newCustomizationPickerUi()) {
+ mRenderer = new LauncherPreviewRenderer(inflationContext, idp, mPreviewColorOverride,
+ mWallpaperColors, launcherWidgetSpanInfo);
+ } else {
+ mRenderer = new LauncherPreviewRenderer(inflationContext, idp,
+ mWallpaperColors, launcherWidgetSpanInfo);
+ }
mRenderer.hideBottomRow(mHideQsb);
View view = mRenderer.getRenderedView(dataModel, widgetProviderInfoMap);
// This aspect scales the view to fit in the surface and centers it
diff --git a/src/com/android/launcher3/icons/CacheableShortcutInfo.kt b/src/com/android/launcher3/icons/CacheableShortcutInfo.kt
new file mode 100644
index 0000000..f8a5552
--- /dev/null
+++ b/src/com/android/launcher3/icons/CacheableShortcutInfo.kt
@@ -0,0 +1,127 @@
+/*
+ * 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.ShortcutInfo
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+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
+
+/** 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 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
+ }
+
+ override fun getFreshnessIdentifier(
+ item: CacheableShortcutInfo,
+ provider: IconProvider,
+ ): String? =
+ item.shortcutInfo.lastChangedTimestamp.toString() +
+ "-" +
+ provider.getStateForApp(getApplicationInfo(item))
+}
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 53a4039..e7c4024 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -29,14 +29,10 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
-import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
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;
@@ -55,8 +51,8 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Utilities;
import com.android.launcher3.icons.cache.BaseIconCache;
+import com.android.launcher3.icons.cache.CachedObject;
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;
@@ -66,8 +62,8 @@
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.ComponentKey;
import com.android.launcher3.util.InstantAppResolver;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.widget.WidgetSections;
@@ -96,10 +92,6 @@
private final Predicate<ItemInfoWithIcon> mIsUsingFallbackOrNonDefaultIconCheck = w ->
w.bitmap != null && (w.bitmap.isNullOrLowRes() || !isDefaultIcon(w.bitmap, w.user));
- 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;
@@ -113,10 +105,6 @@
IconProvider iconProvider) {
super(context, dbFileName, MODEL_EXECUTOR.getLooper(),
idp.fillResIconDpi, idp.iconBitmapSize, true /* inMemoryCache */, iconProvider);
- mComponentWithLabelCachingLogic = new CachedObjectCachingLogic(
- context, false /* loadIcons */, false /* addToMemCache */);
- mLauncherActivityInfoCachingLogic = LauncherActivityCachingLogic.INSTANCE;
- mShortcutCachingLogic = new ShortcutCachingLogic();
mLauncherApps = mContext.getSystemService(LauncherApps.class);
mUserManager = UserCache.INSTANCE.get(mContext);
mInstantAppResolver = InstantAppResolver.newInstance(mContext);
@@ -148,16 +136,9 @@
public synchronized void updateIconsForPkg(@NonNull final String packageName,
@NonNull final UserHandle user) {
removeIconsForPkg(packageName, user);
- try {
- PackageInfo info = mPackageManager.getPackageInfo(packageName,
- PackageManager.GET_UNINSTALLED_PACKAGES);
- long userSerial = mUserManager.getSerialNumberForUser(user);
- for (LauncherActivityInfo app : mLauncherApps.getActivityList(packageName, user)) {
- addIconToDBAndMemCache(app, mLauncherActivityInfoCachingLogic, info, userSerial,
- false /*replace existing*/);
- }
- } catch (NameNotFoundException e) {
- Log.d(TAG, "Package not found", e);
+ long userSerial = mUserManager.getSerialNumberForUser(user);
+ for (LauncherActivityInfo app : mLauncherApps.getActivityList(packageName, user)) {
+ addIconToDBAndMemCache(app, LauncherActivityCachingLogic.INSTANCE, userSerial);
}
}
@@ -209,7 +190,7 @@
CancellableTask<ItemInfoWithIcon> request = new CancellableTask<>(
task, MAIN_EXECUTOR, caller::reapplyItemInfo, endRunnable);
- Utilities.postAsyncCallback(mWorkerHandler, request);
+ Utilities.postAsyncCallback(workerHandler, request);
return request;
}
@@ -225,7 +206,7 @@
*/
public synchronized void updateTitleAndIcon(AppInfo application) {
CacheEntry entry = cacheLocked(application.componentName,
- application.user, () -> null, mLauncherActivityInfoCachingLogic,
+ application.user, () -> null, LauncherActivityCachingLogic.INSTANCE,
application.usingLowResIcon() ? LookupFlag.USE_LOW_RES : LookupFlag.DEFAULT);
if (entry.bitmap != null || !isDefaultIcon(entry.bitmap, application.user)) {
applyCacheEntry(entry, application);
@@ -233,7 +214,7 @@
}
/**
- * 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,
@@ -245,28 +226,41 @@
}
/**
- * 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, LookupFlag.DEFAULT).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()));
}
/**
@@ -327,9 +321,10 @@
/**
* Loads and returns the icon for the provided object without adding it to memCache
*/
- public synchronized String getTitleNoCache(ComponentWithLabel info) {
+ public synchronized String getTitleNoCache(CachedObject info) {
CacheEntry entry = cacheLocked(info.getComponent(), info.getUser(), () -> info,
- mComponentWithLabelCachingLogic, LookupFlag.USE_LOW_RES);
+ CachedObjectCachingLogic.INSTANCE,
+ LookupFlag.USE_LOW_RES | LookupFlag.SKIP_ADD_TO_MEM_CACHE);
return Utilities.trim(entry.title);
}
@@ -344,7 +339,7 @@
if (usePkgIcon) lookupFlags |= LookupFlag.USE_PACKAGE_ICON;
if (useLowResIcon) lookupFlags |= LookupFlag.USE_LOW_RES;
CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), infoInOut.user,
- activityInfoProvider, mLauncherActivityInfoCachingLogic, lookupFlags);
+ activityInfoProvider, LauncherActivityCachingLogic.INSTANCE, lookupFlags);
applyCacheEntry(entry, infoInOut);
}
@@ -445,7 +440,7 @@
cn,
/* user = */ sectionKey.first,
() -> duplicateIconRequests.get(0).launcherActivityInfo,
- mLauncherActivityInfoCachingLogic,
+ LauncherActivityCachingLogic.INSTANCE,
sectionKey.second ? LookupFlag.USE_LOW_RES : LookupFlag.DEFAULT,
c);
@@ -494,7 +489,7 @@
loadFallbackIcon(
lai,
entry,
- mLauncherActivityInfoCachingLogic,
+ LauncherActivityCachingLogic.INSTANCE,
/* usePackageIcon= */ false,
/* usePackageTitle= */ loadFallbackTitle,
cn,
@@ -504,7 +499,7 @@
loadFallbackTitle(
lai,
entry,
- mLauncherActivityInfoCachingLogic,
+ LauncherActivityCachingLogic.INSTANCE,
sectionKey.first);
}
@@ -589,15 +584,16 @@
info.bitmap = packageEntry.bitmap;
}
- public Drawable getFullResIcon(LauncherActivityInfo info) {
- return mIconProvider.getIcon(info, mIconDpi);
- }
-
public void updateSessionCache(PackageUserKey key, PackageInstaller.SessionInfo info) {
cachePackageInstallInfo(key.mPackageName, key.mUser, info.getAppIcon(),
info.getAppLabel());
}
+ @VisibleForTesting
+ synchronized boolean isItemInDb(ComponentKey cacheKey) {
+ return getEntryFromDBLocked(cacheKey, new CacheEntry(), false);
+ }
+
/**
* Interface for receiving itemInfo with high-res icon.
*/
diff --git a/src/com/android/launcher3/icons/LauncherIconProvider.java b/src/com/android/launcher3/icons/LauncherIconProvider.java
index c4d5f2b..78a3128 100644
--- a/src/com/android/launcher3/icons/LauncherIconProvider.java
+++ b/src/com/android/launcher3/icons/LauncherIconProvider.java
@@ -16,14 +16,18 @@
package com.android.launcher3.icons;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
+import androidx.annotation.NonNull;
+
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.ApiWrapper;
import com.android.launcher3.util.Themes;
import org.xmlpull.v1.XmlPullParser;
@@ -70,6 +74,11 @@
return super.getSystemIconState() + (mSupportsIconTheme ? ",with-theme" : ",no-theme");
}
+ @Override
+ protected String getApplicationInfoHash(@NonNull ApplicationInfo appInfo) {
+ return ApiWrapper.INSTANCE.get(mContext).getApplicationInfoHash(appInfo);
+ }
+
private Map<String, ThemeData> getThemedIconMap() {
if (mThemedIconMap != null) {
return mThemedIconMap;
diff --git a/src/com/android/launcher3/icons/Legacy.kt b/src/com/android/launcher3/icons/Legacy.kt
deleted file mode 100644
index 3bf3bb2..0000000
--- a/src/com/android/launcher3/icons/Legacy.kt
+++ /dev/null
@@ -1,31 +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.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 7bb39e1..0000000
--- a/src/com/android/launcher3/icons/ShortcutCachingLogic.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.icons;
-
-import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.LauncherApps;
-import android.content.pm.PackageInfo;
-import android.content.pm.ShortcutInfo;
-import android.graphics.drawable.Drawable;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.icons.BaseIconFactory.IconOptions;
-import com.android.launcher3.icons.cache.BaseIconCache;
-import com.android.launcher3.icons.cache.CachingLogic;
-import com.android.launcher3.shortcuts.ShortcutKey;
-import com.android.launcher3.util.Themes;
-
-/**
- * Caching logic for shortcuts.
- */
-public class ShortcutCachingLogic implements CachingLogic<ShortcutInfo> {
-
- private static final String TAG = "ShortcutCachingLogic";
-
- @Override
- @NonNull
- public ComponentName getComponent(@NonNull ShortcutInfo info) {
- return ShortcutKey.fromInfo(info).componentName;
- }
-
- @NonNull
- @Override
- public UserHandle getUser(@NonNull ShortcutInfo info) {
- return info.getUserHandle();
- }
-
- @NonNull
- @Override
- public CharSequence getLabel(@NonNull ShortcutInfo info) {
- return info.getShortLabel();
- }
-
- @Override
- @NonNull
- public CharSequence getDescription(@NonNull ShortcutInfo object,
- @NonNull CharSequence fallback) {
- CharSequence label = object.getLongLabel();
- return TextUtils.isEmpty(label) ? fallback : label;
- }
-
- @NonNull
- @Override
- public BitmapInfo loadIcon(@NonNull Context context, @NonNull BaseIconCache cache,
- @NonNull ShortcutInfo info) {
- try (LauncherIcons li = LauncherIcons.obtain(context)) {
- Drawable unbadgedDrawable = ShortcutCachingLogic.getIcon(
- context, info, LauncherAppState.getIDP(context).fillResIconDpi);
- if (unbadgedDrawable == null) return BitmapInfo.LOW_RES_INFO;
- return li.createBadgedIconBitmap(unbadgedDrawable,
- new IconOptions().setExtractedColor(Themes.getColorAccent(context)));
- }
- }
-
- @Override
- public long getLastUpdatedTime(@Nullable ShortcutInfo shortcutInfo,
- @NonNull PackageInfo info) {
- if (shortcutInfo == null) {
- return info.lastUpdateTime;
- }
- return Math.max(shortcutInfo.getLastChangedTimestamp(), info.lastUpdateTime);
- }
-
- @Override
- public boolean addToMemCache() {
- return false;
- }
-
- /**
- * Similar to {@link LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)} with additional
- * Launcher specific checks
- */
- public static Drawable getIcon(Context context, ShortcutInfo shortcutInfo, int density) {
- if (!WIDGETS_ENABLED) {
- return null;
- }
- try {
- return context.getSystemService(LauncherApps.class)
- .getShortcutIconDrawable(shortcutInfo, density);
- } catch (SecurityException | IllegalStateException | NullPointerException e) {
- Log.e(TAG, "Failed to get shortcut icon", e);
- return null;
- }
- }
-}
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index fbd24d8..e5cd76a 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -221,6 +221,9 @@
@UiEvent(doc = "User tapped on desktop icon on a task menu.")
LAUNCHER_SYSTEM_SHORTCUT_DESKTOP_TAP(1706),
+ @UiEvent(doc = "Use tapped on external display icon on a task menu,")
+ LAUNCHER_SYSTEM_SHORTCUT_EXTERNAL_DISPLAY_TAP(1957),
+
@UiEvent(doc = "User tapped on pause app system shortcut.")
LAUNCHER_SYSTEM_SHORTCUT_PAUSE_TAP(521),
@@ -798,6 +801,44 @@
@UiEvent(doc = "User long pressed on the taskbar IME switcher button")
LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_LONGPRESS(1798),
+ @UiEvent(doc = "Failed to launch assistant due to Google assistant not available")
+ LAUNCHER_LAUNCH_ASSISTANT_FAILED_NOT_AVAILABLE(1465),
+
+ @UiEvent(doc = "Failed to launch assistant due to service error")
+ LAUNCHER_LAUNCH_ASSISTANT_FAILED_SERVICE_ERROR(1466),
+
+ @UiEvent(doc = "User launched assistant by long-pressing nav handle")
+ LAUNCHER_LAUNCH_ASSISTANT_SUCCESSFUL_NAV_HANDLE(1467),
+
+ @UiEvent(doc = "Failed to launch due to Contextual Search not available")
+ LAUNCHER_LAUNCH_OMNI_FAILED_NOT_AVAILABLE(1471),
+
+ @UiEvent(doc = "Failed to launch due to Contextual Search setting disabled")
+ LAUNCHER_LAUNCH_OMNI_FAILED_SETTING_DISABLED(1632),
+
+ @UiEvent(doc = "User launched Contextual Search by long-pressing home in 3-button mode")
+ LAUNCHER_LAUNCH_OMNI_SUCCESSFUL_HOME(1481),
+
+ @UiEvent(doc = "User launched Contextual Search by using accessibility System Action")
+ LAUNCHER_LAUNCH_OMNI_SUCCESSFUL_SYSTEM_ACTION(1492),
+
+ @UiEvent(doc = "User launched Contextual Search by long pressing the meta key")
+ LAUNCHER_LAUNCH_OMNI_SUCCESSFUL_META(1606),
+
+ @UiEvent(doc = "Contextual Search invocation was attempted over the notification shade")
+ LAUNCHER_LAUNCH_OMNI_ATTEMPTED_OVER_NOTIFICATION_SHADE(1485),
+
+ @UiEvent(doc = "The Contextual Search all entrypoints toggle value in Settings")
+ LAUNCHER_SETTINGS_OMNI_ALL_ENTRYPOINTS_TOGGLE_VALUE(1633),
+
+ @UiEvent(doc = "Contextual Search invocation was attempted over the keyguard")
+ LAUNCHER_LAUNCH_OMNI_ATTEMPTED_OVER_KEYGUARD(1501),
+
+ @UiEvent(doc = "Contextual Search invocation was attempted while splitscreen is active")
+ LAUNCHER_LAUNCH_OMNI_ATTEMPTED_SPLITSCREEN(1505),
+
+ @UiEvent(doc = "User long press nav handle and a long press runnable was created.")
+ LAUNCHER_OMNI_GET_LONG_PRESS_RUNNABLE(1545),
// ADD MORE
;
@@ -828,6 +869,10 @@
@UiEvent(doc = "The duration of asynchronous loading workspace")
LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_ASYNC(1367),
+
+ @UiEvent(doc = "Time passed between Contextual Search runnable creation and execution. This"
+ + " ensures that Recent animations have finished before Contextual Search starts.")
+ LAUNCHER_LATENCY_OMNI_RUNNABLE(1546),
;
private final int mId;
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 609846f..09d1146 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -23,6 +23,7 @@
import static com.android.launcher3.LauncherPrefs.IS_FIRST_LOAD_AFTER_RESTORE;
import static com.android.launcher3.LauncherPrefs.SHOULD_SHOW_SMARTSPACE;
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
+import static com.android.launcher3.icons.CacheableShortcutInfo.convertShortcutsToCacheableShortcuts;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
@@ -69,9 +70,10 @@
import com.android.launcher3.folder.FolderGridOrganizer;
import com.android.launcher3.folder.FolderNameInfos;
import com.android.launcher3.folder.FolderNameProvider;
-import com.android.launcher3.icons.ComponentWithLabelAndIcon;
+import com.android.launcher3.icons.CacheableShortcutCachingLogic;
+import com.android.launcher3.icons.CacheableShortcutInfo;
import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.icons.ShortcutCachingLogic;
+import com.android.launcher3.icons.cache.CachedObject;
import com.android.launcher3.icons.cache.CachedObjectCachingLogic;
import com.android.launcher3.icons.cache.IconCacheUpdateHandler;
import com.android.launcher3.icons.cache.LauncherActivityCachingLogic;
@@ -246,7 +248,7 @@
}
try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
- List<ShortcutInfo> allShortcuts = new ArrayList<>();
+ List<CacheableShortcutInfo> allShortcuts = new ArrayList<>();
loadWorkspace(allShortcuts, "", memoryLogger, restoreEventLogger);
// Sanitize data re-syncs widgets/shortcuts based on the workspace loaded from db.
@@ -304,7 +306,7 @@
verifyNotStopped();
logASplit("save shortcuts in icon cache");
- updateHandler.updateIcons(allShortcuts, new ShortcutCachingLogic(),
+ updateHandler.updateIcons(allShortcuts, CacheableShortcutCachingLogic.INSTANCE,
mApp.getModel()::onPackageIconsUpdated);
// Take a break
@@ -322,8 +324,10 @@
verifyNotStopped();
logASplit("save deep shortcuts in icon cache");
- updateHandler.updateIcons(allDeepShortcuts,
- new ShortcutCachingLogic(), (pkgs, user) -> { });
+ updateHandler.updateIcons(
+ convertShortcutsToCacheableShortcuts(allDeepShortcuts, allActivityList),
+ CacheableShortcutCachingLogic.INSTANCE,
+ (pkgs, user) -> { });
// Take a break
waitForIdle();
@@ -331,8 +335,7 @@
verifyNotStopped();
// fourth step
- List<ComponentWithLabelAndIcon> allWidgetsList =
- mBgDataModel.widgetsModel.update(mApp, null);
+ List<CachedObject> allWidgetsList = mBgDataModel.widgetsModel.update(mApp, null);
logASplit("load widgets");
verifyNotStopped();
@@ -360,7 +363,7 @@
}
updateHandler.updateIcons(allWidgetsList,
- new CachedObjectCachingLogic(mApp.getContext()),
+ CachedObjectCachingLogic.INSTANCE,
mApp.getModel()::onWidgetLabelsUpdated);
logASplit("save widgets in icon cache");
@@ -397,7 +400,7 @@
}
protected void loadWorkspace(
- List<ShortcutInfo> allDeepShortcuts,
+ List<CacheableShortcutInfo> allDeepShortcuts,
String selection,
LoaderMemoryLogger memoryLogger,
@Nullable LauncherRestoreEventLogger restoreEventLogger
@@ -423,7 +426,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/ModelDbController.java b/src/com/android/launcher3/model/ModelDbController.java
index da1a221..5d66d16 100644
--- a/src/com/android/launcher3/model/ModelDbController.java
+++ b/src/com/android/launcher3/model/ModelDbController.java
@@ -25,7 +25,8 @@
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.LauncherSettings.Favorites.addTableToDb;
-import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_KEY;
+import static com.android.launcher3.LauncherSettings.Settings.BLOB_KEY_PREFIX;
+import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_PROVIDER_KEY;
import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_LABEL;
import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_TAG;
import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
@@ -548,9 +549,15 @@
private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction(
LauncherWidgetHolder widgetHolder) {
ContentResolver cr = mContext.getContentResolver();
- String blobHandlerDigest = Settings.Secure.getString(cr, LAYOUT_DIGEST_KEY);
- if (!TextUtils.isEmpty(blobHandlerDigest)) {
+ String systemLayoutProvider = Settings.Secure.getString(cr, LAYOUT_PROVIDER_KEY);
+ if (TextUtils.isEmpty(systemLayoutProvider)) {
+ return null;
+ }
+
+ // Try the blob store first
+ if (systemLayoutProvider.startsWith(BLOB_KEY_PREFIX)) {
BlobStoreManager blobManager = mContext.getSystemService(BlobStoreManager.class);
+ String blobHandlerDigest = systemLayoutProvider.substring(BLOB_KEY_PREFIX.length());
try (InputStream in = new ParcelFileDescriptor.AutoCloseInputStream(
blobManager.openBlob(BlobHandle.createWithSha256(
Base64.decode(blobHandlerDigest, NO_WRAP | NO_PADDING),
@@ -562,25 +569,21 @@
}
}
- String authority = Settings.Secure.getString(cr, "launcher3.layout.provider");
- if (TextUtils.isEmpty(authority)) {
- return null;
- }
-
+ // Try contentProvider based provider
PackageManager pm = mContext.getPackageManager();
- ProviderInfo pi = pm.resolveContentProvider(authority, 0);
+ ProviderInfo pi = pm.resolveContentProvider(systemLayoutProvider, 0);
if (pi == null) {
- Log.e(TAG, "No provider found for authority " + authority);
+ Log.e(TAG, "No provider found for authority " + systemLayoutProvider);
return null;
}
- Uri uri = getLayoutUri(authority, mContext);
+ Uri uri = getLayoutUri(systemLayoutProvider, mContext);
try (InputStream in = cr.openInputStream(uri)) {
- Log.d(TAG, "Loading layout from " + authority);
+ Log.d(TAG, "Loading layout from " + systemLayoutProvider);
Resources res = pm.getResourcesForApplication(pi.applicationInfo);
return getAutoInstallsLayoutFromIS(in, widgetHolder, SourceResources.wrap(res));
} catch (Exception e) {
- Log.e(TAG, "Error getting layout stream from: " + authority , e);
+ Log.e(TAG, "Error getting layout stream from: " + systemLayoutProvider , e);
return null;
}
}
diff --git a/src/com/android/launcher3/model/ModelLauncherCallbacks.kt b/src/com/android/launcher3/model/ModelLauncherCallbacks.kt
index 2ee5b80..7ba2dad 100644
--- a/src/com/android/launcher3/model/ModelLauncherCallbacks.kt
+++ b/src/com/android/launcher3/model/ModelLauncherCallbacks.kt
@@ -17,10 +17,12 @@
package com.android.launcher3.model
import android.content.pm.LauncherApps
+import android.content.pm.PackageInstaller.SessionInfo
import android.content.pm.ShortcutInfo
import android.os.UserHandle
import android.text.TextUtils
import com.android.launcher3.LauncherModel.ModelUpdateTask
+import com.android.launcher3.config.FeatureFlags
import com.android.launcher3.logging.FileLog
import com.android.launcher3.model.PackageUpdatedTask.OP_ADD
import com.android.launcher3.model.PackageUpdatedTask.OP_REMOVE
@@ -28,6 +30,9 @@
import com.android.launcher3.model.PackageUpdatedTask.OP_UNAVAILABLE
import com.android.launcher3.model.PackageUpdatedTask.OP_UNSUSPEND
import com.android.launcher3.model.PackageUpdatedTask.OP_UPDATE
+import com.android.launcher3.pm.InstallSessionTracker
+import com.android.launcher3.pm.PackageInstallInfo
+import com.android.launcher3.util.PackageUserKey
import java.util.function.Consumer
/**
@@ -35,7 +40,7 @@
* model tasks
*/
class ModelLauncherCallbacks(private var taskExecutor: Consumer<ModelUpdateTask>) :
- LauncherApps.Callback() {
+ LauncherApps.Callback(), InstallSessionTracker.Callback {
override fun onPackageAdded(packageName: String, user: UserHandle) {
FileLog.d(TAG, "onPackageAdded triggered for packageName=$packageName, user=$user")
@@ -49,7 +54,7 @@
override fun onPackageLoadingProgressChanged(
packageName: String,
user: UserHandle,
- progress: Float
+ progress: Float,
) {
taskExecutor.accept(PackageIncrementalDownloadUpdatedTask(packageName, user, progress))
}
@@ -62,7 +67,7 @@
override fun onPackagesAvailable(
vararg packageNames: String,
user: UserHandle,
- replacing: Boolean
+ replacing: Boolean,
) {
taskExecutor.accept(PackageUpdatedTask(OP_UPDATE, user, *packageNames))
}
@@ -74,7 +79,7 @@
override fun onPackagesUnavailable(
packageNames: Array<String>,
user: UserHandle,
- replacing: Boolean
+ replacing: Boolean,
) {
if (!replacing) {
taskExecutor.accept(PackageUpdatedTask(OP_UNAVAILABLE, user, *packageNames))
@@ -88,7 +93,7 @@
override fun onShortcutsChanged(
packageName: String,
shortcuts: MutableList<ShortcutInfo>,
- user: UserHandle
+ user: UserHandle,
) {
taskExecutor.accept(ShortcutsChangedTask(packageName, shortcuts, user, true))
}
@@ -98,6 +103,37 @@
taskExecutor.accept(PackageUpdatedTask(OP_REMOVE, user, *packages.toTypedArray()))
}
+ override fun onSessionFailure(packageName: String, user: UserHandle) {
+ taskExecutor.accept(SessionFailureTask(packageName, user))
+ }
+
+ override fun onPackageStateChanged(installInfo: PackageInstallInfo) {
+ taskExecutor.accept(PackageInstallStateChangedTask(installInfo))
+ }
+
+ override fun onUpdateSessionDisplay(key: PackageUserKey, info: SessionInfo) {
+ /** Updates the icons and label of all pending icons for the provided package name. */
+ taskExecutor.accept { controller, _, _ ->
+ controller.app.iconCache.updateSessionCache(key, info)
+ }
+ taskExecutor.accept(
+ CacheDataUpdatedTask(
+ CacheDataUpdatedTask.OP_SESSION_UPDATE,
+ key.mUser,
+ hashSetOf(key.mPackageName),
+ )
+ )
+ }
+
+ override fun onInstallSessionCreated(sessionInfo: PackageInstallInfo) {
+ if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
+ taskExecutor.accept { taskController, _, apps ->
+ apps.addPromiseApp(taskController.app.context, sessionInfo)
+ taskController.bindApplicationsIfNeeded()
+ }
+ }
+ }
+
companion object {
private const val TAG = "LauncherAppsCallbackImpl"
}
diff --git a/src/com/android/launcher3/model/SessionFailureTask.kt b/src/com/android/launcher3/model/SessionFailureTask.kt
new file mode 100644
index 0000000..0d006fa
--- /dev/null
+++ b/src/com/android/launcher3/model/SessionFailureTask.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model
+
+import android.content.ComponentName
+import android.os.UserHandle
+import android.text.TextUtils
+import com.android.launcher3.LauncherModel.ModelUpdateTask
+import com.android.launcher3.icons.cache.BaseIconCache
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.util.ApplicationInfoWrapper
+import com.android.launcher3.util.ItemInfoMatcher
+
+/** Model task run when there is a package install session failure */
+class SessionFailureTask(val packageName: String, val user: UserHandle) : ModelUpdateTask {
+
+ override fun execute(
+ taskController: ModelTaskController,
+ dataModel: BgDataModel,
+ apps: AllAppsList,
+ ) {
+ val iconCache = taskController.app.iconCache
+ val isAppArchived =
+ ApplicationInfoWrapper(taskController.app.context, packageName, user).isArchived()
+ synchronized(dataModel) {
+ if (isAppArchived) {
+ val updatedItems = mutableListOf<WorkspaceItemInfo>()
+ // Remove package icon cache entry for archived app in case of a session
+ // failure.
+ iconCache.remove(
+ ComponentName(packageName, packageName + BaseIconCache.EMPTY_CLASS_NAME),
+ user,
+ )
+ for (info in dataModel.itemsIdMap) {
+ if (info is WorkspaceItemInfo && info.isArchived && user == info.user) {
+ // Refresh icons on the workspace for archived apps.
+ iconCache.getTitleAndIcon(info, info.usingLowResIcon())
+ updatedItems.add(info)
+ }
+ }
+
+ if (updatedItems.isNotEmpty()) {
+ taskController.bindUpdatedWorkspaceItems(updatedItems)
+ }
+ apps.updateIconsAndLabels(hashSetOf(packageName), user)
+ taskController.bindApplicationsIfNeeded()
+ } else {
+ val removedItems =
+ dataModel.itemsIdMap.filter { info ->
+ (info is WorkspaceItemInfo && info.hasPromiseIconUi()) &&
+ user == info.user &&
+ TextUtils.equals(packageName, info.intent.getPackage())
+ }
+ if (removedItems.isNotEmpty()) {
+ taskController.deleteAndBindComponentsRemoved(
+ ItemInfoMatcher.ofItems(removedItems),
+ "removed because install session failed",
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index 55c4d30..b5a7382 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -24,6 +24,7 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel.ModelUpdateTask;
import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.icons.CacheableShortcutInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.shortcuts.ShortcutRequest;
@@ -79,9 +80,9 @@
}
if (!matchingWorkspaceItems.isEmpty()) {
+ ApplicationInfoWrapper infoWrapper =
+ new ApplicationInfoWrapper(context, mPackageName, mUser);
if (mShortcuts.isEmpty()) {
- ApplicationInfoWrapper infoWrapper =
- new ApplicationInfoWrapper(context, mPackageName, mUser);
// Verify that the app is indeed installed.
if (!infoWrapper.isInstalled() && !infoWrapper.isArchived()) {
// App is not installed or archived, ignoring package events
@@ -103,7 +104,6 @@
if (!fullDetails.isPinned()) {
continue;
}
-
String sid = fullDetails.getId();
nonPinnedIds.remove(sid);
matchingWorkspaceItems
@@ -111,7 +111,8 @@
.filter(itemInfo -> sid.equals(itemInfo.getDeepShortcutId()))
.forEach(workspaceItemInfo -> {
workspaceItemInfo.updateFromDeepShortcutInfo(fullDetails, context);
- app.getIconCache().getShortcutIcon(workspaceItemInfo, fullDetails);
+ app.getIconCache().getShortcutIcon(workspaceItemInfo,
+ new CacheableShortcutInfo(fullDetails, infoWrapper));
updatedWorkspaceItemInfos.add(workspaceItemInfo);
});
}
diff --git a/src/com/android/launcher3/model/WidgetItem.java b/src/com/android/launcher3/model/WidgetItem.java
index ac9f2d6..e757a68 100644
--- a/src/com/android/launcher3/model/WidgetItem.java
+++ b/src/com/android/launcher3/model/WidgetItem.java
@@ -7,7 +7,6 @@
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.util.SparseArray;
import android.widget.RemoteViews;
@@ -75,10 +74,10 @@
this(info, idp, iconCache, context, new WidgetManagerHelper(context));
}
- public WidgetItem(ShortcutConfigActivityInfo info, IconCache iconCache, PackageManager pm) {
+ public WidgetItem(ShortcutConfigActivityInfo info, IconCache iconCache) {
super(info.getComponent(), info.getUser());
label = info.isPersistable() ? iconCache.getTitleNoCache(info) :
- Utilities.trim(info.getLabel(pm));
+ Utilities.trim(info.getLabel());
description = null;
widgetInfo = null;
activityInfo = info;
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index c949ce6..b450f46 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -14,7 +14,6 @@
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.util.Log;
import android.util.Pair;
@@ -27,8 +26,8 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.icons.ComponentWithLabelAndIcon;
import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.icons.cache.CachedObject;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.pm.ShortcutConfigActivityInfo;
import com.android.launcher3.util.ComponentKey;
@@ -96,20 +95,18 @@
* @param packageUser If null, all widgets and shortcuts are updated and returned, otherwise
* only widgets and shortcuts associated with the package/user are.
*/
- public List<ComponentWithLabelAndIcon> update(
+ public List<CachedObject> update(
LauncherAppState app, @Nullable PackageUserKey packageUser) {
if (!WIDGETS_ENABLED) {
- return Collections.emptyList();
+ return new ArrayList<>();
}
Preconditions.assertWorkerThread();
Context context = app.getContext();
final ArrayList<WidgetItem> widgetsAndShortcuts = new ArrayList<>();
- List<ComponentWithLabelAndIcon> updatedItems = new ArrayList<>();
+ List<CachedObject> updatedItems = new ArrayList<>();
try {
InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
- PackageManager pm = app.getContext().getPackageManager();
-
// Widgets
WidgetManagerHelper widgetManager = new WidgetManagerHelper(context);
for (AppWidgetProviderInfo widgetInfo : widgetManager.getAllProviders(packageUser)) {
@@ -125,7 +122,7 @@
// Shortcuts
for (ShortcutConfigActivityInfo info :
queryList(context, packageUser)) {
- widgetsAndShortcuts.add(new WidgetItem(info, app.getIconCache(), pm));
+ widgetsAndShortcuts.add(new WidgetItem(info, app.getIconCache()));
updatedItems.add(info);
}
setWidgetsAndShortcuts(widgetsAndShortcuts, app, packageUser);
@@ -190,8 +187,7 @@
WidgetItem item = items.get(i);
if (item.user.equals(user)) {
if (item.activityInfo != null) {
- items.set(i, new WidgetItem(item.activityInfo, app.getIconCache(),
- app.getContext().getPackageManager()));
+ items.set(i, new WidgetItem(item.activityInfo, app.getIconCache()));
} else {
items.set(i, new WidgetItem(item.widgetInfo,
app.getInvariantDeviceProfile(), app.getIconCache(),
diff --git a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
index 18c7f95..c02336e 100644
--- a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
+++ b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
@@ -32,6 +32,7 @@
import com.android.launcher3.LauncherSettings.Favorites
import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError
import com.android.launcher3.config.FeatureFlags
+import com.android.launcher3.icons.CacheableShortcutInfo
import com.android.launcher3.logging.FileLog
import com.android.launcher3.model.data.AppInfo
import com.android.launcher3.model.data.AppPairInfo
@@ -76,7 +77,7 @@
private val pmHelper: PackageManagerHelper,
private val iconRequestInfos: MutableList<IconRequestInfo<WorkspaceItemInfo>>,
private val unlockedUsers: LongSparseArray<Boolean>,
- private val allDeepShortcuts: MutableList<ShortcutInfo>,
+ private val allDeepShortcuts: MutableList<CacheableShortcutInfo>,
) {
private val isSafeMode = app.isSafeModeEnabled
@@ -278,13 +279,14 @@
info = WorkspaceItemInfo(pinnedShortcut, app.context)
// If the pinned deep shortcut is no longer published,
// use the last saved icon instead of the default.
- iconCache.getShortcutIcon(info, pinnedShortcut, c::loadIcon)
+ val csi = CacheableShortcutInfo(pinnedShortcut, appInfoWrapper)
+ iconCache.getShortcutIcon(info, csi, c::loadIcon)
if (appInfoWrapper.isSuspended()) {
info.runtimeStatusFlags =
info.runtimeStatusFlags or ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED
}
intent = info.getIntent()
- allDeepShortcuts.add(pinnedShortcut)
+ allDeepShortcuts.add(csi)
} else {
// Create a shortcut info in disabled mode for now.
info = c.loadSimpleWorkspaceItem()
diff --git a/src/com/android/launcher3/model/data/AppPairInfo.kt b/src/com/android/launcher3/model/data/AppPairInfo.kt
index 2eb6154..3496c17 100644
--- a/src/com/android/launcher3/model/data/AppPairInfo.kt
+++ b/src/com/android/launcher3/model/data/AppPairInfo.kt
@@ -74,7 +74,7 @@
(ActivityContext.lookupContext(context) as ActivityContext).getDeviceProfile().isTablet
return Pair(
isTablet || !getFirstApp().isNonResizeable(),
- isTablet || !getSecondApp().isNonResizeable()
+ isTablet || !getSecondApp().isNonResizeable(),
)
}
@@ -105,10 +105,10 @@
}
/** Generates an ItemInfo for logging. */
- override fun buildProto(cInfo: CollectionInfo?): LauncherAtom.ItemInfo {
+ override fun buildProto(cInfo: CollectionInfo?, context: Context): LauncherAtom.ItemInfo {
val appPairIcon = LauncherAtom.FolderIcon.newBuilder().setCardinality(contents.size)
appPairIcon.setLabelInfo(title.toString())
- return getDefaultItemInfoBuilder()
+ return getDefaultItemInfoBuilder(context)
.setFolderIcon(appPairIcon)
.setRank(rank)
.setContainerInfo(getContainerInfo())
diff --git a/src/com/android/launcher3/model/data/CollectionInfo.kt b/src/com/android/launcher3/model/data/CollectionInfo.kt
index 4f5e12f..12ba164 100644
--- a/src/com/android/launcher3/model/data/CollectionInfo.kt
+++ b/src/com/android/launcher3/model/data/CollectionInfo.kt
@@ -17,7 +17,6 @@
package com.android.launcher3.model.data
import com.android.launcher3.LauncherSettings
-import com.android.launcher3.logger.LauncherAtom
import com.android.launcher3.util.ContentWriter
import java.util.function.Predicate
@@ -42,9 +41,4 @@
super.onAddToDatabase(writer)
writer.put(LauncherSettings.Favorites.TITLE, title)
}
-
- /** Returns the collection wrapped as {@link LauncherAtom.ItemInfo} for logging. */
- override fun buildProto(): LauncherAtom.ItemInfo {
- return buildProto(null)
- }
}
diff --git a/src/com/android/launcher3/model/data/FolderInfo.java b/src/com/android/launcher3/model/data/FolderInfo.java
index 18d2b85..f0f2892 100644
--- a/src/com/android/launcher3/model/data/FolderInfo.java
+++ b/src/com/android/launcher3/model/data/FolderInfo.java
@@ -24,6 +24,8 @@
import static com.android.launcher3.logger.LauncherAtom.Attribute.MANUAL_LABEL;
import static com.android.launcher3.logger.LauncherAtom.Attribute.SUGGESTED_LABEL;
+import android.content.Context;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -245,13 +247,13 @@
@NonNull
@Override
- public LauncherAtom.ItemInfo buildProto(@Nullable CollectionInfo cInfo) {
+ public LauncherAtom.ItemInfo buildProto(@Nullable CollectionInfo cInfo, Context context) {
FolderIcon.Builder folderIcon = FolderIcon.newBuilder()
.setCardinality(getContents().size());
if (LabelState.SUGGESTED.equals(getLabelState())) {
folderIcon.setLabelInfo(title.toString());
}
- return getDefaultItemInfoBuilder()
+ return getDefaultItemInfoBuilder(context)
.setFolderIcon(folderIcon)
.setRank(rank)
.addItemAttributes(getLabelState().mLogAttribute)
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index b706d24..c22a8a5 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -36,6 +36,7 @@
import android.content.ComponentName;
import android.content.ContentValues;
+import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Process;
@@ -352,16 +353,16 @@
* Creates {@link LauncherAtom.ItemInfo} with important fields and parent container info.
*/
@NonNull
- public LauncherAtom.ItemInfo buildProto() {
- return buildProto(null);
+ public LauncherAtom.ItemInfo buildProto(Context context) {
+ return buildProto(null, context);
}
/**
* Creates {@link LauncherAtom.ItemInfo} with important fields and parent container info.
*/
@NonNull
- public LauncherAtom.ItemInfo buildProto(@Nullable final CollectionInfo cInfo) {
- LauncherAtom.ItemInfo.Builder itemBuilder = getDefaultItemInfoBuilder();
+ public LauncherAtom.ItemInfo buildProto(@Nullable final CollectionInfo cInfo, Context context) {
+ LauncherAtom.ItemInfo.Builder itemBuilder = getDefaultItemInfoBuilder(context);
Optional<ComponentName> nullableComponent = Optional.ofNullable(getTargetComponent());
switch (itemType) {
case ITEM_TYPE_APPLICATION:
@@ -434,10 +435,10 @@
}
@NonNull
- protected LauncherAtom.ItemInfo.Builder getDefaultItemInfoBuilder() {
+ protected LauncherAtom.ItemInfo.Builder getDefaultItemInfoBuilder(Context context) {
LauncherAtom.ItemInfo.Builder itemBuilder = LauncherAtom.ItemInfo.newBuilder();
- SettingsCache.INSTANCE.executeIfCreated(cache ->
- itemBuilder.setIsKidsMode(cache.getValue(NAV_BAR_KIDS_MODE, 0)));
+ itemBuilder.setIsKidsMode(
+ SettingsCache.INSTANCE.get(context).getValue(NAV_BAR_KIDS_MODE, 0));
UserCache.INSTANCE.executeIfCreated(cache ->
itemBuilder.setUserType(getUserType(cache.getUserInfo(user))));
itemBuilder.setRank(rank);
diff --git a/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java b/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
index 361f09d..7569ed5 100644
--- a/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
@@ -24,6 +24,7 @@
import android.appwidget.AppWidgetHostView;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Process;
@@ -270,8 +271,9 @@
@NonNull
@Override
- public LauncherAtom.ItemInfo buildProto(@Nullable CollectionInfo collectionInfo) {
- LauncherAtom.ItemInfo info = super.buildProto(collectionInfo);
+ public LauncherAtom.ItemInfo buildProto(
+ @Nullable CollectionInfo collectionInfo, Context context) {
+ LauncherAtom.ItemInfo info = super.buildProto(collectionInfo, context);
return info.toBuilder()
.setWidget(info.getWidget().toBuilder().setWidgetFeatures(widgetFeatures))
.addItemAttributes(getAttribute(sourceContainer))
diff --git a/src/com/android/launcher3/pm/InstallSessionHelper.java b/src/com/android/launcher3/pm/InstallSessionHelper.java
index 124907f..afc5117 100644
--- a/src/com/android/launcher3/pm/InstallSessionHelper.java
+++ b/src/com/android/launcher3/pm/InstallSessionHelper.java
@@ -31,15 +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.IntArray;
import com.android.launcher3.util.IntSet;
-import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Preconditions;
-import com.android.launcher3.util.SafeCloseable;
import java.util.ArrayList;
import java.util.HashMap;
@@ -47,11 +49,14 @@
import java.util.List;
import java.util.Objects;
+import javax.inject.Inject;
+
/**
* Utility class to tracking install sessions
*/
@SuppressWarnings("NewApi")
-public class InstallSessionHelper implements SafeCloseable {
+@LauncherAppSingleton
+public class InstallSessionHelper {
@NonNull
private static final String LOG = "InstallSessionHelper";
@@ -64,8 +69,8 @@
private static final boolean DEBUG = false;
@NonNull
- public static final MainThreadInitializedObject<InstallSessionHelper> INSTANCE =
- new MainThreadInitializedObject<>(InstallSessionHelper::new);
+ public static final DaggerSingletonObject<InstallSessionHelper> INSTANCE =
+ new DaggerSingletonObject<>(LauncherBaseAppComponent::getInstallSessionHelper);
@Nullable
private final LauncherApps mLauncherApps;
@@ -82,15 +87,13 @@
@Nullable
private IntSet mPromiseIconIds;
- public InstallSessionHelper(@NonNull final Context context) {
+ @Inject
+ public InstallSessionHelper(@NonNull @ApplicationContext final Context context) {
mInstaller = context.getPackageManager().getPackageInstaller();
mAppContext = context.getApplicationContext();
mLauncherApps = context.getSystemService(LauncherApps.class);
}
- @Override
- public void close() { }
-
@WorkerThread
@NonNull
private IntSet getPromiseIconIds() {
diff --git a/src/com/android/launcher3/pm/PinRequestHelper.java b/src/com/android/launcher3/pm/PinRequestHelper.java
index 47afeef..2ed6591 100644
--- a/src/com/android/launcher3/pm/PinRequestHelper.java
+++ b/src/com/android/launcher3/pm/PinRequestHelper.java
@@ -32,7 +32,8 @@
import androidx.annotation.Nullable;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.icons.ShortcutCachingLogic;
+import com.android.launcher3.icons.CacheableShortcutCachingLogic;
+import com.android.launcher3.icons.CacheableShortcutInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
public class PinRequestHelper {
@@ -78,7 +79,8 @@
// Apply the unbadged icon synchronously using the caching logic directly and
// fetch the actual icon asynchronously.
LauncherAppState app = LauncherAppState.getInstance(context);
- info.bitmap = new ShortcutCachingLogic().loadIcon(context, app.getIconCache(), si);
+ info.bitmap = CacheableShortcutCachingLogic.INSTANCE.loadIcon(
+ context, app.getIconCache(), new CacheableShortcutInfo(si, context));
app.getModel().updateAndBindWorkspaceItem(info, si);
return info;
} else {
diff --git a/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java b/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java
index 351ebce..409174e 100644
--- a/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java
+++ b/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java
@@ -26,9 +26,9 @@
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;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Process;
@@ -40,9 +40,10 @@
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
-import com.android.launcher3.icons.ComponentWithLabelAndIcon;
-import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.icons.cache.BaseIconCache;
+import com.android.launcher3.icons.cache.CachedObject;
import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.util.ApplicationInfoWrapper;
import com.android.launcher3.util.PackageUserKey;
import java.util.ArrayList;
@@ -52,16 +53,26 @@
/**
* Wrapper class for representing a shortcut configure activity.
*/
-public abstract class ShortcutConfigActivityInfo implements ComponentWithLabelAndIcon {
+public abstract class ShortcutConfigActivityInfo implements CachedObject {
private static final String TAG = "SCActivityInfo";
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
@@ -79,7 +90,7 @@
}
@Override
- public abstract Drawable getFullResIcon(IconCache cache);
+ public abstract Drawable getFullResIcon(BaseIconCache cache);
/**
* Return a WorkspaceItemInfo, if it can be created directly on drop, without requiring any
@@ -89,6 +100,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());
@@ -107,7 +124,7 @@
}
/**
- * Returns true if various properties ({@link #getLabel(PackageManager)},
+ * Returns true if various properties ({@link #getLabel()},
* {@link #getFullResIcon}) can be safely persisted.
*/
public boolean isPersistable() {
@@ -120,18 +137,19 @@
private final LauncherActivityInfo mInfo;
public ShortcutConfigActivityInfoVO(LauncherActivityInfo info) {
- super(info.getComponentName(), info.getUser());
+ super(info.getComponentName(), info.getUser(),
+ new ApplicationInfoWrapper(info.getApplicationInfo()));
mInfo = info;
}
@Override
- public CharSequence getLabel(PackageManager pm) {
+ public CharSequence getLabel() {
return mInfo.getLabel();
}
@Override
- public Drawable getFullResIcon(IconCache cache) {
- return cache.getFullResIcon(mInfo);
+ public Drawable getFullResIcon(BaseIconCache cache) {
+ 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/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/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index 303290d..763f3ba 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -18,6 +18,7 @@
import static android.animation.ValueAnimator.areAnimatorsEnabled;
+import static com.android.launcher3.Flags.enableStateManagerProtoLog;
import static com.android.launcher3.anim.AnimatorPlaybackController.callListenerCommandRecursively;
import static com.android.launcher3.states.StateAnimationConfig.HANDLE_STATE_APPLY;
import static com.android.launcher3.states.StateAnimationConfig.SKIP_ALL_ANIMATIONS;
@@ -39,6 +40,7 @@
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
import com.android.launcher3.states.StateAnimationConfig.AnimationPropertyFlags;
+import com.android.launcher3.util.StateManagerProtoLogProxy;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -243,7 +245,10 @@
private void goToState(
STATE_TYPE state, boolean animated, long delay, AnimatorListener listener) {
- if (DEBUG) {
+ if (enableStateManagerProtoLog()) {
+ StateManagerProtoLogProxy.logGoToState(
+ mState, state, getTrimmedStackTrace("StateManager.goToState"));
+ } else if (DEBUG) {
Log.d(TAG, "goToState - fromState: " + mState + ", toState: " + state
+ ", partial trace:\n" + getTrimmedStackTrace("StateManager.goToState"));
}
@@ -331,7 +336,10 @@
*/
public AnimatorSet createAtomicAnimation(
STATE_TYPE fromState, STATE_TYPE toState, StateAnimationConfig config) {
- if (DEBUG) {
+ if (enableStateManagerProtoLog()) {
+ StateManagerProtoLogProxy.logCreateAtomicAnimation(
+ mState, toState, getTrimmedStackTrace("StateManager.createAtomicAnimation"));
+ } else if (DEBUG) {
Log.d(TAG, "createAtomicAnimation - fromState: " + fromState + ", toState: " + toState
+ ", partial trace:\n" + getTrimmedStackTrace(
"StateManager.createAtomicAnimation"));
@@ -408,7 +416,9 @@
mState = state;
mStatefulContainer.onStateSetStart(mState);
- if (DEBUG) {
+ if (enableStateManagerProtoLog()) {
+ StateManagerProtoLogProxy.logOnStateTransitionStart(state);
+ } else if (DEBUG) {
Log.d(TAG, "onStateTransitionStart - state: " + state);
}
for (int i = mListeners.size() - 1; i >= 0; i--) {
@@ -428,7 +438,9 @@
setRestState(null);
}
- if (DEBUG) {
+ if (enableStateManagerProtoLog()) {
+ StateManagerProtoLogProxy.logOnStateTransitionEnd(state);
+ } else if (DEBUG) {
Log.d(TAG, "onStateTransitionEnd - state: " + state);
}
for (int i = mListeners.size() - 1; i >= 0; i--) {
@@ -468,7 +480,11 @@
* Cancels the current animation.
*/
public void cancelAnimation() {
- if (DEBUG && mConfig.currentAnimation != null) {
+ if (enableStateManagerProtoLog()) {
+ StateManagerProtoLogProxy.logCancelAnimation(
+ mConfig.currentAnimation != null,
+ getTrimmedStackTrace("StateManager.cancelAnimation"));
+ } else if (DEBUG && mConfig.currentAnimation != null) {
Log.d(TAG, "cancelAnimation - with ongoing animation"
+ ", partial trace:\n" + getTrimmedStackTrace("StateManager.cancelAnimation"));
}
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index fdb37f0..b3bcada 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -26,8 +26,6 @@
import static com.android.launcher3.util.window.WindowManagerProxy.MIN_TABLET_WIDTH;
import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Handler;
import android.os.Message;
@@ -36,13 +34,14 @@
import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherPrefChangeListener;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.util.DisplayController;
/**
* Utility class to manage launcher rotation
*/
-public class RotationHelper implements OnSharedPreferenceChangeListener,
+public class RotationHelper implements LauncherPrefChangeListener,
DeviceProfile.OnDeviceProfileChangeListener,
DisplayController.DisplayInfoChangeListener {
@@ -112,7 +111,7 @@
}
@Override
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
+ public void onPrefChanged(String s) {
if (mDestroyed || mIgnoreAutoRotateSettings) return;
boolean wasRotationEnabled = mHomeRotationEnabled;
mHomeRotationEnabled = LauncherPrefs.get(mActivity).get(ALLOW_ROTATION);
diff --git a/src/com/android/launcher3/util/ApiWrapper.java b/src/com/android/launcher3/util/ApiWrapper.java
index 095518c..21f91acd 100644
--- a/src/com/android/launcher3/util/ApiWrapper.java
+++ b/src/com/android/launcher3/util/ApiWrapper.java
@@ -24,6 +24,7 @@
import android.app.role.RoleManager;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.ShortcutInfo;
import android.graphics.drawable.ColorDrawable;
@@ -32,6 +33,7 @@
import android.os.UserManager;
import android.util.ArrayMap;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.BuildConfig;
@@ -156,6 +158,14 @@
}
}
+ /**
+ * Returns a hash to uniquely identify a particular version of appInfo
+ */
+ public String getApplicationInfoHash(@NonNull ApplicationInfo appInfo) {
+ // The hashString in source dir changes with every install
+ return appInfo.sourceDir;
+ }
+
@Override
public void close() { }
diff --git a/src/com/android/launcher3/util/DaggerSingletonObject.java b/src/com/android/launcher3/util/DaggerSingletonObject.java
index b8cf2ae..febe6af 100644
--- a/src/com/android/launcher3/util/DaggerSingletonObject.java
+++ b/src/com/android/launcher3/util/DaggerSingletonObject.java
@@ -29,7 +29,7 @@
* We should delete this class at the end and use @Inject to get dagger provided singletons.
*/
-public class DaggerSingletonObject<T extends SafeCloseable> {
+public class DaggerSingletonObject<T> {
private final Function<LauncherAppComponent, T> mFunction;
public DaggerSingletonObject(Function<LauncherAppComponent, T> function) {
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index c59cc81..0b45118 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -35,7 +35,6 @@
import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.Intent;
-import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
@@ -51,6 +50,7 @@
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.InvariantDeviceProfile.DeviceType;
+import com.android.launcher3.LauncherPrefChangeListener;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.Utilities;
import com.android.launcher3.logging.FileLog;
@@ -116,8 +116,7 @@
private Info mInfo;
private boolean mDestroyed = false;
- private SharedPreferences.OnSharedPreferenceChangeListener
- mTaskbarPinningPreferenceChangeListener;
+ private LauncherPrefChangeListener mTaskbarPinningPreferenceChangeListener;
@VisibleForTesting
protected DisplayController(Context context) {
@@ -142,19 +141,18 @@
}
private void attachTaskbarPinningSharedPreferenceChangeListener(Context context) {
- mTaskbarPinningPreferenceChangeListener =
- (sharedPreferences, key) -> {
- LauncherPrefs prefs = LauncherPrefs.get(mContext);
- boolean isTaskbarPinningChanged = TASKBAR_PINNING_KEY.equals(key)
- && mInfo.mIsTaskbarPinned != prefs.get(TASKBAR_PINNING);
- boolean isTaskbarPinningDesktopModeChanged =
- TASKBAR_PINNING_DESKTOP_MODE_KEY.equals(key)
- && mInfo.mIsTaskbarPinnedInDesktopMode != prefs.get(
- TASKBAR_PINNING_IN_DESKTOP_MODE);
- if (isTaskbarPinningChanged || isTaskbarPinningDesktopModeChanged) {
- notifyConfigChange();
- }
- };
+ mTaskbarPinningPreferenceChangeListener = key -> {
+ LauncherPrefs prefs = LauncherPrefs.get(mContext);
+ boolean isTaskbarPinningChanged = TASKBAR_PINNING_KEY.equals(key)
+ && mInfo.mIsTaskbarPinned != prefs.get(TASKBAR_PINNING);
+ boolean isTaskbarPinningDesktopModeChanged =
+ TASKBAR_PINNING_DESKTOP_MODE_KEY.equals(key)
+ && mInfo.mIsTaskbarPinnedInDesktopMode != prefs.get(
+ TASKBAR_PINNING_IN_DESKTOP_MODE);
+ if (isTaskbarPinningChanged || isTaskbarPinningDesktopModeChanged) {
+ notifyConfigChange();
+ }
+ };
LauncherPrefs.get(context).addListener(
mTaskbarPinningPreferenceChangeListener, TASKBAR_PINNING);
diff --git a/src/com/android/launcher3/util/MainThreadInitializedObject.java b/src/com/android/launcher3/util/MainThreadInitializedObject.java
index a7d5c13..9a70298 100644
--- a/src/com/android/launcher3/util/MainThreadInitializedObject.java
+++ b/src/com/android/launcher3/util/MainThreadInitializedObject.java
@@ -50,7 +50,7 @@
public T get(Context context) {
Context app = context.getApplicationContext();
- if (app instanceof SandboxApplication sc) {
+ if (app instanceof ObjectSandbox sc) {
return sc.getObject(this);
}
@@ -100,7 +100,8 @@
T get(Context context);
}
- public interface SandboxApplication {
+ /** Sandbox for isolating {@link MainThreadInitializedObject} instances from Launcher. */
+ public interface ObjectSandbox {
/**
* Find a cached object from mObjectMap if we have already created one. If not, generate
@@ -116,7 +117,7 @@
<T extends SafeCloseable> void putObject(MainThreadInitializedObject<T> object, T value);
/**
- * Returns whether this context should cleanup all objects when its destroyed or leave it
+ * Returns whether this sandbox 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.
@@ -137,7 +138,7 @@
* Abstract Context which allows custom implementations for
* {@link MainThreadInitializedObject} providers
*/
- public static class SandboxContext extends LauncherApplication implements SandboxApplication {
+ public static class SandboxContext extends LauncherApplication implements ObjectSandbox {
private static final String TAG = "SandboxContext";
@@ -159,8 +160,8 @@
@Override
public boolean shouldCleanUpOnDestroy() {
- return (getBaseContext().getApplicationContext() instanceof SandboxApplication sa)
- ? sa.shouldCleanUpOnDestroy() : true;
+ return (getBaseContext().getApplicationContext() instanceof ObjectSandbox os)
+ ? os.shouldCleanUpOnDestroy() : true;
}
public void onDestroy() {
diff --git a/src/com/android/launcher3/util/ScreenOnTracker.java b/src/com/android/launcher3/util/ScreenOnTracker.java
index 8ee799a..3582ad8 100644
--- a/src/com/android/launcher3/util/ScreenOnTracker.java
+++ b/src/com/android/launcher3/util/ScreenOnTracker.java
@@ -26,16 +26,22 @@
import androidx.annotation.VisibleForTesting;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.dagger.LauncherBaseAppComponent;
+
import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.function.Consumer;
+
+import javax.inject.Inject;
/**
* Utility class for tracking if the screen is currently on or off
*/
+@LauncherAppSingleton
public class ScreenOnTracker implements SafeCloseable {
- public static final MainThreadInitializedObject<ScreenOnTracker> INSTANCE =
- new MainThreadInitializedObject<>(ScreenOnTracker::new);
+ public static final DaggerSingletonObject<ScreenOnTracker> INSTANCE =
+ new DaggerSingletonObject<>(LauncherBaseAppComponent::getScreenOnTracker);
private final SimpleBroadcastReceiver mReceiver;
private final CopyOnWriteArrayList<ScreenOnListener> mListeners = new CopyOnWriteArrayList<>();
@@ -43,23 +49,26 @@
private final Context mContext;
private boolean mIsScreenOn;
- private ScreenOnTracker(Context context) {
+ @Inject
+ ScreenOnTracker(@ApplicationContext Context context, DaggerSingletonTracker tracker) {
// Assume that the screen is on to begin with
mContext = context;
mReceiver = new SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, this::onReceive);
- init();
+ init(tracker);
}
@VisibleForTesting
- ScreenOnTracker(Context context, SimpleBroadcastReceiver receiver) {
+ ScreenOnTracker(@ApplicationContext Context context, SimpleBroadcastReceiver receiver,
+ DaggerSingletonTracker tracker) {
mContext = context;
mReceiver = receiver;
- init();
+ init(tracker);
}
- private void init() {
+ private void init(DaggerSingletonTracker tracker) {
mIsScreenOn = true;
mReceiver.register(mContext, ACTION_SCREEN_ON, ACTION_SCREEN_OFF, ACTION_USER_PRESENT);
+ ExecutorUtil.executeSyncOnMainOrFail(() -> tracker.addCloseable(this));
}
@Override
diff --git a/src/com/android/launcher3/util/SettingsCache.java b/src/com/android/launcher3/util/SettingsCache.java
index cd6701d..a1ed499 100644
--- a/src/com/android/launcher3/util/SettingsCache.java
+++ b/src/com/android/launcher3/util/SettingsCache.java
@@ -25,14 +25,21 @@
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
+import android.os.Looper;
import android.provider.Settings;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.dagger.LauncherBaseAppComponent;
+
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
+import javax.inject.Inject;
+
/**
* ContentObserver over Settings keys that also has a caching layer.
* Consumers can register for callbacks via {@link #register(Uri, OnChangeListener)} and
@@ -47,6 +54,7 @@
*
* Cache will also be updated if a key queried is missing (even if it has no listeners registered).
*/
+@LauncherAppSingleton
public class SettingsCache extends ContentObserver implements SafeCloseable {
/** Hidden field Settings.Secure.NOTIFICATION_BADGING */
@@ -79,12 +87,14 @@
/**
* Singleton instance
*/
- public static MainThreadInitializedObject<SettingsCache> INSTANCE =
- new MainThreadInitializedObject<>(SettingsCache::new);
+ public static final DaggerSingletonObject<SettingsCache> INSTANCE =
+ new DaggerSingletonObject<>(LauncherBaseAppComponent::getSettingsCache);
- private SettingsCache(Context context) {
- super(new Handler());
+ @Inject
+ SettingsCache(@ApplicationContext Context context, DaggerSingletonTracker tracker) {
+ super(new Handler(Looper.getMainLooper()));
mResolver = context.getContentResolver();
+ ExecutorUtil.executeSyncOnMainOrFail(() -> tracker.addCloseable(this));
}
@Override
diff --git a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
index ab42839..e100157 100644
--- a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
+++ b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
@@ -213,8 +213,8 @@
// Draw icon in the center.
try {
- Drawable icon = LauncherAppState.getInstance(mContext).getIconCache()
- .getFullResIcon(info.provider.getPackageName(), info.icon);
+ Drawable icon = info.getFullResIcon(
+ LauncherAppState.getInstance(mContext).getIconCache());
if (icon != null) {
int appIconSize = dp.iconSizePx;
int iconSize = (int) Math.min(appIconSize * scale,
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
index e77ba24..b877d7a 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
@@ -3,6 +3,7 @@
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Point;
import android.graphics.Rect;
@@ -10,11 +11,13 @@
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;
-import com.android.launcher3.icons.ComponentWithLabelAndIcon;
-import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.icons.cache.BaseIconCache;
+import com.android.launcher3.icons.cache.CachedObject;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
/**
@@ -23,8 +26,7 @@
* (who's implementation is owned by the launcher). This object represents a widget type / class,
* as opposed to a widget instance, and so should not be confused with {@link LauncherAppWidgetInfo}
*/
-public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo
- implements ComponentWithLabelAndIcon {
+public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo implements CachedObject {
public static final String CLS_CUSTOM_WIDGET_PREFIX = "#custom-widget-";
@@ -66,6 +68,8 @@
protected boolean mIsMinSizeFulfilled;
+ private PackageManager mPM;
+
public static LauncherAppWidgetProviderInfo fromProviderInfo(Context context,
AppWidgetProviderInfo info) {
final LauncherAppWidgetProviderInfo launcherInfo;
@@ -94,6 +98,7 @@
}
public void initSpans(Context context, InvariantDeviceProfile idp) {
+ mPM = context.getApplicationContext().getPackageManager();
int minSpanX = 0;
int minSpanY = 0;
int maxSpanX = idp.numColumns;
@@ -101,7 +106,6 @@
int spanX = 0;
int spanY = 0;
-
Point cellSize = new Point();
for (DeviceProfile dp : idp.supportedProfiles) {
dp.getCellSize(cellSize);
@@ -185,8 +189,9 @@
(widgetSize + widgetPadding + cellSpacing) / (cellSize + cellSpacing)));
}
- public String getLabel(PackageManager packageManager) {
- return super.loadLabel(packageManager);
+ @Override
+ public CharSequence getLabel() {
+ return super.loadLabel(mPM);
}
public Point getMinSpans() {
@@ -222,7 +227,13 @@
}
@Override
- public Drawable getFullResIcon(IconCache cache) {
- return cache.getFullResIcon(provider.getPackageName(), icon);
+ public Drawable getFullResIcon(BaseIconCache cache) {
+ 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/LocalColorExtractor.java b/src/com/android/launcher3/widget/LocalColorExtractor.java
index 7b500c7..d26eb38 100644
--- a/src/com/android/launcher3/widget/LocalColorExtractor.java
+++ b/src/com/android/launcher3/widget/LocalColorExtractor.java
@@ -48,4 +48,9 @@
public SparseIntArray generateColorsOverride(WallpaperColors colors) {
return null;
}
+
+ /**
+ * Updates the base context to contain the colors override
+ */
+ public void applyColorsOverride(Context base, SparseIntArray override) { }
}
diff --git a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
index a916252..23ab0fb 100644
--- a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
+++ b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
@@ -82,8 +82,9 @@
@NonNull
@Override
- public LauncherAtom.ItemInfo buildProto(@Nullable CollectionInfo collectionInfo) {
- LauncherAtom.ItemInfo info = super.buildProto(collectionInfo);
+ public LauncherAtom.ItemInfo buildProto(
+ @Nullable CollectionInfo collectionInfo, Context context) {
+ LauncherAtom.ItemInfo info = super.buildProto(collectionInfo, context);
return info.toBuilder()
.addItemAttributes(LauncherAppWidgetInfo.getAttribute(sourceContainer))
.build();
diff --git a/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java b/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java
index 5ad9222..82a6883 100644
--- a/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java
@@ -18,7 +18,6 @@
import android.content.ComponentName;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.os.Parcel;
import android.os.Parcelable;
@@ -64,7 +63,7 @@
}
@Override
- public String getLabel(PackageManager packageManager) {
+ public CharSequence getLabel() {
return Utilities.trim(label);
}
diff --git a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
index faa5d12..0778172 100644
--- a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
+++ b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
@@ -33,7 +33,12 @@
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.R;
-import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.dagger.LauncherBaseAppComponent;
+import com.android.launcher3.util.DaggerSingletonObject;
+import com.android.launcher3.util.DaggerSingletonTracker;
+import com.android.launcher3.util.ExecutorUtil;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.PluginManagerWrapper;
import com.android.launcher3.util.SafeCloseable;
@@ -50,13 +55,16 @@
import java.util.function.Consumer;
import java.util.stream.Stream;
+import javax.inject.Inject;
+
/**
* CustomWidgetManager handles custom widgets implemented as a plugin.
*/
+@LauncherAppSingleton
public class CustomWidgetManager implements PluginListener<CustomWidgetPlugin>, SafeCloseable {
- public static final MainThreadInitializedObject<CustomWidgetManager> INSTANCE =
- new MainThreadInitializedObject<>(CustomWidgetManager::new);
+ public static final DaggerSingletonObject<CustomWidgetManager> INSTANCE =
+ new DaggerSingletonObject<>(LauncherBaseAppComponent::getCustomWidgetManager);
private static final String TAG = "CustomWidgetManager";
private static final String PLUGIN_PKG = "android";
@@ -66,34 +74,44 @@
private Consumer<PackageUserKey> mWidgetRefreshCallback;
private final @NonNull AppWidgetManager mAppWidgetManager;
- private CustomWidgetManager(Context context) {
- this(context, AppWidgetManager.getInstance(context));
+ @Inject
+ CustomWidgetManager(@ApplicationContext Context context, DaggerSingletonTracker tracker) {
+ this(context, AppWidgetManager.getInstance(context), tracker);
}
@VisibleForTesting
- CustomWidgetManager(Context context, @NonNull AppWidgetManager widgetManager) {
+ CustomWidgetManager(@ApplicationContext Context context,
+ @NonNull AppWidgetManager widgetManager,
+ DaggerSingletonTracker tracker) {
mContext = context;
mAppWidgetManager = widgetManager;
mPlugins = new HashMap<>();
mCustomWidgets = new ArrayList<>();
- PluginManagerWrapper.INSTANCE.get(context)
- .addPluginListener(this, CustomWidgetPlugin.class, true);
- if (enableSmartspaceAsAWidget()) {
- for (String s: context.getResources()
- .getStringArray(R.array.custom_widget_providers)) {
- try {
- Class<?> cls = Class.forName(s);
- CustomWidgetPlugin plugin = (CustomWidgetPlugin)
- cls.getDeclaredConstructor(Context.class).newInstance(context);
- onPluginConnected(plugin, context);
- } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
- | ClassCastException | NoSuchMethodException
- | InvocationTargetException e) {
- Log.e(TAG, "Exception found when trying to add custom widgets: " + e);
+
+ ExecutorUtil.executeSyncOnMainOrFail(() -> {
+ PluginManagerWrapper.INSTANCE.get(context)
+ .addPluginListener(this, CustomWidgetPlugin.class, true);
+
+ if (enableSmartspaceAsAWidget()) {
+ for (String s: context.getResources()
+ .getStringArray(R.array.custom_widget_providers)) {
+ try {
+ Class<?> cls = Class.forName(s);
+ CustomWidgetPlugin plugin = (CustomWidgetPlugin)
+ cls.getDeclaredConstructor(Context.class).newInstance(context);
+ onPluginConnected(plugin, context);
+ } catch (ClassNotFoundException | InstantiationException
+ | IllegalAccessException
+ | ClassCastException | NoSuchMethodException
+ | InvocationTargetException e) {
+ Log.e(TAG, "Exception found when trying to add custom widgets: " + e);
+ }
}
}
- }
+
+ tracker.addCloseable(this);
+ });
}
@Override
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/src_no_quickstep/com/android/launcher3/util/StateManagerProtoLogProxy.java b/src_no_quickstep/com/android/launcher3/util/StateManagerProtoLogProxy.java
new file mode 100644
index 0000000..34e15f7
--- /dev/null
+++ b/src_no_quickstep/com/android/launcher3/util/StateManagerProtoLogProxy.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.launcher3.util;
+
+/**
+ * Proxy class used for StateManager ProtoLog support.
+ */
+public class StateManagerProtoLogProxy {
+
+ public static void logGoToState(Object fromState, Object toState, String trace) { }
+
+ public static void logCreateAtomicAnimation(Object fromState, Object toState, String trace) { }
+
+ public static void logOnStateTransitionStart(Object state) { }
+
+ public static void logOnStateTransitionEnd(Object state) { }
+
+ public static void logCancelAnimation(boolean animationOngoing, String trace) { }
+}
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index 2553cf9..68e493d 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -420,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..825b52b 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,6 @@
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/AppWidgetsRestoredReceiverTest.kt b/tests/multivalentTests/src/com/android/launcher3/AppWidgetsRestoredReceiverTest.kt
index 21abab4..0e06051 100644
--- a/tests/multivalentTests/src/com/android/launcher3/AppWidgetsRestoredReceiverTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/AppWidgetsRestoredReceiverTest.kt
@@ -29,7 +29,7 @@
@Before
fun setup() {
- launcherPrefs = LauncherPrefs(DeviceHelpers.context)
+ launcherPrefs = LauncherPrefs.get(DeviceHelpers.context)
receiverUnderTest = AppWidgetsRestoredReceiver()
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefs.kt b/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefs.kt
new file mode 100644
index 0000000..946bbc5
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefs.kt
@@ -0,0 +1,77 @@
+/*
+ * 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 android.content.Context
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+
+/** Emulates Launcher preferences for a test environment. */
+class FakeLauncherPrefs(private val context: Context) : LauncherPrefs() {
+ private val prefsMap = mutableMapOf<String, Any>()
+ private val listeners = mutableSetOf<LauncherPrefChangeListener>()
+
+ @Suppress("UNCHECKED_CAST")
+ override fun <T> get(item: ContextualItem<T>): T {
+ return prefsMap.getOrDefault(item.sharedPrefKey, item.defaultValueFromContext(context)) as T
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ override fun <T> get(item: ConstantItem<T>): T {
+ return prefsMap.getOrDefault(item.sharedPrefKey, item.defaultValue) as T
+ }
+
+ override fun put(vararg itemsToValues: Pair<Item, Any>) = putSync(*itemsToValues)
+
+ override fun <T : Any> put(item: Item, value: T) = putSync(item to value)
+
+ override fun putSync(vararg itemsToValues: Pair<Item, Any>) {
+ itemsToValues
+ .map { (i, v) -> i.sharedPrefKey to v }
+ .forEach { (k, v) ->
+ prefsMap[k] = v
+ notifyChange(k)
+ }
+ }
+
+ override fun addListener(listener: LauncherPrefChangeListener, vararg items: Item) {
+ listeners.add(listener)
+ }
+
+ override fun removeListener(listener: LauncherPrefChangeListener, vararg items: Item) {
+ listeners.remove(listener)
+ }
+
+ override fun has(vararg items: Item) = items.all { it.sharedPrefKey in prefsMap }
+
+ override fun remove(vararg items: Item) = removeSync(*items)
+
+ override fun removeSync(vararg items: Item) {
+ items
+ .filter { it.sharedPrefKey in prefsMap }
+ .forEach {
+ prefsMap.remove(it.sharedPrefKey)
+ notifyChange(it.sharedPrefKey)
+ }
+ }
+
+ override fun close() = Unit
+
+ private fun notifyChange(key: String) {
+ // Mimics SharedPreferencesImpl#notifyListeners main thread dispatching.
+ MAIN_EXECUTOR.execute { listeners.forEach { it.onPrefChanged(key) } }
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefsTest.kt b/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefsTest.kt
new file mode 100644
index 0000000..2463c93
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefsTest.kt
@@ -0,0 +1,135 @@
+/*
+ * 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 androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private val TEST_CONSTANT_ITEM = LauncherPrefs.nonRestorableItem("TEST_BOOLEAN_ITEM", false)
+
+private val TEST_CONTEXTUAL_ITEM =
+ ContextualItem(
+ "TEST_CONTEXTUAL_ITEM",
+ true,
+ { false },
+ EncryptionType.ENCRYPTED,
+ Boolean::class.java,
+ )
+
+@RunWith(LauncherMultivalentJUnit::class)
+class FakeLauncherPrefsTest {
+ private val launcherPrefs = FakeLauncherPrefs(getApplicationContext())
+
+ @Test
+ fun testGet_constantItemNotInPrefs_returnsDefaultValue() {
+ assertThat(launcherPrefs.get(TEST_CONSTANT_ITEM)).isFalse()
+ }
+
+ @Test
+ fun testGet_constantItemInPrefs_returnsStoredValue() {
+ launcherPrefs.put(TEST_CONSTANT_ITEM, true)
+ assertThat(launcherPrefs.get(TEST_CONSTANT_ITEM)).isTrue()
+ }
+
+ @Test
+ fun testGet_contextualItemNotInPrefs_returnsDefaultValue() {
+ assertThat(launcherPrefs.get(TEST_CONTEXTUAL_ITEM)).isFalse()
+ }
+
+ @Test
+ fun testGet_contextualItemInPrefs_returnsStoredValue() {
+ launcherPrefs.put(TEST_CONTEXTUAL_ITEM, true)
+ assertThat(launcherPrefs.get(TEST_CONTEXTUAL_ITEM)).isTrue()
+ }
+
+ @Test
+ fun testPut_multipleItems_storesAll() {
+ launcherPrefs.put(TEST_CONSTANT_ITEM to true, TEST_CONTEXTUAL_ITEM to true)
+ assertThat(launcherPrefs.get(TEST_CONSTANT_ITEM)).isTrue()
+ assertThat(launcherPrefs.get(TEST_CONTEXTUAL_ITEM)).isTrue()
+ }
+
+ @Test
+ fun testHas_itemNotInPrefs_returnsFalse() {
+ assertThat(launcherPrefs.has(TEST_CONSTANT_ITEM)).isFalse()
+ }
+
+ @Test
+ fun testHas_itemInPrefs_returnsTrue() {
+ launcherPrefs.put(TEST_CONSTANT_ITEM, true)
+ assertThat(launcherPrefs.has(TEST_CONSTANT_ITEM)).isTrue()
+ }
+
+ @Test
+ fun testHas_twoItemsWithOneInPrefs_returnsFalse() {
+ launcherPrefs.put(TEST_CONSTANT_ITEM, true)
+ assertThat(launcherPrefs.has(TEST_CONSTANT_ITEM, TEST_CONTEXTUAL_ITEM)).isFalse()
+ }
+
+ @Test
+ fun testHas_twoItemsInPrefs_returnsTrue() {
+ launcherPrefs.put(TEST_CONSTANT_ITEM to true, TEST_CONTEXTUAL_ITEM to true)
+ assertThat(launcherPrefs.has(TEST_CONSTANT_ITEM, TEST_CONTEXTUAL_ITEM)).isTrue()
+ }
+
+ @Test
+ fun testRemove_itemInPrefs_removesItem() {
+ launcherPrefs.put(TEST_CONSTANT_ITEM, true)
+ launcherPrefs.remove(TEST_CONSTANT_ITEM)
+ assertThat(launcherPrefs.has(TEST_CONSTANT_ITEM)).isFalse()
+ }
+
+ @Test
+ fun testRemove_itemsInPrefs_removesItems() {
+ launcherPrefs.put(TEST_CONSTANT_ITEM to true, TEST_CONTEXTUAL_ITEM to true)
+ launcherPrefs.remove(TEST_CONSTANT_ITEM, TEST_CONTEXTUAL_ITEM)
+ assertThat(launcherPrefs.has(TEST_CONSTANT_ITEM, TEST_CONTEXTUAL_ITEM)).isFalse()
+ }
+
+ @Test
+ fun testAddListener_changeItemInPrefs_callsListener() {
+ var changedKey: String? = null
+ launcherPrefs.addListener({ changedKey = it }, TEST_CONSTANT_ITEM)
+ getInstrumentation().runOnMainSync { launcherPrefs.put(TEST_CONSTANT_ITEM, true) }
+ assertThat(changedKey).isEqualTo(TEST_CONSTANT_ITEM.sharedPrefKey)
+ }
+
+ @Test
+ fun testAddListener_removeItemFromPrefs_callsListener() {
+ var changedKey: String? = null
+ launcherPrefs.put(TEST_CONSTANT_ITEM, true)
+ launcherPrefs.addListener({ changedKey = it }, TEST_CONSTANT_ITEM)
+
+ getInstrumentation().runOnMainSync { launcherPrefs.remove(TEST_CONSTANT_ITEM) }
+ assertThat(changedKey).isEqualTo(TEST_CONSTANT_ITEM.sharedPrefKey)
+ }
+
+ @Test
+ fun testRemoveListener_changeItemInPrefs_doesNotCallListener() {
+ var changedKey: String? = null
+ val listener = LauncherPrefChangeListener { changedKey = it }
+ launcherPrefs.addListener(listener, TEST_CONSTANT_ITEM)
+
+ launcherPrefs.removeListener(listener)
+ getInstrumentation().runOnMainSync { launcherPrefs.put(TEST_CONSTANT_ITEM, true) }
+ assertThat(changedKey).isNull()
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/LauncherPrefsTest.kt b/tests/multivalentTests/src/com/android/launcher3/LauncherPrefsTest.kt
index b813095..4aeef2e 100644
--- a/tests/multivalentTests/src/com/android/launcher3/LauncherPrefsTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/LauncherPrefsTest.kt
@@ -17,7 +17,6 @@
import android.content.Context
import android.content.SharedPreferences
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
@@ -63,7 +62,7 @@
@Test
fun addListener_listeningForStringItemUpdates_isCorrectlyNotifiedOfUpdates() {
val latch = CountDownLatch(1)
- val listener = OnSharedPreferenceChangeListener { _, _ -> latch.countDown() }
+ val listener = LauncherPrefChangeListener { latch.countDown() }
with(launcherPrefs) {
putSync(TEST_STRING_ITEM.to(TEST_STRING_ITEM.defaultValue))
@@ -78,7 +77,7 @@
@Test
fun removeListener_previouslyListeningForStringItemUpdates_isNoLongerNotifiedOfUpdates() {
val latch = CountDownLatch(1)
- val listener = OnSharedPreferenceChangeListener { _, _ -> latch.countDown() }
+ val listener = LauncherPrefChangeListener { latch.countDown() }
with(launcherPrefs) {
addListener(listener, TEST_STRING_ITEM)
@@ -94,14 +93,14 @@
@Test
fun addListenerAndRemoveListener_forMultipleItems_bothWorkProperly() {
var latch = CountDownLatch(3)
- val listener = OnSharedPreferenceChangeListener { _, _ -> latch.countDown() }
+ val listener = LauncherPrefChangeListener { latch.countDown() }
with(launcherPrefs) {
addListener(listener, TEST_INT_ITEM, TEST_STRING_ITEM, TEST_BOOLEAN_ITEM)
putSync(
TEST_INT_ITEM.to(TEST_INT_ITEM.defaultValue + 123),
TEST_STRING_ITEM.to(TEST_STRING_ITEM.defaultValue + "abc"),
- TEST_BOOLEAN_ITEM.to(!TEST_BOOLEAN_ITEM.defaultValue)
+ TEST_BOOLEAN_ITEM.to(!TEST_BOOLEAN_ITEM.defaultValue),
)
assertThat(latch.await(WAIT_TIME_IN_SECONDS, TimeUnit.SECONDS)).isTrue()
@@ -110,7 +109,7 @@
putSync(
TEST_INT_ITEM.to(TEST_INT_ITEM.defaultValue),
TEST_STRING_ITEM.to(TEST_STRING_ITEM.defaultValue),
- TEST_BOOLEAN_ITEM.to(TEST_BOOLEAN_ITEM.defaultValue)
+ TEST_BOOLEAN_ITEM.to(TEST_BOOLEAN_ITEM.defaultValue),
)
remove(TEST_INT_ITEM, TEST_STRING_ITEM, TEST_BOOLEAN_ITEM)
@@ -150,7 +149,7 @@
putSync(
TEST_STRING_ITEM.to(TEST_STRING_ITEM.defaultValue),
TEST_INT_ITEM.to(TEST_INT_ITEM.defaultValue),
- TEST_BOOLEAN_ITEM.to(TEST_BOOLEAN_ITEM.defaultValue)
+ TEST_BOOLEAN_ITEM.to(TEST_BOOLEAN_ITEM.defaultValue),
)
assertThat(has(TEST_BOOLEAN_ITEM, TEST_INT_ITEM, TEST_STRING_ITEM)).isTrue()
remove(TEST_STRING_ITEM, TEST_INT_ITEM, TEST_BOOLEAN_ITEM)
@@ -191,7 +190,7 @@
LauncherPrefs.backedUpItem(
TEST_PREF_KEY,
TEST_DEFAULT_VALUE,
- EncryptionType.DEVICE_PROTECTED
+ EncryptionType.DEVICE_PROTECTED,
)
val bootAwarePrefs: SharedPreferences =
@@ -212,7 +211,7 @@
LauncherPrefs.backedUpItem(
TEST_PREF_KEY,
TEST_DEFAULT_VALUE,
- EncryptionType.DEVICE_PROTECTED
+ EncryptionType.DEVICE_PROTECTED,
)
val bootAwarePrefs: SharedPreferences =
diff --git a/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt b/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
index d236551..111ffaa 100644
--- a/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
@@ -29,7 +29,6 @@
import com.android.launcher3.icons.BaseIconFactory
import com.android.launcher3.icons.FastBitmapDrawable
import com.android.launcher3.icons.UserBadgeDrawable
-import com.android.launcher3.model.ModelTestRule
import com.android.launcher3.model.data.FolderInfo
import com.android.launcher3.model.data.ItemInfo
import com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED
@@ -45,7 +44,6 @@
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -54,8 +52,6 @@
@RunWith(AndroidJUnit4::class)
class PreviewItemManagerTest {
- @get:Rule val modelTestRule = ModelTestRule()
-
private lateinit var previewItemManager: PreviewItemManager
private lateinit var context: Context
private lateinit var folderItems: ArrayList<ItemInfo>
@@ -99,8 +95,8 @@
BaseIconFactory(
context,
context.resources.configuration.densityDpi,
- previewItemManager.mIconSize
- )
+ previewItemManager.mIconSize,
+ ),
)
// Set second icon to be non-themed.
@@ -111,8 +107,8 @@
BaseIconFactory(
context,
context.resources.configuration.densityDpi,
- previewItemManager.mIconSize
- )
+ previewItemManager.mIconSize,
+ ),
)
// Set third icon to be themed with badge.
@@ -123,8 +119,8 @@
BaseIconFactory(
context,
context.resources.configuration.densityDpi,
- previewItemManager.mIconSize
- )
+ previewItemManager.mIconSize,
+ ),
)
folderApps[2].bitmap = folderApps[2].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
@@ -137,8 +133,8 @@
BaseIconFactory(
context,
context.resources.configuration.densityDpi,
- previewItemManager.mIconSize
- )
+ previewItemManager.mIconSize,
+ ),
)
defaultThemedIcons = get(context).get(THEMED_ICONS)
diff --git a/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheTest.java b/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheTest.java
index 495d583..ce00b28 100644
--- a/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheTest.java
@@ -15,21 +15,40 @@
*/
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_ACTIVITY2;
+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 +56,29 @@
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.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,162 @@
assertEquals(((PackageItemInfo) item).packageName, otherPackage);
}
+ @Test
+ public void launcherActivityInfo_cached_in_memory() {
+ 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)));
+ }
+
+ @Test
+ public void item_kept_in_db_if_nothing_changes() {
+ ComponentName cn = new ComponentName(TEST_PACKAGE, TEST_ACTIVITY);
+ UserHandle user = myUserHandle();
+
+ LauncherActivityInfo lai = mContext.getSystemService(LauncherApps.class)
+ .resolveActivity(makeLaunchIntent(cn), user);
+ assertNotNull(lai);
+
+ // Since this is a new update, there should not be any update
+ Truth.assertThat(executeIconUpdate(lai, LauncherActivityCachingLogic.INSTANCE)).isEmpty();
+ assertTrue(mIconCache.isItemInDb(new ComponentKey(cn, user)));
+
+ // Another update should not cause any changes
+ Truth.assertThat(executeIconUpdate(lai, LauncherActivityCachingLogic.INSTANCE)).isEmpty();
+ assertTrue(mIconCache.isItemInDb(new ComponentKey(cn, user)));
+ }
+
+ @Test
+ public void item_updated_in_db_if_appInfo_changes() {
+ ComponentName cn = new ComponentName(TEST_PACKAGE, TEST_ACTIVITY);
+ UserHandle user = myUserHandle();
+
+ LauncherActivityInfo lai = mContext.getSystemService(LauncherApps.class)
+ .resolveActivity(makeLaunchIntent(cn), user);
+ assertNotNull(lai);
+
+ // Since this is a new update, there should not be any update
+ Truth.assertThat(executeIconUpdate(lai, LauncherActivityCachingLogic.INSTANCE)).isEmpty();
+ assertTrue(mIconCache.isItemInDb(new ComponentKey(cn, user)));
+
+ // Another update should trigger an update
+ lai.getApplicationInfo().sourceDir = "some-random-source-dir";
+ Truth.assertThat(executeIconUpdate(lai, LauncherActivityCachingLogic.INSTANCE))
+ .containsExactly(new PackageUserKey(TEST_PACKAGE, user));
+ assertTrue(mIconCache.isItemInDb(new ComponentKey(cn, user)));
+ }
+
+ @Test
+ public void item_removed_in_db_if_item_removed() {
+ ComponentName cn = new ComponentName(TEST_PACKAGE, TEST_ACTIVITY);
+ UserHandle user = myUserHandle();
+
+ LauncherActivityInfo lai = mContext.getSystemService(LauncherApps.class)
+ .resolveActivity(makeLaunchIntent(cn), user);
+ assertNotNull(lai);
+
+ // Since this is a new update, there should not be any update
+ Truth.assertThat(executeIconUpdate(lai, LauncherActivityCachingLogic.INSTANCE)).isEmpty();
+ assertTrue(mIconCache.isItemInDb(new ComponentKey(cn, user)));
+
+ // Another update should trigger an update
+ ComponentName cn2 = new ComponentName(TEST_PACKAGE, TEST_ACTIVITY2);
+ LauncherActivityInfo lai2 = mContext.getSystemService(LauncherApps.class)
+ .resolveActivity(makeLaunchIntent(cn2), user);
+
+ Truth.assertThat(executeIconUpdate(lai2, LauncherActivityCachingLogic.INSTANCE)).isEmpty();
+ assertFalse(mIconCache.isItemInDb(new ComponentKey(cn, user)));
+ assertTrue(mIconCache.isItemInDb(new ComponentKey(cn2, user)));
+ }
+
+ /**
+ * 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..ed9a080 100644
--- a/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheUpdateHandlerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheUpdateHandlerTest.kt
@@ -17,81 +17,133 @@
package com.android.launcher3.icons
import android.content.ComponentName
-import android.content.pm.PackageInfo
-import android.database.Cursor
-import android.os.UserHandle
+import android.content.pm.ApplicationInfo
+import android.database.MatrixCursor
+import android.os.Process.myUserHandle
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.launcher3.icons.cache.BaseIconCache
-import com.android.launcher3.icons.cache.CachingLogic
+import com.android.launcher3.icons.cache.BaseIconCache.IconDB
+import com.android.launcher3.icons.cache.CachedObject
+import com.android.launcher3.icons.cache.CachedObjectCachingLogic
import com.android.launcher3.icons.cache.IconCacheUpdateHandler
+import com.android.launcher3.util.RoboApiWrapper
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.FutureTask
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
class IconCacheUpdateHandlerTest {
- @Mock private lateinit var cursor: Cursor
- @Mock private lateinit var user: UserHandle
- @Mock private lateinit var cachingLogic: CachingLogic<String>
+ @Mock private lateinit var iconProvider: IconProvider
@Mock private lateinit var baseIconCache: BaseIconCache
- private var componentMap: HashMap<ComponentName, String> = hashMapOf()
- private var ignorePackages: Set<String> = setOf()
- private var packageInfoMap: HashMap<String, PackageInfo> = hashMapOf()
-
- private val dummyRowData =
- IconCacheRowData(
- "com.android.fake/.FakeActivity",
- System.currentTimeMillis(),
- 1,
- 1.0.toLong(),
- "stateOfConfusion"
- )
+ private var cursor: MatrixCursor? = null
+ private var cachingLogic = CachedObjectCachingLogic
@Before
fun setup() {
-
MockitoAnnotations.initMocks(this)
- // Load in a specific row to the database
- doReturn(0).`when`(cursor).getColumnIndex(BaseIconCache.IconDB.COLUMN_COMPONENT)
- doReturn(1).`when`(cursor).getColumnIndex(BaseIconCache.IconDB.COLUMN_LAST_UPDATED)
- doReturn(2).`when`(cursor).getColumnIndex(BaseIconCache.IconDB.COLUMN_VERSION)
- doReturn(3).`when`(cursor).getColumnIndex(BaseIconCache.IconDB.COLUMN_ROWID)
- doReturn(4).`when`(cursor).getColumnIndex(BaseIconCache.IconDB.COLUMN_SYSTEM_STATE)
- doReturn(dummyRowData.component).`when`(cursor).getString(0)
- doReturn(dummyRowData.lastUpdated).`when`(cursor).getLong(1)
- doReturn(dummyRowData.version).`when`(cursor).getInt(2)
- doReturn(dummyRowData.row).`when`(cursor).getLong(3)
- doReturn(dummyRowData.systemState).`when`(cursor).getString(4)
+ doReturn(iconProvider).whenever(baseIconCache).iconProvider
+ }
+
+ @After
+ fun tearDown() {
+ cursor?.close()
}
@Test
fun `IconCacheUpdateHandler returns null if the component name is malformed`() {
- val updateHandlerUnderTest = IconCacheUpdateHandler(packageInfoMap, baseIconCache)
+ val updateHandlerUnderTest = IconCacheUpdateHandler(baseIconCache)
+ val cn = ComponentName.unflattenFromString("com.android.fake/.FakeActivity")!!
val result =
updateHandlerUnderTest.updateOrDeleteIcon(
- cursor,
- componentMap,
- ignorePackages,
- user,
- cachingLogic
+ createCursor(1, cn.flattenToString() + "#", "freshId-old"),
+ hashMapOf(cn to TestCachedObject(cn, "freshId")),
+ setOf(),
+ myUserHandle(),
+ cachingLogic,
)
+ assertThat(result).isNull()
+ }
- assert(result == null)
+ @Test
+ fun `IconCacheUpdateHandler returns null if the freshId match`() {
+ val updateHandlerUnderTest = IconCacheUpdateHandler(baseIconCache)
+ val cn = ComponentName.unflattenFromString("com.android.fake/.FakeActivity")!!
+
+ val result =
+ updateHandlerUnderTest.updateOrDeleteIcon(
+ createCursor(1, cn.flattenToString(), "freshId"),
+ hashMapOf(cn to TestCachedObject(cn, "freshId")),
+ setOf(),
+ myUserHandle(),
+ cachingLogic,
+ )
+ assertThat(result).isNull()
+ }
+
+ @Test
+ fun `IconCacheUpdateHandler returns non-null if the freshId do not match`() {
+ val updateHandlerUnderTest = IconCacheUpdateHandler(baseIconCache)
+ val cn = ComponentName.unflattenFromString("com.android.fake/.FakeActivity")!!
+ val testObj = TestCachedObject(cn, "freshId")
+
+ val result =
+ updateHandlerUnderTest.updateOrDeleteIcon(
+ createCursor(1, cn.flattenToString(), "freshId-old"),
+ hashMapOf(cn to testObj),
+ setOf(),
+ myUserHandle(),
+ cachingLogic,
+ )
+ assertThat(result).isEqualTo(testObj)
+ }
+
+ private fun createCursor(row: Long, component: String, appState: String) =
+ MatrixCursor(
+ arrayOf(IconDB.COLUMN_ROWID, IconDB.COLUMN_COMPONENT, IconDB.COLUMN_FRESHNESS_ID)
+ )
+ .apply { addRow(arrayOf(row, component, appState)) }
+ .apply {
+ cursor = this
+ moveToNext()
+ }
+}
+
+/** 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
-)
+class TestCachedObject(val cn: ComponentName, val freshnessId: String) : CachedObject {
+
+ override fun getComponent() = cn
+
+ override fun getUser() = myUserHandle()
+
+ override fun getLabel(): CharSequence? = null
+
+ override fun getApplicationInfo(): ApplicationInfo? = null
+
+ override fun getFreshnessIdentifier(iconProvider: IconProvider): String? = freshnessId
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
index 43dc36b..ce04682 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
@@ -27,7 +27,6 @@
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.times
@@ -44,8 +43,6 @@
@RunWith(AndroidJUnit4::class)
class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() {
- @get:Rule val modelTestRule = ModelTestRule()
-
private lateinit var mDataModelCallbacks: MyCallbacks
private val mWorkspaceItemSpaceFinder: WorkspaceItemSpaceFinder = mock()
@@ -121,18 +118,8 @@
@Test
fun givenMultipleItems_whenExecuteTask_thenAddThem() {
val itemsToAdd =
- arrayOf(
- getNewItem(),
- getExistingItem(),
- getNewItem(),
- getNewItem(),
- getExistingItem(),
- )
- givenNewItemSpaces(
- NewItemSpace(1, 3, 3),
- NewItemSpace(2, 0, 0),
- NewItemSpace(2, 0, 1),
- )
+ arrayOf(getNewItem(), getExistingItem(), getNewItem(), getNewItem(), getExistingItem())
+ givenNewItemSpaces(NewItemSpace(1, 3, 3), NewItemSpace(2, 0, 0), NewItemSpace(2, 0, 1))
val nonEmptyScreenIds = listOf(0, 1)
val addedItems = testAddItems(nonEmptyScreenIds, *itemsToAdd)
@@ -173,7 +160,7 @@
eq(IntArray.wrap(*nonEmptyScreenIds.toIntArray())),
eq(IntArray()),
eq(1),
- eq(1)
+ eq(1),
)
}
@@ -183,7 +170,7 @@
*/
private fun testAddItems(
nonEmptyScreenIds: List<Int>,
- vararg itemsToAdd: WorkspaceItemInfo
+ vararg itemsToAdd: WorkspaceItemInfo,
): List<AddedItem> {
setupWorkspaces(nonEmptyScreenIds)
val task = newTask(*itemsToAdd)
@@ -220,7 +207,7 @@
override fun bindAppsAdded(
newScreens: IntArray?,
addNotAnimated: ArrayList<ItemInfo>,
- addAnimated: ArrayList<ItemInfo>
+ addAnimated: ArrayList<ItemInfo>,
) {
addedItems.addAll(addAnimated.map { AddedItem(it, true) })
addedItems.addAll(addNotAnimated.map { AddedItem(it, false) })
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/AsyncBindingTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/AsyncBindingTest.kt
index dce75b9..ba59253 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/AsyncBindingTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/AsyncBindingTest.kt
@@ -64,8 +64,6 @@
@get:Rule val setFlagsRule = SetFlagsRule()
- @get:Rule val modelTestRule = ModelTestRule()
-
@Spy private var callbacks = MyCallbacks()
@Mock private lateinit var itemInflater: ItemInflater<*>
@@ -138,7 +136,7 @@
@Test
fun test_bind_sync_partially_inflates_on_background() {
modelHelper.loadModelSync()
- assertTrue(modelHelper.model.isModelLoaded)
+ assertTrue(modelHelper.model.isModelLoaded())
callbacks.inflater = itemInflater
val firstPageBindIds = IntSet()
@@ -203,7 +201,7 @@
pendingTasks: RunnableList,
onCompleteSignal: RunnableList,
workspaceItemCount: Int,
- isBindSync: Boolean
+ isBindSync: Boolean,
) {
this.pendingTasks = pendingTasks
this.onCompleteSignal = onCompleteSignal
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java b/tests/multivalentTests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
index 535080a..600af42 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
@@ -64,11 +64,7 @@
@RunWith(AndroidJUnit4.class)
public class CacheDataUpdatedTaskTest {
- @Rule(order = 0)
- public TestRule testStabilityRule = new TestStabilityRule();
-
- @Rule(order = 1)
- public ModelTestRule mModelTestRule = new ModelTestRule();
+ @Rule public TestRule testStabilityRule = new TestStabilityRule();
private static final String PENDING_APP_1 = TEST_PACKAGE + ".pending1";
private static final String PENDING_APP_2 = TEST_PACKAGE + ".pending2";
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java b/tests/multivalentTests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
index e14e145..1e2431f 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
@@ -39,7 +39,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -50,8 +49,6 @@
@RunWith(AndroidJUnit4.class)
public class DefaultLayoutProviderTest {
- @Rule public ModelTestRule rule = new ModelTestRule();
-
private LauncherModelHelper mModelHelper;
private LauncherModelHelper.SandboxModelContext mTargetContext;
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt
index d2d9512..9cc380e 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt
@@ -34,7 +34,6 @@
import com.android.launcher3.util.PackageManagerHelper
import com.android.launcher3.util.PackageUserKey
import junit.framework.Assert.assertEquals
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
@@ -46,8 +45,6 @@
@RunWith(AndroidJUnit4::class)
class FirstScreenBroadcastHelperTest {
- @get:Rule val modelTestRule = ModelTestRule()
-
private val context = spy(InstrumentationRegistry.getInstrumentation().targetContext)
private val mockPmHelper = mock<PackageManagerHelper>()
private val expectedAppPackage = "appPackageExpected"
@@ -70,7 +67,7 @@
container = CONTAINER_HOTSEAT
intent = expectedIntent
},
- LauncherAppWidgetInfo().apply { providerName = expectedComponentName }
+ LauncherAppWidgetInfo().apply { providerName = expectedComponentName },
)
@Test
@@ -89,7 +86,7 @@
val sessionInfoMap: HashMap<PackageUserKey, SessionInfo> =
hashMapOf(
PackageUserKey(unexpectedAppPackage, UserHandle(0)) to sessionInfoExpected,
- PackageUserKey(expectedAppPackage, UserHandle(0)) to sessionInfoUnexpected
+ PackageUserKey(expectedAppPackage, UserHandle(0)) to sessionInfoUnexpected,
)
// When
@@ -98,7 +95,7 @@
packageManagerHelper = mockPmHelper,
firstScreenItems = firstScreenItems,
userKeyToSessionMap = sessionInfoMap,
- allWidgets = listOf()
+ allWidgets = listOf(),
)
// Then
@@ -108,7 +105,7 @@
installerPackage = expectedInstallerPackage,
pendingWorkspaceItems = mutableSetOf(expectedAppPackage),
pendingHotseatItems = mutableSetOf(expectedAppPackage),
- pendingWidgetItems = mutableSetOf(expectedAppPackage)
+ pendingWidgetItems = mutableSetOf(expectedAppPackage),
)
)
@@ -133,7 +130,7 @@
providerName = expectedComponentName
screenId = 0
}
- )
+ ),
)
// Then
@@ -143,7 +140,7 @@
installerPackage = expectedInstallerPackage,
installedHotseatItems = mutableSetOf(expectedAppPackage),
installedWorkspaceItems = mutableSetOf(expectedAppPackage),
- firstScreenInstalledWidgets = mutableSetOf(expectedAppPackage)
+ firstScreenInstalledWidgets = mutableSetOf(expectedAppPackage),
)
)
assertEquals(expectedResult, actualResult)
@@ -178,8 +175,8 @@
LauncherAppWidgetInfo().apply {
providerName = unexpectedComponentName
screenId = 0
- }
- )
+ },
+ ),
)
// Then
@@ -190,7 +187,7 @@
installedHotseatItems = mutableSetOf(),
installedWorkspaceItems = mutableSetOf(),
firstScreenInstalledWidgets = mutableSetOf(expectedAppPackage),
- secondaryScreenInstalledWidgets = mutableSetOf(expectedAppPackage2)
+ secondaryScreenInstalledWidgets = mutableSetOf(expectedAppPackage2),
)
)
assertEquals(expectedResult, actualResult)
@@ -224,7 +221,7 @@
packageManagerHelper = mockPmHelper,
firstScreenItems = firstScreenItems,
userKeyToSessionMap = sessionInfoMap,
- allWidgets = listOf()
+ allWidgets = listOf(),
)
// Then
@@ -232,7 +229,7 @@
listOf(
FirstScreenBroadcastModel(
installerPackage = expectedInstallerPackage,
- pendingCollectionItems = mutableSetOf(expectedAppPackage)
+ pendingCollectionItems = mutableSetOf(expectedAppPackage),
)
)
assertEquals(expectedResult, actualResult)
@@ -259,7 +256,7 @@
firstScreenInstalledWidgets =
mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } },
secondaryScreenInstalledWidgets =
- mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } }
+ mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } },
)
// When
@@ -334,7 +331,7 @@
installedWorkspaceItems = mutableSetOf("installedWorkspaceItems"),
installedHotseatItems = mutableSetOf("installedHotseatItems"),
firstScreenInstalledWidgets = mutableSetOf("firstScreenInstalledWidgetItems"),
- secondaryScreenInstalledWidgets = mutableSetOf("secondaryInstalledWidgetItems")
+ secondaryScreenInstalledWidgets = mutableSetOf("secondaryInstalledWidgetItems"),
)
)
val expectedPendingIntent =
@@ -342,7 +339,7 @@
context,
0 /* requestCode */,
Intent(),
- PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
+ PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE,
)
// When
@@ -354,40 +351,40 @@
assertEquals(
"com.android.launcher3.action.FIRST_SCREEN_ACTIVE_INSTALLS",
- argumentCaptor.value.action
+ argumentCaptor.value.action,
)
assertEquals(expectedInstallerPackage, argumentCaptor.value.`package`)
assertEquals(
expectedPendingIntent,
- argumentCaptor.value.getParcelableExtra("verificationToken")
+ argumentCaptor.value.getParcelableExtra("verificationToken"),
)
assertEquals(
arrayListOf("pendingCollectionItem"),
- argumentCaptor.value.getStringArrayListExtra("folderItem")
+ argumentCaptor.value.getStringArrayListExtra("folderItem"),
)
assertEquals(
arrayListOf("pendingWorkspaceItem"),
- argumentCaptor.value.getStringArrayListExtra("workspaceItem")
+ argumentCaptor.value.getStringArrayListExtra("workspaceItem"),
)
assertEquals(
arrayListOf("pendingHotseatItems"),
- argumentCaptor.value.getStringArrayListExtra("hotseatItem")
+ argumentCaptor.value.getStringArrayListExtra("hotseatItem"),
)
assertEquals(
arrayListOf("pendingWidgetItems"),
- argumentCaptor.value.getStringArrayListExtra("widgetItem")
+ argumentCaptor.value.getStringArrayListExtra("widgetItem"),
)
assertEquals(
arrayListOf("installedWorkspaceItems"),
- argumentCaptor.value.getStringArrayListExtra("workspaceInstalledItems")
+ argumentCaptor.value.getStringArrayListExtra("workspaceInstalledItems"),
)
assertEquals(
arrayListOf("installedHotseatItems"),
- argumentCaptor.value.getStringArrayListExtra("hotseatInstalledItems")
+ argumentCaptor.value.getStringArrayListExtra("hotseatInstalledItems"),
)
assertEquals(
arrayListOf("firstScreenInstalledWidgetItems", "secondaryInstalledWidgetItems"),
- argumentCaptor.value.getStringArrayListExtra("widgetInstalledItems")
+ argumentCaptor.value.getStringArrayListExtra("widgetInstalledItems"),
)
}
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt
index 371bac2..e8f778f 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt
@@ -19,19 +19,17 @@
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
import org.junit.Test
import org.junit.runner.RunWith
@@ -40,8 +38,6 @@
@RunWith(AndroidJUnit4::class)
class FolderIconLoadTest {
- @get:Rule(order = 0) val modelTestRule = ModelTestRule()
-
private lateinit var modelHelper: LauncherModelHelper
private val uniqueActivities =
@@ -147,14 +143,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()
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/multivalentTests/src/com/android/launcher3/model/LoaderCursorTest.java
index ac911b3..b4945d7 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/LoaderCursorTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -67,7 +67,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -78,8 +77,6 @@
@RunWith(AndroidJUnit4.class)
public class LoaderCursorTest {
- @Rule public ModelTestRule rule = new ModelTestRule();
-
private LauncherModelHelper mModelHelper;
private LauncherAppState mApp;
private PackageManagerHelper mPmHelper;
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/ModelTestRule.kt b/tests/multivalentTests/src/com/android/launcher3/model/ModelTestRule.kt
deleted file mode 100644
index ad2c2a4..0000000
--- a/tests/multivalentTests/src/com/android/launcher3/model/ModelTestRule.kt
+++ /dev/null
@@ -1,27 +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.model
-
-import com.android.launcher3.util.RoboApiWrapper
-import org.junit.rules.TestWatcher
-import org.junit.runner.Description
-
-class ModelTestRule : TestWatcher() {
- override fun starting(description: Description?) {
- RoboApiWrapper.initialize()
- }
-}
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java b/tests/multivalentTests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
index a0d9da9..0f1fc00 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
@@ -37,7 +37,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -48,8 +47,6 @@
@RunWith(AndroidJUnit4.class)
public class PackageInstallStateChangedTaskTest {
- @Rule public ModelTestRule mModelTestRule = new ModelTestRule();
-
private static final String PENDING_APP_1 = TEST_PACKAGE + ".pending1";
private static final String PENDING_APP_2 = TEST_PACKAGE + ".pending2";
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
index 7529ba9..ed8b397 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
@@ -57,7 +58,6 @@
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
@@ -76,8 +76,6 @@
@RunWith(AndroidJUnit4::class)
class WorkspaceItemProcessorTest {
- @get:Rule val modelTestRule = ModelTestRule()
-
@Mock private lateinit var mockIconRequestInfo: IconRequestInfo<WorkspaceItemInfo>
@Mock private lateinit var mockWorkspaceInfo: WorkspaceItemInfo
@Mock private lateinit var mockBgDataModel: BgDataModel
@@ -97,7 +95,7 @@
private var mUnlockedUsersArray: LongSparseArray<Boolean> = LongSparseArray()
private var mKeyToPinnedShortcutsMap: MutableMap<ShortcutKey, ShortcutInfo> = mutableMapOf()
private var mInstallingPkgs: HashMap<PackageUserKey, PackageInstaller.SessionInfo> = hashMapOf()
- private var mAllDeepShortcuts: MutableList<ShortcutInfo> = mutableListOf()
+ private var mAllDeepShortcuts: MutableList<CacheableShortcutInfo> = mutableListOf()
private var mWidgetProvidersMap: MutableMap<ComponentKey, AppWidgetProviderInfo?> =
mutableMapOf()
private var mPendingPackages: MutableSet<PackageUserKey> = mutableSetOf()
@@ -194,7 +192,7 @@
pendingPackages: MutableSet<PackageUserKey> = mPendingPackages,
unlockedUsers: LongSparseArray<Boolean> = mUnlockedUsersArray,
installingPkgs: HashMap<PackageUserKey, PackageInstaller.SessionInfo> = mInstallingPkgs,
- allDeepShortcuts: MutableList<ShortcutInfo> = mAllDeepShortcuts,
+ allDeepShortcuts: MutableList<CacheableShortcutInfo> = mAllDeepShortcuts,
) =
WorkspaceItemProcessor(
c = cursor,
@@ -387,7 +385,8 @@
.that(mockCursor.restoreFlag)
.isEqualTo(0)
assertThat(mIconRequestInfos).isEmpty()
- assertThat(mAllDeepShortcuts).containsExactly(expectedShortcutInfo)
+ assertThat(mAllDeepShortcuts.size).isEqualTo(1)
+ assertThat(mAllDeepShortcuts[0].shortcutInfo).isEqualTo(expectedShortcutInfo)
verify(mockCursor).markRestored()
verify(mockCursor).checkAndAddItem(any(), any(), anyOrNull())
}
@@ -452,7 +451,8 @@
.that(mockCursor.restoreFlag)
.isEqualTo(0)
assertThat(mIconRequestInfos).isEmpty()
- assertThat(mAllDeepShortcuts).containsExactly(expectedShortcutInfo)
+ assertThat(mAllDeepShortcuts.size).isEqualTo(1)
+ assertThat(mAllDeepShortcuts[0].shortcutInfo).isEqualTo(expectedShortcutInfo)
verify(mockCursor).markRestored()
verify(mockCursor).checkAndAddItem(any(), any(), anyOrNull())
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemSpaceFinderTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemSpaceFinderTest.kt
index ae8e966..dd03eee 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemSpaceFinderTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemSpaceFinderTest.kt
@@ -21,7 +21,6 @@
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -30,8 +29,6 @@
@RunWith(AndroidJUnit4::class)
class WorkspaceItemSpaceFinderTest : AbstractWorkspaceModelTest() {
- @get:Rule val modelTestRule = ModelTestRule()
-
private val mItemSpaceFinder = WorkspaceItemSpaceFinder()
@Before
@@ -52,7 +49,7 @@
mExistingScreens,
mNewScreens,
spanX,
- spanY
+ spanY,
)
.let { NewItemSpace.fromIntArray(it) }
@@ -62,7 +59,7 @@
newItemSpace.cellX,
newItemSpace.cellY,
spanX,
- spanY
+ spanY,
)
)
.isTrue()
@@ -171,7 +168,7 @@
screen0 = listOf(Rect(2, 0, 5, 2)),
screen1 = fullScreenSpaces, // full screens are skipped
screen2 = fullScreenSpaces, // full screens are skipped
- screen3 = emptyScreenSpaces
+ screen3 = emptyScreenSpaces,
)
val spaceFound = findSpace(3, 1)
diff --git a/tests/multivalentTests/src/com/android/launcher3/pm/InstallSessionTrackerTest.kt b/tests/multivalentTests/src/com/android/launcher3/pm/InstallSessionTrackerTest.kt
index d860710..15a9964 100644
--- a/tests/multivalentTests/src/com/android/launcher3/pm/InstallSessionTrackerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/pm/InstallSessionTrackerTest.kt
@@ -26,7 +26,6 @@
import androidx.test.filters.SdkSuppress
import androidx.test.filters.SmallTest
import com.android.launcher3.Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING
-import com.android.launcher3.model.ModelTestRule
import com.android.launcher3.util.Executors.MODEL_EXECUTOR
import com.android.launcher3.util.LauncherModelHelper
import com.android.launcher3.util.PackageUserKey
@@ -45,9 +44,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class InstallSessionTrackerTest {
- @get:Rule(order = 0) val setFlagsRule = SetFlagsRule()
-
- @get:Rule(order = 1) val modelTestRule = ModelTestRule()
+ @get:Rule val setFlagsRule = SetFlagsRule()
private val mockInstallSessionHelper: InstallSessionHelper = mock()
private val mockCallback: InstallSessionTracker.Callback = mock()
@@ -67,7 +64,7 @@
mockInstallSessionHelper,
mockCallback,
mockPackageInstaller,
- launcherApps
+ launcherApps,
)
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/pm/UserCacheTest.kt b/tests/multivalentTests/src/com/android/launcher3/pm/UserCacheTest.kt
index 482dced..5f08c31 100644
--- a/tests/multivalentTests/src/com/android/launcher3/pm/UserCacheTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/pm/UserCacheTest.kt
@@ -20,7 +20,6 @@
import android.os.UserHandle
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
-import com.android.launcher3.model.ModelTestRule
import com.android.launcher3.util.Executors.MODEL_EXECUTOR
import com.android.launcher3.util.LauncherModelHelper
import com.android.launcher3.util.TestUtil
@@ -28,15 +27,12 @@
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class UserCacheTest {
- @get:Rule val modelTestRule = ModelTestRule()
-
private val launcherModelHelper = LauncherModelHelper()
private val sandboxContext = launcherModelHelper.sandboxContext
private lateinit var userCache: UserCache
diff --git a/tests/multivalentTests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/tests/multivalentTests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
index b3675a6..d0c168a 100644
--- a/tests/multivalentTests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
@@ -101,7 +101,7 @@
mMockController = Mockito.mock(ModelDbController.class);
mMockDb = mock(SQLiteDatabase.class);
mMockCursor = mock(Cursor.class);
- mPrefs = new LauncherPrefs(mContext);
+ mPrefs = LauncherPrefs.get(mContext);
mMockRestoreEventLogger = mock(LauncherRestoreEventLogger.class);
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java b/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
index 748d376..09b9a3b 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -31,6 +31,7 @@
import android.content.ContentProvider;
import android.content.ContentResolver;
+import android.content.Context;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
@@ -250,15 +251,16 @@
private final File mDbDir;
public SandboxModelContext() {
- super(ApplicationProvider.getApplicationContext());
+ this(ApplicationProvider.getApplicationContext());
+ }
+
+ public SandboxModelContext(Context context) {
+ super(context);
// System settings cache content provider. Ensure that they are statically initialized
- Settings.Secure.getString(
- ApplicationProvider.getApplicationContext().getContentResolver(), "test");
- Settings.System.getString(
- ApplicationProvider.getApplicationContext().getContentResolver(), "test");
- Settings.Global.getString(
- ApplicationProvider.getApplicationContext().getContentResolver(), "test");
+ Settings.Secure.getString(context.getContentResolver(), "test");
+ Settings.System.getString(context.getContentResolver(), "test");
+ Settings.Global.getString(context.getContentResolver(), "test");
mPm = spy(getBaseContext().getPackageManager());
mDbDir = new File(getCacheDir(), UUID.randomUUID().toString());
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt b/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt
new file mode 100644
index 0000000..efe7637
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt
@@ -0,0 +1,164 @@
+/*
+ * 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.ContextParams
+import android.content.ContextWrapper
+import android.content.pm.ApplicationInfo
+import android.content.res.Configuration
+import android.os.Bundle
+import android.os.IBinder
+import android.os.UserHandle
+import android.view.Display
+import androidx.test.core.app.ApplicationProvider
+import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
+import com.android.launcher3.util.MainThreadInitializedObject.ObjectSandbox
+import org.junit.Rule
+import org.junit.rules.ExternalResource
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Sandbox application where created [Context] instances are still sandboxed within it.
+ *
+ * Tests can declare this application as a [Rule], so that it is set up and destroyed automatically.
+ * Alternatively, they can call [init] and [onDestroy] directly. Either way, these need to be called
+ * for it to work and avoid leaks from created singletons.
+ *
+ * The create [Context] APIs construct a `ContextImpl`, which resets the application to the true
+ * application, thus leaving the sandbox. This implementation wraps the created contexts to
+ * propagate this application (see [SandboxApplicationWrapper]).
+ */
+class SandboxApplication private constructor(private val base: SandboxApplicationWrapper) :
+ SandboxModelContext(base), TestRule {
+
+ constructor(
+ base: Context = ApplicationProvider.getApplicationContext()
+ ) : this(SandboxApplicationWrapper(base))
+
+ /**
+ * Initializes the sandbox application propagation logic.
+ *
+ * This function either needs to be called manually or automatically through using [Rule].
+ */
+ fun init() {
+ base.app = this@SandboxApplication
+ }
+
+ /** Returns `this` if [init] was called, otherwise crashes the test. */
+ override fun getApplicationContext(): Context = base.applicationContext
+
+ override fun shouldCleanUpOnDestroy(): Boolean {
+ // Defer to the true application to decide whether to clean up. For instance, we do not want
+ // to cleanup under Robolectric.
+ val app = ApplicationProvider.getApplicationContext<Context>()
+ return if (app is ObjectSandbox) app.shouldCleanUpOnDestroy() else true
+ }
+
+ override fun apply(statement: Statement, description: Description): Statement {
+ return object : ExternalResource() {
+ override fun before() = init()
+
+ override fun after() = onDestroy()
+ }
+ .apply(statement, description)
+ }
+}
+
+private class SandboxApplicationWrapper(base: Context, var app: Context? = null) :
+ ContextWrapper(base) {
+
+ override fun getApplicationContext(): Context {
+ return checkNotNull(app) { "SandboxApplication accessed before #init() was called." }
+ }
+
+ override fun createPackageContext(packageName: String?, flags: Int): Context {
+ return SandboxApplicationWrapper(super.createPackageContext(packageName, flags), app)
+ }
+
+ override fun createPackageContextAsUser(
+ packageName: String,
+ flags: Int,
+ user: UserHandle,
+ ): Context {
+ return SandboxApplicationWrapper(
+ super.createPackageContextAsUser(packageName, flags, user),
+ app,
+ )
+ }
+
+ override fun createContextAsUser(user: UserHandle, flags: Int): Context {
+ return SandboxApplicationWrapper(super.createContextAsUser(user, flags), app)
+ }
+
+ override fun createApplicationContext(application: ApplicationInfo?, flags: Int): Context {
+ return SandboxApplicationWrapper(super.createApplicationContext(application, flags), app)
+ }
+
+ override fun createContextForSdkInSandbox(sdkInfo: ApplicationInfo, flags: Int): Context {
+ return SandboxApplicationWrapper(super.createContextForSdkInSandbox(sdkInfo, flags), app)
+ }
+
+ override fun createContextForSplit(splitName: String?): Context {
+ return SandboxApplicationWrapper(super.createContextForSplit(splitName), app)
+ }
+
+ override fun createConfigurationContext(overrideConfiguration: Configuration): Context {
+ return SandboxApplicationWrapper(
+ super.createConfigurationContext(overrideConfiguration),
+ app,
+ )
+ }
+
+ override fun createDisplayContext(display: Display): Context {
+ return SandboxApplicationWrapper(super.createDisplayContext(display), app)
+ }
+
+ override fun createDeviceContext(deviceId: Int): Context {
+ return SandboxApplicationWrapper(super.createDeviceContext(deviceId), app)
+ }
+
+ override fun createWindowContext(type: Int, options: Bundle?): Context {
+ return SandboxApplicationWrapper(super.createWindowContext(type, options), app)
+ }
+
+ override fun createWindowContext(display: Display, type: Int, options: Bundle?): Context {
+ return SandboxApplicationWrapper(super.createWindowContext(display, type, options), app)
+ }
+
+ override fun createContext(contextParams: ContextParams): Context {
+ return SandboxApplicationWrapper(super.createContext(contextParams), app)
+ }
+
+ override fun createAttributionContext(attributionTag: String?): Context {
+ return SandboxApplicationWrapper(super.createAttributionContext(attributionTag), app)
+ }
+
+ override fun createCredentialProtectedStorageContext(): Context {
+ return SandboxApplicationWrapper(super.createCredentialProtectedStorageContext(), app)
+ }
+
+ override fun createDeviceProtectedStorageContext(): Context {
+ return SandboxApplicationWrapper(super.createDeviceProtectedStorageContext(), app)
+ }
+
+ override fun createTokenContext(token: IBinder, display: Display): Context {
+ return SandboxApplicationWrapper(super.createTokenContext(token, display), app)
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplicationTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplicationTest.kt
new file mode 100644
index 0000000..d87a406
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplicationTest.kt
@@ -0,0 +1,78 @@
+/*
+ * 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.hardware.display.DisplayManager
+import android.view.Display
+import android.view.Display.DEFAULT_DISPLAY
+import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(LauncherMultivalentJUnit::class)
+class SandboxApplicationTest {
+ @get:Rule val app = SandboxApplication()
+
+ private val display: Display
+ get() {
+ return checkNotNull(app.getSystemService(DisplayManager::class.java))
+ .getDisplay(DEFAULT_DISPLAY)
+ }
+
+ @Test
+ fun testCreateDisplayContext_isSandboxed() {
+ val displayContext = app.createDisplayContext(display)
+ assertThat(displayContext.applicationContext).isEqualTo(app)
+ }
+
+ @Test
+ fun testCreateWindowContext_fromSandboxedDisplayContext_isSandboxed() {
+ val displayContext = app.createDisplayContext(display)
+ val nestedContext = displayContext.createWindowContext(TYPE_APPLICATION_OVERLAY, null)
+ assertThat(nestedContext.applicationContext).isEqualTo(app)
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun testGetApplicationContext_beforeManualInit_throwsException() {
+ val manualApp = SandboxApplication()
+ assertThat(manualApp.applicationContext).isEqualTo(manualApp)
+ }
+
+ @Test
+ fun testGetApplicationContext_afterManualInit_isApplication() {
+ SandboxApplication().run {
+ init()
+ assertThat(applicationContext).isEqualTo(this)
+ onDestroy()
+ }
+ }
+
+ @Test
+ fun testGetObject_objectCreatesDisplayContext_isSandboxed() {
+ class TestSingleton(context: Context) : SafeCloseable {
+ override fun close() = Unit
+
+ val displayContext = context.createDisplayContext(display)
+ }
+
+ val displayContext = MainThreadInitializedObject { TestSingleton(it) }[app].displayContext
+ assertThat(displayContext.applicationContext).isEqualTo(app)
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/ScreenOnTrackerTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/ScreenOnTrackerTest.kt
index 430aad2..45cc19c 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/ScreenOnTrackerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/ScreenOnTrackerTest.kt
@@ -39,13 +39,14 @@
@Mock private lateinit var receiver: SimpleBroadcastReceiver
@Mock private lateinit var context: Context
@Mock private lateinit var listener: ScreenOnTracker.ScreenOnListener
+ @Mock private lateinit var tracker: DaggerSingletonTracker
private lateinit var underTest: ScreenOnTracker
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- underTest = ScreenOnTracker(context, receiver)
+ underTest = ScreenOnTracker(context, receiver, tracker)
}
@Test
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/TestUtil.java b/tests/multivalentTests/src/com/android/launcher3/util/TestUtil.java
index 64035da..ce682f1 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/TestUtil.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/TestUtil.java
@@ -15,14 +15,12 @@
*/
package com.android.launcher3.util;
-import static android.util.Base64.NO_PADDING;
-import static android.util.Base64.NO_WRAP;
-
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_KEY;
import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_LABEL;
import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_TAG;
+import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_PROVIDER_KEY;
+import static com.android.launcher3.LauncherSettings.Settings.createBlobProviderKey;
import static org.junit.Assert.assertTrue;
@@ -42,7 +40,6 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.system.OsConstants;
-import android.util.Base64;
import android.util.Log;
import androidx.test.uiautomator.UiDevice;
@@ -169,13 +166,12 @@
session.commit(AsyncTask.THREAD_POOL_EXECUTOR, i -> wait.countDown());
}
- String key = Base64.encodeToString(digest, NO_WRAP | NO_PADDING);
-
grantWriteSecurePermission();
- Settings.Secure.putString(context.getContentResolver(), LAYOUT_DIGEST_KEY, key);
+ Settings.Secure.putString(
+ context.getContentResolver(), LAYOUT_PROVIDER_KEY, createBlobProviderKey(digest));
wait.await();
return () ->
- Settings.Secure.putString(context.getContentResolver(), LAYOUT_DIGEST_KEY, null);
+ Settings.Secure.putString(context.getContentResolver(), LAYOUT_PROVIDER_KEY, null);
}
/**
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
index ec83b8b..7484bce 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
@@ -38,8 +38,8 @@
@get:Rule val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
private val providerName =
ComponentName(
- "com.android.launcher3.tests",
- "com.android.launcher3.testcomponent.AppWidgetNoConfig"
+ getInstrumentation().getContext().getPackageName(),
+ "com.android.launcher3.testcomponent.AppWidgetNoConfig",
)
private val generatedPreviewLayout =
getInstrumentation().context.run {
@@ -61,7 +61,7 @@
ActivityContextWrapper(
ContextThemeWrapper(
context,
- com.android.launcher3.R.style.WidgetContainerTheme
+ com.android.launcher3.R.style.WidgetContainerTheme,
)
)
)
@@ -78,7 +78,7 @@
object : WidgetManagerHelper(context) {
override fun loadGeneratedPreview(
info: AppWidgetProviderInfo,
- widgetCategory: Int
+ widgetCategory: Int,
) =
generatedPreview.takeIf {
info === appWidgetProviderInfo &&
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfoTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfoTest.kt
index 0a3035a..af2c378 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfoTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfoTest.kt
@@ -17,7 +17,6 @@
package com.android.launcher3.widget.custom
import android.content.ComponentName
-import android.content.pm.PackageManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
@@ -25,7 +24,6 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
@MediumTest
@RunWith(AndroidJUnit4::class)
@@ -47,7 +45,7 @@
@Test
fun get_label() {
underTest.label = " TEST_LABEL"
- assertEquals(LABEL_NAME, underTest.getLabel(mock(PackageManager::class.java)))
+ assertEquals(LABEL_NAME, underTest.getLabel())
}
companion object {
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/custom/CustomWidgetManagerTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/custom/CustomWidgetManagerTest.kt
index 4b5710d..82f56b8 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/custom/CustomWidgetManagerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/custom/CustomWidgetManagerTest.kt
@@ -23,6 +23,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.util.DaggerSingletonTracker
import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
import com.android.launcher3.util.PluginManagerWrapper
import com.android.launcher3.util.WidgetUtils
@@ -57,12 +58,13 @@
@Mock private lateinit var pluginManager: PluginManagerWrapper
@Mock private lateinit var mockAppWidgetManager: AppWidgetManager
+ @Mock private lateinit var tracker: DaggerSingletonTracker
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
context.putObject(PluginManagerWrapper.INSTANCE, pluginManager)
- underTest = CustomWidgetManager(context, mockAppWidgetManager)
+ underTest = CustomWidgetManager(context, mockAppWidgetManager, tracker)
}
@After
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/model/WidgetsListBaseEntriesBuilderTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/model/WidgetsListBaseEntriesBuilderTest.kt
index 5df7caa..063ab32 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/model/WidgetsListBaseEntriesBuilderTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/model/WidgetsListBaseEntriesBuilderTest.kt
@@ -26,8 +26,8 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.LauncherAppState
-import com.android.launcher3.icons.ComponentWithLabel
import com.android.launcher3.icons.IconCache
+import com.android.launcher3.icons.cache.CachedObject
import com.android.launcher3.model.WidgetItem
import com.android.launcher3.model.data.PackageItemInfo
import com.android.launcher3.util.ActivityContextWrapper
@@ -66,11 +66,11 @@
testInvariantProfile = LauncherAppState.getIDP(context)
doAnswer { invocation: InvocationOnMock ->
- val componentWithLabel = invocation.getArgument<Any>(0) as ComponentWithLabel
+ val componentWithLabel = invocation.getArgument<Any>(0) as CachedObject
componentWithLabel.getComponent().shortClassName
}
.`when`(iconCache)
- .getTitleNoCache(any<ComponentWithLabel>())
+ .getTitleNoCache(any<CachedObject>())
underTest = WidgetsListBaseEntriesBuilder(context)
allWidgets =
@@ -79,14 +79,14 @@
packageItemInfoWithTitle(APP_1_PACKAGE_NAME, APP_1_PACKAGE_TITLE) to
listOf(
createWidgetItem(APP_1_PACKAGE_NAME, APP_1_PROVIDER_1_CLASS_NAME),
- createWidgetItem(APP_1_PACKAGE_NAME, APP_1_PROVIDER_2_CLASS_NAME)
+ createWidgetItem(APP_1_PACKAGE_NAME, APP_1_PROVIDER_2_CLASS_NAME),
),
// app 2
packageItemInfoWithTitle(APP_2_PACKAGE_NAME, APP_2_PACKAGE_TITLE) to
listOf(createWidgetItem(APP_2_PACKAGE_NAME, APP_2_PROVIDER_1_CLASS_NAME)),
// app 3
packageItemInfoWithTitle(APP_3_PACKAGE_NAME, APP_3_PACKAGE_TITLE) to
- listOf(createWidgetItem(APP_3_PACKAGE_NAME, APP_3_PROVIDER_1_CLASS_NAME))
+ listOf(createWidgetItem(APP_3_PACKAGE_NAME, APP_3_PROVIDER_1_CLASS_NAME)),
)
}
@@ -96,7 +96,7 @@
listOf(
APP_1_EXPECTED_SECTION_NAME to 2,
APP_2_EXPECTED_SECTION_NAME to 1,
- APP_3_EXPECTED_SECTION_NAME to 1
+ APP_3_EXPECTED_SECTION_NAME to 1,
)
val entries = underTest.build(allWidgets)
@@ -122,7 +122,7 @@
val expectedWidgetsCountBySection =
listOf(
APP_1_EXPECTED_SECTION_NAME to 1, // one widget filtered out
- APP_3_EXPECTED_SECTION_NAME to 1
+ APP_3_EXPECTED_SECTION_NAME to 1,
)
val entries =
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
index d4e061a..c9b6d4f 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
@@ -42,8 +42,8 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.icons.ComponentWithLabel;
import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.icons.cache.CachedObject;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.util.ActivityContextWrapper;
@@ -87,7 +87,7 @@
mTestProfile.numColumns = 5;
doAnswer(invocation -> {
- ComponentWithLabel componentWithLabel = (ComponentWithLabel) invocation.getArgument(0);
+ CachedObject componentWithLabel = invocation.getArgument(0);
return componentWithLabel.getComponent().getShortClassName();
}).when(mIconCache).getTitleNoCache(any());
mViewHolderBinder = new WidgetsListHeaderViewHolderBinder(
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
index e1cc010..0d9464a 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
@@ -45,8 +45,8 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.icons.ComponentWithLabel;
import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.icons.cache.CachedObject;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.util.ActivityContextWrapper;
@@ -92,7 +92,7 @@
mTestProfile.numColumns = 5;
doAnswer(invocation -> {
- ComponentWithLabel componentWithLabel = (ComponentWithLabel) invocation.getArgument(0);
+ CachedObject componentWithLabel = invocation.getArgument(0);
return componentWithLabel.getComponent().getShortClassName();
}).when(mIconCache).getTitleNoCache(any());
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetPickerDataProviderTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetPickerDataProviderTest.kt
index 1822639..1da74cb 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetPickerDataProviderTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetPickerDataProviderTest.kt
@@ -27,8 +27,8 @@
import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.LauncherAppState
import com.android.launcher3.LauncherSettings
-import com.android.launcher3.icons.ComponentWithLabel
import com.android.launcher3.icons.IconCache
+import com.android.launcher3.icons.cache.CachedObject
import com.android.launcher3.model.WidgetItem
import com.android.launcher3.model.data.PackageItemInfo
import com.android.launcher3.util.ActivityContextWrapper
@@ -81,11 +81,11 @@
testInvariantProfile = LauncherAppState.getIDP(context)
doAnswer { invocation: InvocationOnMock ->
- val componentWithLabel = invocation.getArgument<Any>(0) as ComponentWithLabel
+ val componentWithLabel = invocation.getArgument<Any>(0) as CachedObject
componentWithLabel.getComponent().shortClassName
}
.`when`(iconCache)
- .getTitleNoCache(any<ComponentWithLabel>())
+ .getTitleNoCache(any<CachedObject>())
appWidgetItem = createWidgetItem()
}
@@ -113,8 +113,8 @@
listOf(
PendingAddWidgetInfo(
appWidgetItem.widgetInfo,
- LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION
- ),
+ LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION,
+ )
)
underTest.setWidgetRecommendations(recommendations)
@@ -133,8 +133,8 @@
listOf(
PendingAddWidgetInfo(
appWidgetItem.widgetInfo,
- LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION
- ),
+ LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION,
+ )
)
underTest.setWidgetRecommendations(recommendations)
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java
index 7552619..6088c8e 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java
@@ -33,8 +33,8 @@
import androidx.test.filters.SmallTest;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.icons.ComponentWithLabel;
import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.icons.cache.CachedObject;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
@@ -81,7 +81,7 @@
mTestProfile.numColumns = 5;
doAnswer(invocation -> {
- ComponentWithLabel componentWithLabel = (ComponentWithLabel) invocation.getArgument(0);
+ CachedObject componentWithLabel = invocation.getArgument(0);
return mWidgetsToLabels.get(componentWithLabel.getComponent());
}).when(mIconCache).getTitleNoCache(any());
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/data/WidgetPickerDataTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/data/WidgetPickerDataTest.kt
index e59e211..deec67a 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/data/WidgetPickerDataTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/data/WidgetPickerDataTest.kt
@@ -27,8 +27,8 @@
import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.LauncherAppState
import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION
-import com.android.launcher3.icons.ComponentWithLabel
import com.android.launcher3.icons.IconCache
+import com.android.launcher3.icons.cache.CachedObject
import com.android.launcher3.model.WidgetItem
import com.android.launcher3.model.data.ItemInfo
import com.android.launcher3.model.data.PackageItemInfo
@@ -86,11 +86,11 @@
testInvariantProfile = LauncherAppState.getIDP(context)
doAnswer { invocation: InvocationOnMock ->
- val componentWithLabel = invocation.getArgument<Any>(0) as ComponentWithLabel
+ val componentWithLabel = invocation.getArgument<Any>(0) as CachedObject
componentWithLabel.getComponent().shortClassName
}
.`when`(iconCache)
- .getTitleNoCache(any<ComponentWithLabel>())
+ .getTitleNoCache(any<CachedObject>())
app1PackageItemInfo = packageItemInfoWithTitle(APP_1_PACKAGE_NAME, APP_1_PACKAGE_TITLE)
app2PackageItemInfo = packageItemInfoWithTitle(APP_2_PACKAGE_NAME, APP_2_PACKAGE_TITLE)
@@ -123,7 +123,7 @@
val widgetPickerData =
WidgetPickerData(
allWidgets = appTwoWidgetsListBaseEntries(),
- defaultWidgets = appTwoWidgetsListBaseEntries()
+ defaultWidgets = appTwoWidgetsListBaseEntries(),
)
val newWidgetData =
@@ -143,19 +143,19 @@
addAll(appOneWidgetsListBaseEntries())
addAll(appTwoWidgetsListBaseEntries())
},
- defaultWidgets = buildList { appTwoWidgetsListBaseEntries() }
+ defaultWidgets = buildList { appTwoWidgetsListBaseEntries() },
)
val recommendations: List<ItemInfo> =
listOf(
PendingAddWidgetInfo(
app1WidgetItem1.widgetInfo,
CONTAINER_WIDGETS_PREDICTION,
- CATEGORY_1
+ CATEGORY_1,
),
PendingAddWidgetInfo(
app2WidgetItem1.widgetInfo,
CONTAINER_WIDGETS_PREDICTION,
- CATEGORY_2
+ CATEGORY_2,
),
)
@@ -175,7 +175,7 @@
addAll(appOneWidgetsListBaseEntries())
addAll(appTwoWidgetsListBaseEntries())
},
- defaultWidgets = buildList { appTwoWidgetsListBaseEntries() }
+ defaultWidgets = buildList { appTwoWidgetsListBaseEntries() },
)
val recommendations: List<ItemInfo> =
listOf(
@@ -201,7 +201,7 @@
addAll(appTwoWidgetsListBaseEntries())
},
defaultWidgets = buildList { appTwoWidgetsListBaseEntries() },
- recommendations = mapOf(CATEGORY_1 to listOf(app1WidgetItem1))
+ recommendations = mapOf(CATEGORY_1 to listOf(app1WidgetItem1)),
)
val updatedData = widgetPickerData.withRecommendedWidgets(listOf())
@@ -242,7 +242,7 @@
addAll(appOneWidgetsListBaseEntries())
addAll(appTwoWidgetsListBaseEntries())
},
- defaultWidgets = buildList { addAll(appTwoWidgetsListBaseEntries()) }
+ defaultWidgets = buildList { addAll(appTwoWidgetsListBaseEntries()) },
)
val app1PackageUserKey = PackageUserKey.fromPackageItemInfo(app1PackageItemInfo)
@@ -263,7 +263,7 @@
addAll(appTwoWidgetsListBaseEntries())
},
defaultWidgets =
- buildList { addAll(appOneWidgetsListBaseEntries(includeWidgetTwo = false)) }
+ buildList { addAll(appOneWidgetsListBaseEntries(includeWidgetTwo = false)) },
)
val app1PackageUserKey = PackageUserKey.fromPackageItemInfo(app1PackageItemInfo)
@@ -271,7 +271,7 @@
findContentEntryForPackageUser(
widgetPickerData = widgetPickerData,
packageUserKey = app1PackageUserKey,
- fromDefaultWidgets = true
+ fromDefaultWidgets = true,
)
assertThat(contentEntry).isNotNull()
@@ -302,7 +302,7 @@
addAll(appTwoWidgetsListBaseEntries())
},
defaultWidgets =
- buildList { addAll(appOneWidgetsListBaseEntries(includeWidgetTwo = false)) }
+ buildList { addAll(appOneWidgetsListBaseEntries(includeWidgetTwo = false)) },
)
val widgets = findAllWidgetsForPackageUser(widgetPickerData, app1PackageUserKey)
@@ -314,9 +314,7 @@
@Test
fun findAllWidgetsForPackageUser_noMatch_returnsEmptyList() {
val widgetPickerData =
- WidgetPickerData(
- allWidgets = buildList { addAll(appTwoWidgetsListBaseEntries()) },
- )
+ WidgetPickerData(allWidgets = buildList { addAll(appTwoWidgetsListBaseEntries()) })
val app1PackageUserKey = PackageUserKey.fromPackageItemInfo(app1PackageItemInfo)
val widgets = findAllWidgetsForPackageUser(widgetPickerData, app1PackageUserKey)
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
index 24d66a3..59f352b 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
@@ -41,8 +41,8 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.icons.ComponentWithLabel;
import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.icons.cache.CachedObject;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.search.SearchCallback;
@@ -87,7 +87,7 @@
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
doAnswer(invocation -> {
- ComponentWithLabel componentWithLabel = (ComponentWithLabel) invocation.getArgument(0);
+ CachedObject componentWithLabel = invocation.getArgument(0);
return componentWithLabel.getComponent().getShortClassName();
}).when(mIconCache).getTitleNoCache(any());
mTestProfile = new InvariantDeviceProfile();
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..2f5fcfe 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
@@ -28,7 +28,6 @@
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
@@ -39,8 +38,9 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.icons.ComponentWithLabel;
import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.icons.cache.BaseIconCache;
+import com.android.launcher3.icons.cache.CachedObject;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.pm.ShortcutConfigActivityInfo;
import com.android.launcher3.util.ActivityContextWrapper;
@@ -99,7 +99,7 @@
initTestWidgets();
initTestShortcuts();
- doAnswer(invocation -> ((ComponentWithLabel) invocation.getArgument(0))
+ doAnswer(invocation -> ((CachedObject) invocation.getArgument(0))
.getComponent().getPackageName())
.when(mIconCache).getTitleNoCache(any());
}
@@ -280,32 +280,31 @@
}
private void initTestShortcuts() {
- PackageManager packageManager = mContext.getPackageManager();
mShortcut1 = new WidgetItem(new TestShortcutConfigActivityInfo(
ComponentName.createRelative(TEST_PACKAGE, ".shortcut1"), UserHandle.CURRENT),
- mIconCache, packageManager);
+ mIconCache);
mShortcut2 = new WidgetItem(new TestShortcutConfigActivityInfo(
ComponentName.createRelative(TEST_PACKAGE, ".shortcut2"), UserHandle.CURRENT),
- mIconCache, packageManager);
+ mIconCache);
mShortcut3 = new WidgetItem(new TestShortcutConfigActivityInfo(
ComponentName.createRelative(TEST_PACKAGE, ".shortcut3"), UserHandle.CURRENT),
- mIconCache, packageManager);
+ mIconCache);
}
private final class TestShortcutConfigActivityInfo extends ShortcutConfigActivityInfo {
TestShortcutConfigActivityInfo(ComponentName componentName, UserHandle user) {
- super(componentName, user);
+ super(componentName, user, mContext);
}
@Override
- public Drawable getFullResIcon(IconCache cache) {
+ public Drawable getFullResIcon(BaseIconCache cache) {
return null;
}
@Override
- public CharSequence getLabel(PackageManager pm) {
+ public CharSequence getLabel() {
return null;
}
}
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_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/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 0dd13a9..b17cd4d 100644
--- a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
+++ b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
@@ -33,17 +33,15 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.ArgumentMatchers.anyList
-import org.mockito.ArgumentMatchers.anyMap
import org.mockito.Mock
import org.mockito.Mockito
+import org.mockito.Mockito.mock
import org.mockito.Mockito.times
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
import org.mockito.MockitoSession
import org.mockito.Spy
+import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.spy
@@ -67,7 +65,7 @@
installedHotseatItems = mutableSetOf("installedHotseatItem"),
installedWorkspaceItems = mutableSetOf("installedWorkspaceItem"),
firstScreenInstalledWidgets = mutableSetOf("installedFirstScreenWidget"),
- secondaryScreenInstalledWidgets = mutableSetOf("installedSecondaryScreenWidget")
+ secondaryScreenInstalledWidgets = mutableSetOf("installedSecondaryScreenWidget"),
)
private lateinit var mockitoSession: MockitoSession
@@ -75,7 +73,7 @@
@Mock private lateinit var bgAllAppsList: AllAppsList
@Mock private lateinit var modelDelegate: ModelDelegate
@Mock private lateinit var launcherBinder: BaseLauncherBinder
- @Mock private lateinit var launcherModel: LauncherModel
+ private lateinit var launcherModel: LauncherModel
@Mock private lateinit var transaction: LoaderTransaction
@Mock private lateinit var iconCache: IconCache
@Mock private lateinit var idleLock: LooperIdleLock
@@ -89,6 +87,7 @@
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
+ launcherModel = mock(LauncherModel::class.java)
mockitoSession =
ExtendedMockito.mockitoSession()
.strictness(Strictness.LENIENT)
@@ -105,15 +104,16 @@
doReturn(TestViewHelpers.findWidgetProvider(false))
.`when`(context.spyService(AppWidgetManager::class.java))
- .getAppWidgetInfo(anyInt())
+ .getAppWidgetInfo(any())
`when`(app.context).thenReturn(context)
`when`(app.model).thenReturn(launcherModel)
- `when`(launcherModel.beginLoader(any(LoaderTask::class.java))).thenReturn(transaction)
+
+ `when`(launcherModel.beginLoader(any())).thenReturn(transaction)
`when`(app.iconCache).thenReturn(iconCache)
`when`(launcherModel.modelDbController)
.thenReturn(FactitiousDbController(context, INSERTION_STATEMENT_FILE))
`when`(app.invariantDeviceProfile).thenReturn(idp)
- `when`(launcherBinder.newIdleLock(any(LoaderTask::class.java))).thenReturn(idleLock)
+ `when`(launcherBinder.newIdleLock(any())).thenReturn(idleLock)
`when`(idleLock.awaitLocked(1000)).thenReturn(false)
`when`(iconCache.updateHandler).thenReturn(iconCacheUpdateHandler)
context.putObject(UserCache.INSTANCE, userCache)
@@ -149,12 +149,12 @@
verify(launcherBinder).bindWorkspace(true, false)
verify(modelDelegate).workspaceLoadComplete()
- verify(modelDelegate).loadAndBindAllAppsItems(any(), any(), any())
+ verify(modelDelegate).loadAndBindAllAppsItems(any(), anyOrNull(), any())
verify(launcherBinder).bindAllApps()
verify(iconCacheUpdateHandler, times(4)).updateIcons(any(), any<CachingLogic<Any>>(), any())
verify(launcherBinder).bindDeepShortcuts()
verify(launcherBinder).bindWidgets()
- verify(modelDelegate).loadAndBindOtherItems(any())
+ verify(modelDelegate).loadAndBindOtherItems(anyOrNull())
verify(iconCacheUpdateHandler).finish()
verify(modelDelegate).modelLoadComplete()
verify(transaction).commit()
@@ -209,10 +209,10 @@
`when`(app.context).thenReturn(spyContext)
whenever(
FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
- anyOrNull(),
- anyList(),
- anyMap(),
- anyList()
+ any(),
+ any(),
+ any(),
+ any(),
)
)
.thenReturn(listOf(expectedBroadcastModel))
@@ -220,7 +220,7 @@
whenever(
FirstScreenBroadcastHelper.sendBroadcastsForModels(
spyContext,
- listOf(expectedBroadcastModel)
+ listOf(expectedBroadcastModel),
)
)
.thenCallRealMethod()
@@ -239,34 +239,34 @@
assertEquals(expectedBroadcastModel.installerPackage, actualBroadcastIntent.`package`)
assertEquals(
ArrayList(expectedBroadcastModel.installedWorkspaceItems),
- actualBroadcastIntent.getStringArrayListExtra("workspaceInstalledItems")
+ actualBroadcastIntent.getStringArrayListExtra("workspaceInstalledItems"),
)
assertEquals(
ArrayList(expectedBroadcastModel.installedHotseatItems),
- actualBroadcastIntent.getStringArrayListExtra("hotseatInstalledItems")
+ actualBroadcastIntent.getStringArrayListExtra("hotseatInstalledItems"),
)
assertEquals(
ArrayList(
expectedBroadcastModel.firstScreenInstalledWidgets +
expectedBroadcastModel.secondaryScreenInstalledWidgets
),
- actualBroadcastIntent.getStringArrayListExtra("widgetInstalledItems")
+ actualBroadcastIntent.getStringArrayListExtra("widgetInstalledItems"),
)
assertEquals(
ArrayList(expectedBroadcastModel.pendingCollectionItems),
- actualBroadcastIntent.getStringArrayListExtra("folderItem")
+ actualBroadcastIntent.getStringArrayListExtra("folderItem"),
)
assertEquals(
ArrayList(expectedBroadcastModel.pendingWorkspaceItems),
- actualBroadcastIntent.getStringArrayListExtra("workspaceItem")
+ actualBroadcastIntent.getStringArrayListExtra("workspaceItem"),
)
assertEquals(
ArrayList(expectedBroadcastModel.pendingHotseatItems),
- actualBroadcastIntent.getStringArrayListExtra("hotseatItem")
+ actualBroadcastIntent.getStringArrayListExtra("hotseatItem"),
)
assertEquals(
ArrayList(expectedBroadcastModel.pendingWidgetItems),
- actualBroadcastIntent.getStringArrayListExtra("widgetItem")
+ actualBroadcastIntent.getStringArrayListExtra("widgetItem"),
)
}
@@ -277,10 +277,10 @@
`when`(app.context).thenReturn(spyContext)
whenever(
FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
- anyOrNull(),
- anyList(),
- anyMap(),
- anyList()
+ any(),
+ any(),
+ any(),
+ any(),
)
)
.thenReturn(listOf(expectedBroadcastModel))
@@ -288,7 +288,7 @@
whenever(
FirstScreenBroadcastHelper.sendBroadcastsForModels(
spyContext,
- listOf(expectedBroadcastModel)
+ listOf(expectedBroadcastModel),
)
)
.thenCallRealMethod()
@@ -300,7 +300,7 @@
.runSyncOnBackgroundThread()
// Then
- verify(spyContext, times(0)).sendBroadcast(any(Intent::class.java))
+ verify(spyContext, times(0)).sendBroadcast(any())
}
@Test
@@ -310,10 +310,10 @@
`when`(app.context).thenReturn(spyContext)
whenever(
FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
- anyOrNull(),
- anyList(),
- anyMap(),
- anyList()
+ any(),
+ any(),
+ any(),
+ any(),
)
)
.thenReturn(listOf(expectedBroadcastModel))
@@ -321,7 +321,7 @@
whenever(
FirstScreenBroadcastHelper.sendBroadcastsForModels(
spyContext,
- listOf(expectedBroadcastModel)
+ listOf(expectedBroadcastModel),
)
)
.thenCallRealMethod()
@@ -334,7 +334,7 @@
.runSyncOnBackgroundThread()
// Then
- verify(spyContext, times(0)).sendBroadcast(any(Intent::class.java))
+ verify(spyContext, times(0)).sendBroadcast(any())
}
}
diff --git a/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt b/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt
index 05f626d..d9af07a 100644
--- a/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt
+++ b/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt
@@ -58,8 +58,7 @@
@RunWith(AndroidJUnit4::class)
class PackageUpdatedTaskTest {
- @get:Rule(order = 0) val setFlagsRule = SetFlagsRule()
- @get:Rule(order = 1) val modelTestRule = ModelTestRule()
+ @get:Rule val setFlagsRule = SetFlagsRule()
private val mUser = UserHandle(0)
private val mDataModel: BgDataModel = BgDataModel()
diff --git a/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt b/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt
index f1b6271..d553f47 100644
--- a/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt
+++ b/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt
@@ -35,6 +35,7 @@
import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
+import com.android.launcher3.icons.CacheableShortcutInfo
import com.android.launcher3.model.data.IconRequestInfo
import com.android.launcher3.model.data.LauncherAppWidgetInfo
import com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_RESTORE_STARTED
@@ -84,7 +85,7 @@
private var mUnlockedUsersArray: LongSparseArray<Boolean> = LongSparseArray()
private var mKeyToPinnedShortcutsMap: MutableMap<ShortcutKey, ShortcutInfo> = mutableMapOf()
private var mInstallingPkgs: HashMap<PackageUserKey, PackageInstaller.SessionInfo> = hashMapOf()
- private var mAllDeepShortcuts: MutableList<ShortcutInfo> = mutableListOf()
+ private var mAllDeepShortcuts: MutableList<CacheableShortcutInfo> = mutableListOf()
private var mWidgetProvidersMap: MutableMap<ComponentKey, AppWidgetProviderInfo?> =
mutableMapOf()
private var mPendingPackages: MutableSet<PackageUserKey> = mutableSetOf()
@@ -290,7 +291,7 @@
pendingPackages: MutableSet<PackageUserKey> = mPendingPackages,
unlockedUsers: LongSparseArray<Boolean> = mUnlockedUsersArray,
installingPkgs: HashMap<PackageUserKey, PackageInstaller.SessionInfo> = mInstallingPkgs,
- allDeepShortcuts: MutableList<ShortcutInfo> = mAllDeepShortcuts,
+ allDeepShortcuts: MutableList<CacheableShortcutInfo> = mAllDeepShortcuts,
) =
WorkspaceItemProcessor(
c = cursor,
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 68004bb..df5c80d 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -20,12 +20,12 @@
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import static com.android.launcher3.testing.shared.TestProtocol.ICON_MISSING;
-import static com.android.launcher3.testing.shared.TestProtocol.WIDGET_CONFIG_NULL_EXTRA_INTENT;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
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 +103,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 +126,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 +198,8 @@
}
protected AbstractLauncherUiTest() {
+ mActivityManager = InstrumentationRegistry.getContext()
+ .getSystemService(ActivityManager.class);
mLauncher.enableCheckEventsForSuccessfulGestures();
mLauncher.setAnomalyChecker(AbstractLauncherUiTest::verifyKeyguardInvisible);
try {
@@ -311,6 +319,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();
@@ -534,23 +562,13 @@
@Override
public void onReceive(Context context, Intent intent) {
- Log.d(WIDGET_CONFIG_NULL_EXTRA_INTENT, intent == null
- ? "AbstractLauncherUiTest.onReceive(): inputted intent NULL"
- : "AbstractLauncherUiTest.onReceive(): inputted intent NOT NULL");
mIntent = intent;
latch.countDown();
- Log.d(WIDGET_CONFIG_NULL_EXTRA_INTENT,
- "AbstractLauncherUiTest.onReceive() Countdown Latch started");
}
public Intent blockingGetIntent() throws InterruptedException {
- Log.d(WIDGET_CONFIG_NULL_EXTRA_INTENT,
- "AbstractLauncherUiTest.blockingGetIntent()");
assertTrue("Timed Out", latch.await(DEFAULT_BROADCAST_TIMEOUT_SECS, TimeUnit.SECONDS));
mTargetContext.unregisterReceiver(this);
- Log.d(WIDGET_CONFIG_NULL_EXTRA_INTENT, mIntent == null
- ? "AbstractLauncherUiTest.onReceive(): mIntent NULL"
- : "AbstractLauncherUiTest.onReceive(): mIntent NOT NULL");
return mIntent;
}
diff --git a/tests/src/com/android/launcher3/ui/widget/TaplAddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/TaplAddConfigWidgetTest.java
index e6e02b4..961e7fc 100644
--- a/tests/src/com/android/launcher3/ui/widget/TaplAddConfigWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/TaplAddConfigWidgetTest.java
@@ -93,7 +93,7 @@
WidgetConfigStartupMonitor monitor = new WidgetConfigStartupMonitor();
mLauncher.getWorkspace()
.openAllWidgets()
- .getWidget(mWidgetInfo.getLabel(mTargetContext.getPackageManager()))
+ .getWidget(mWidgetInfo.getLabel())
.dragToWorkspace(true, false);
// Widget id for which the config activity was opened
mWidgetId = monitor.getWidgetId();
diff --git a/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
index 9c916fa..9a2147a 100644
--- a/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
@@ -61,7 +61,7 @@
WidgetResizeFrame resizeFrame = mLauncher
.getWorkspace()
.openAllWidgets()
- .getWidget(widgetInfo.getLabel(mTargetContext.getPackageManager()))
+ .getWidget(widgetInfo.getLabel())
.dragWidgetToWorkspace();
assertNotNull("Widget resize frame not shown after widget add", resizeFrame);
@@ -111,7 +111,7 @@
WidgetResizeFrame resizeFrame = mLauncher
.getWorkspace()
.openAllWidgets()
- .getWidget(widgetInfo.getLabel(mTargetContext.getPackageManager()))
+ .getWidget(widgetInfo.getLabel())
.dragWidgetToWorkspace();
assertNotNull("Widget resize frame not shown after widget add", resizeFrame);
diff --git a/tests/src/com/android/launcher3/ui/workspace/TaplThemeIconsTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplThemeIconsTest.java
index a148744..d653317 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TaplThemeIconsTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TaplThemeIconsTest.java
@@ -16,8 +16,6 @@
package com.android.launcher3.ui.workspace;
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 org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -40,8 +38,6 @@
import com.android.launcher3.tapl.HomeAppIconMenuItem;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.util.Executors;
-import com.android.launcher3.util.rule.ScreenRecordRule;
-import com.android.launcher3.util.rule.TestStabilityRule;
import org.junit.Test;
@@ -115,8 +111,6 @@
}
@Test
- @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/350557998
- @ScreenRecordRule.ScreenRecord // b/350557998
public void testShortcutIconWithTheme() throws Exception {
setThemeEnabled(true);
initialize(this);
diff --git a/tests/src/com/android/launcher3/util/RoboApiWrapper.kt b/tests/src/com/android/launcher3/util/RoboApiWrapper.kt
index 583652d..7f74e56 100644
--- a/tests/src/com/android/launcher3/util/RoboApiWrapper.kt
+++ b/tests/src/com/android/launcher3/util/RoboApiWrapper.kt
@@ -24,12 +24,10 @@
object RoboApiWrapper {
- fun initialize() {}
-
fun registerInputStream(
contentResolver: ContentResolver,
uri: Uri,
- inputStreamSupplier: Supplier<InputStream>
+ inputStreamSupplier: Supplier<InputStream>,
) {}
fun waitForLooperSync(looper: Looper) {}
diff --git a/tests/src_deviceless/com/android/launcher3/util/RoboApiWrapper.kt b/tests/src_deviceless/com/android/launcher3/util/RoboApiWrapper.kt
index 9232268..a2b8303 100644
--- a/tests/src_deviceless/com/android/launcher3/util/RoboApiWrapper.kt
+++ b/tests/src_deviceless/com/android/launcher3/util/RoboApiWrapper.kt
@@ -16,70 +16,19 @@
package com.android.launcher3.util
-import android.content.ComponentName
import android.content.ContentResolver
-import android.content.Intent
-import android.content.IntentFilter
-import android.content.pm.ApplicationInfo
-import android.content.pm.LauncherActivityInfo
-import android.content.pm.LauncherApps
import android.net.Uri
import android.os.Looper
-import android.os.Process
-import androidx.test.platform.app.InstrumentationRegistry
import java.io.InputStream
import java.util.function.Supplier
-import org.mockito.Mockito
-import org.mockito.kotlin.whenever
-import org.robolectric.RuntimeEnvironment
import org.robolectric.Shadows
object RoboApiWrapper {
- fun initialize() {
- Shadows.shadowOf(
- RuntimeEnvironment.getApplication().getSystemService(LauncherApps::class.java)
- )
- .addEnabledPackage(
- Process.myUserHandle(),
- InstrumentationRegistry.getInstrumentation().context.packageName
- )
- LauncherModelHelper.ACTIVITY_LIST.forEach {
- installApp(ComponentName(InstrumentationRegistry.getInstrumentation().context, it))
- }
- }
-
- private fun installApp(componentName: ComponentName) {
- val app = RuntimeEnvironment.getApplication()
- val user = Process.myUserHandle()
-
- val pm = Shadows.shadowOf(app.packageManager)
- val ai = pm.addActivityIfNotPresent(componentName)
- pm.addIntentFilterForActivity(
- componentName,
- IntentFilter(Intent.ACTION_MAIN).apply { addCategory(Intent.CATEGORY_LAUNCHER) }
- )
-
- val li = Mockito.mock(LauncherActivityInfo::class.java)
- val appInfo = ApplicationInfo().apply { flags = 0 }
- Mockito.doReturn(ai).whenever(li).activityInfo
- Mockito.doReturn(appInfo).whenever(li).applicationInfo
- Mockito.doReturn(user).whenever(li).user
- Mockito.doReturn(1f).whenever(li).loadingProgress
- Mockito.doReturn(componentName).whenever(li).componentName
-
- Shadows.shadowOf(app.getSystemService(LauncherApps::class.java)).apply {
- addActivity(user, li)
- addEnabledPackage(user, componentName.packageName)
- setActivityEnabled(user, componentName)
- addApplicationInfo(user, componentName.packageName, ai.applicationInfo)
- }
- }
-
fun registerInputStream(
contentResolver: ContentResolver,
uri: Uri,
- inputStreamSupplier: Supplier<InputStream>
+ inputStreamSupplier: Supplier<InputStream>,
) {
Shadows.shadowOf(contentResolver).registerInputStreamSupplier(uri, inputStreamSupplier)
}
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index 02a862d..9294755 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -16,10 +16,8 @@
package com.android.launcher3.tapl;
-import static com.android.launcher3.testing.shared.TestProtocol.TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE;
import android.graphics.Point;
-import android.util.Log;
import android.widget.TextView;
import androidx.annotation.NonNull;
@@ -99,8 +97,6 @@
@Override
protected void waitForLongPressConfirmation() {
- Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
- "AppIcon.waitForLongPressConfirmation, resName: popupContainer");
mLauncher.waitForLauncherObject("popup_container");
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index 9d3bc6e..c40e5a9 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -17,10 +17,8 @@
package com.android.launcher3.tapl;
import static com.android.launcher3.testing.shared.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
-import static com.android.launcher3.testing.shared.TestProtocol.TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE;
import android.graphics.Point;
-import android.util.Log;
import android.view.MotionEvent;
import androidx.test.uiautomator.UiObject2;
@@ -115,10 +113,6 @@
iconCenter.y - getStartDragThreshold());
if (runToSpringLoadedState) {
- Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
- "Launchable.startDrag: actionName: long-pressing and triggering drag start"
- + " iconCenter: " + iconCenter + " dragStartCenter: "
- + dragStartCenter);
mLauncher.runToState(() -> movePointerForStartDrag(
downTime,
iconCenter,
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 78627e5..08c5552 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -31,7 +31,6 @@
import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_GET_SPLIT_SELECTION_ACTIVE;
import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_NUM_ALL_APPS_COLUMNS;
-import static com.android.launcher3.testing.shared.TestProtocol.TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE;
import static com.android.launcher3.testing.shared.TestProtocol.TEST_INFO_RESPONSE_FIELD;
import android.app.ActivityManager;
@@ -1212,11 +1211,6 @@
log("Hierarchy before clicking home:");
dumpViewHierarchy();
action = "clicking home button";
- Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
- "LauncherInstrumentation.goHome: isThreeFingerTrackpadGesture: "
- + isThreeFingerTrackpadGesture
- + "getNavigationModel() == NavigationModel.ZERO_BUTTON: " + (
- getNavigationModel() == NavigationModel.ZERO_BUTTON));
runToState(
getHomeButton()::click,
NORMAL_STATE_ORDINAL,
@@ -1567,8 +1561,6 @@
@NonNull
UiObject2 waitForLauncherObject(String resName) {
- Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
- "LauncherInstrumentation.waitForLauncherObject");
return waitForObjectBySelector(getLauncherObjectSelector(resName));
}
@@ -1598,16 +1590,12 @@
@NonNull
List<UiObject2> waitForObjectsBySelector(BySelector selector) {
- Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
- "LauncherInstrumentation.waitForObjectsBySelector");
final List<UiObject2> objects = mDevice.wait(Until.findObjects(selector), WAIT_TIME_MS);
assertNotNull("Can't find any view in Launcher, selector: " + selector, objects);
return objects;
}
UiObject2 waitForObjectBySelector(BySelector selector) {
- Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
- "LauncherInstrumentation.waitForObjectBySelector");
final UiObject2 object = mDevice.wait(Until.findObject(selector), WAIT_TIME_MS);
assertNotNull("Can't find a view in Launcher, selector: " + selector, object);
return object;
@@ -1650,9 +1638,6 @@
void runToState(Runnable command, int expectedState, boolean requireEvent, String actionName) {
if (requireEvent) {
- Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
- "LauncherInstrumentation.runToState: command: " + command + " expectedState: "
- + expectedState + " actionName: " + actionName + "requireEvent: true");
runToState(command, expectedState, actionName);
} else {
command.run();
@@ -2052,15 +2037,11 @@
mPointerCount = 1;
pointerCount = mPointerCount;
}
- Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
- "LauncherInstrumentation.sendPointer: ACTION_DOWN");
break;
case MotionEvent.ACTION_UP:
if (hasTIS && gestureScope == GestureScope.EXPECT_PILFER) {
expectEvent(TestProtocol.SEQUENCE_PILFER, EVENT_PILFER_POINTERS);
}
- Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
- "LauncherInstrumentation.sendPointer: ACTION_UP");
break;
case MotionEvent.ACTION_POINTER_DOWN:
mPointerCount++;
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index 6387b05..3097d9c 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -116,8 +116,8 @@
}
/** Get widget with supplied text. */
- public Widget getWidget(String labelText) {
- return getWidget(labelText, null);
+ public Widget getWidget(CharSequence labelText) {
+ return getWidget(labelText.toString(), null);
}
/** Get widget with supplied text and app package */
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,