Merge "Invert the 2 stage LPNH logic to lower false positive triggers." into main
diff --git a/Android.bp b/Android.bp
index 13a926b..e358005 100644
--- a/Android.bp
+++ b/Android.bp
@@ -19,6 +19,17 @@
min_launcher3_sdk_version = "30"
+// Targets that don't inherit framework aconfig libs (i.e., those that don't set
+// `platform_apis: true`) must manually link them.
+java_defaults {
+ name: "launcher-non-platform-apis-defaults",
+ static_libs: [
+ "android.os.flags-aconfig-java",
+ "android.appwidget.flags-aconfig-java",
+ "com.android.window.flags.window-aconfig-java",
+ ]
+}
+
// Common source files used to build launcher (java and kotlin)
// All sources are split so they can be reused in many other libraries/apps in other folders
@@ -141,7 +152,6 @@
static_libs: [
"LauncherPluginLib",
"launcher_quickstep_log_protos_lite",
- "android.os.flags-aconfig-java",
"androidx-constraintlayout_constraintlayout",
"androidx.recyclerview_recyclerview",
"androidx.dynamicanimation_dynamicanimation",
@@ -163,8 +173,6 @@
"kotlinx_coroutines",
"com_android_launcher3_flags_lib",
"com_android_wm_shell_flags_lib",
- "android.appwidget.flags-aconfig-java",
- "com.android.window.flags.window-aconfig-java",
],
manifest: "AndroidManifest-common.xml",
sdk_version: "current",
@@ -179,6 +187,7 @@
//
android_app {
name: "Launcher3",
+ defaults: ["launcher-non-platform-apis-defaults"],
static_libs: [
"Launcher3ResLib",
@@ -190,7 +199,7 @@
],
optimize: {
- proguard_flags_files: ["proguard.flags"],
+ proguard_flags_files: [":launcher-proguard-rules"],
// Proguard is disable for testing. Derivarive prjects to keep proguard enabled
enabled: false,
},
@@ -302,7 +311,9 @@
static_libs: ["Launcher3QuickStepLib"],
optimize: {
- enabled: false,
+ proguard_flags_files: [":launcher-proguard-rules"],
+ enabled: true,
+ shrink_resources: true,
},
platform_apis: true,
@@ -349,6 +360,7 @@
optimize: {
proguard_flags_files: ["proguard.flags"],
enabled: true,
+ shrink_resources: true,
},
privileged: true,
@@ -385,6 +397,7 @@
optimize: {
proguard_flags_files: ["proguard.flags"],
enabled: true,
+ shrink_resources: true,
},
privileged: true,
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 88804b7..21b9863 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -310,3 +310,9 @@
bug: "341795751"
}
+flag {
+ name: "enable_new_archiving_icon"
+ namespace: "launcher"
+ description: "Archived apps will use new icon in app title"
+ bug: "350758155"
+}
diff --git a/go/quickstep/res/values-fr-rCA/strings.xml b/go/quickstep/res/values-fr-rCA/strings.xml
index 2cc9d8f..e48faeb 100644
--- a/go/quickstep/res/values-fr-rCA/strings.xml
+++ b/go/quickstep/res/values-fr-rCA/strings.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_share_drop_target_label" msgid="5804774105974539508">"Partager application"</string>
+ <string name="app_share_drop_target_label" msgid="5804774105974539508">"Partager appli"</string>
<string name="action_listen" msgid="2370304050784689486">"Écouter"</string>
<string name="action_translate" msgid="8028378961867277746">"Traduire"</string>
<string name="action_search" msgid="6269564710943755464">"Lentille"</string>
@@ -9,12 +9,12 @@
<string name="dialog_cancel" msgid="6464336969134856366">"ANNULER"</string>
<string name="dialog_settings" msgid="6564397136021186148">"PARAMÈTRES"</string>
<string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Traduire ou écouter le texte à l\'écran"</string>
- <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Des renseignements comme du texte sur votre écran, des adresses Web et des captures d\'écran peuvent être partagés avec Google.\n\nPour modifier les renseignements que vous partagez, accédez à "<b>"Paramètres > Applications > Applications par défaut > Application d\'assistant numérique"</b>"."</string>
+ <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Des renseignements comme du texte sur votre écran, des adresses Web et des captures d\'écran peuvent être partagés avec Google.\n\nPour modifier les renseignements que vous partagez, accédez à "<b>"Paramètres > Applis > Applis par défaut > Appli d\'assistant numérique"</b>"."</string>
<string name="assistant_not_selected_title" msgid="5017072974603345228">"Choisir un assistant pour utiliser cette fonctionnalité"</string>
- <string name="assistant_not_selected_text" msgid="3244613673884359276">"Pour écouter ou traduire le texte affiché sur votre écran, choisissez l\'application d\'un assistant numérique dans les paramètres"</string>
+ <string name="assistant_not_selected_text" msgid="3244613673884359276">"Pour écouter ou traduire le texte affiché sur votre écran, choisissez l\'appli d\'un assistant numérique dans les paramètres"</string>
<string name="assistant_not_supported_title" msgid="1675788067597484142">"Modifier votre assistant pour utiliser cette fonctionnalité"</string>
- <string name="assistant_not_supported_text" msgid="1708031078549268884">"Pour écouter ou traduire le texte affiché sur votre écran, modifiez l\'application de votre assistant numérique dans les paramètres"</string>
+ <string name="assistant_not_supported_text" msgid="1708031078549268884">"Pour écouter ou traduire le texte affiché sur votre écran, modifiez l\'appli de votre assistant numérique dans les paramètres"</string>
<string name="tooltip_listen" msgid="7634466447860989102">"Touchez ce bouton pour écouter le texte affiché sur cet écran"</string>
<string name="tooltip_translate" msgid="4184845868901542567">"Touchez ce bouton pour traduire le texte affiché sur cet écran"</string>
- <string name="toast_p2p_app_not_shareable" msgid="7229739094132131536">"Cette application ne peut pas être partagée"</string>
+ <string name="toast_p2p_app_not_shareable" msgid="7229739094132131536">"Cette appli ne peut pas être partagée"</string>
</resources>
diff --git a/quickstep/res/layout/customizable_taskbar.xml b/quickstep/res/layout/customizable_taskbar.xml
new file mode 100644
index 0000000..e1a80ae
--- /dev/null
+++ b/quickstep/res/layout/customizable_taskbar.xml
@@ -0,0 +1,121 @@
+<?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.
+-->
+<com.android.launcher3.taskbar.TaskbarDragLayer
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/taskbar_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:clipChildren="false">
+
+ <!-- TODO(b/349885828) : to be removed in future cl. -->
+ <com.android.launcher3.taskbar.TaskbarView
+ android:id="@+id/taskbar_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:focusable="true"
+ android:importantForAccessibility="yes"
+ android:forceHasOverlappingRendering="false"
+ android:layout_gravity="bottom"
+ android:layout_marginBottom="@dimen/transient_taskbar_bottom_margin"
+ android:clipChildren="false" />
+
+ <com.android.launcher3.taskbar.customization.CustomizableTaskbarView
+ android:id="@+id/customizable_taskbar_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:focusable="true"
+ android:importantForAccessibility="yes"
+ android:forceHasOverlappingRendering="false"
+ android:layout_gravity="bottom"
+ android:layout_marginBottom="@dimen/transient_taskbar_bottom_margin"
+ android:clipChildren="false" />
+
+ <com.android.launcher3.taskbar.TaskbarScrimView
+ android:id="@+id/taskbar_scrim"
+ 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"
+ />
+
+ <com.android.launcher3.taskbar.navbutton.NearestTouchFrame
+ android:id="@+id/navbuttons_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom" >
+
+ <FrameLayout
+ android:id="@+id/start_contextual_buttons"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:paddingStart="@dimen/taskbar_contextual_button_padding"
+ android:paddingEnd="@dimen/taskbar_contextual_button_padding"
+ android:paddingTop="@dimen/taskbar_contextual_padding_top"
+ android:gravity="center_vertical"
+ android:layout_gravity="start"/>
+
+ <LinearLayout
+ android:id="@+id/end_nav_buttons"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:gravity="center_vertical"
+ android:layout_gravity="end"/>
+
+ <FrameLayout
+ android:id="@+id/end_contextual_buttons"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:paddingTop="@dimen/taskbar_contextual_padding_top"
+ android:gravity="center_vertical"
+ android:layout_gravity="end"/>
+ </com.android.launcher3.taskbar.navbutton.NearestTouchFrame>
+
+ <com.android.launcher3.taskbar.StashedHandleView
+ android:id="@+id/stashed_handle"
+ tools:comment1="The actual size and shape will be set as a ViewOutlineProvider at runtime"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@color/taskbar_stashed_handle_dark_color"
+ android:clipToOutline="true"
+ android:layout_gravity="bottom"/>
+
+ <com.android.launcher3.taskbar.StashedHandleView
+ android:id="@+id/stashed_bubble_handle"
+ tools:comment1="The actual size and shape will be set as a ViewOutlineProvider at runtime"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:background="@color/taskbar_stashed_handle_dark_color"
+ android:clipToOutline="true"
+ android:layout_gravity="bottom"/>
+
+</com.android.launcher3.taskbar.TaskbarDragLayer>
\ No newline at end of file
diff --git a/quickstep/res/layout/customizable_taskbar_view.xml b/quickstep/res/layout/customizable_taskbar_view.xml
new file mode 100644
index 0000000..f55e1d0
--- /dev/null
+++ b/quickstep/res/layout/customizable_taskbar_view.xml
@@ -0,0 +1,56 @@
+<?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.
+-->
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <com.android.launcher3.taskbar.customization.TaskbarAllAppsButtonContainer
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintLeft_toLeftOf="@id/guideline1"/>
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/guideline1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ app:layout_constraintGuide_percent="0.1" />
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/guideline2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ app:layout_constraintGuide_percent="0.5" />
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/guideline3"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ app:layout_constraintGuide_percent="0.7" />
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/guideline4"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ app:layout_constraintGuide_percent="0.9" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index cc3b30e..46c1332 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -28,7 +28,7 @@
launcher:focusBorderColor="?androidprv:attr/materialColorOutline"
launcher:hoverBorderColor="?androidprv:attr/materialColorPrimary">
- <include layout="@layout/task_thumbnail" />
+ <include layout="@layout/task_thumbnail_deprecated" />
<!-- Filtering affects only alpha instead of the visibility since visibility can be altered
separately through RecentsView#resetFromSplitSelectionState() -->
diff --git a/quickstep/res/layout/task_grouped.xml b/quickstep/res/layout/task_grouped.xml
index 87a0f70..708aa3c 100644
--- a/quickstep/res/layout/task_grouped.xml
+++ b/quickstep/res/layout/task_grouped.xml
@@ -33,9 +33,9 @@
launcher:focusBorderColor="?androidprv:attr/materialColorOutline"
launcher:hoverBorderColor="?androidprv:attr/materialColorPrimary">
- <include layout="@layout/task_thumbnail"/>
+ <include layout="@layout/task_thumbnail_deprecated"/>
- <include layout="@layout/task_thumbnail"
+ <include layout="@layout/task_thumbnail_deprecated"
android:id="@+id/bottomright_snapshot" />
<!-- Filtering affects only alpha instead of the visibility since visibility can be altered
diff --git a/quickstep/res/layout/task_thumbnail.xml b/quickstep/res/layout/task_thumbnail.xml
index f1a3d62..34640e6 100644
--- a/quickstep/res/layout/task_thumbnail.xml
+++ b/quickstep/res/layout/task_thumbnail.xml
@@ -13,8 +13,29 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.quickstep.views.TaskThumbnailViewDeprecated
+<com.android.quickstep.task.thumbnail.TaskThumbnailView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/snapshot"
android:layout_width="match_parent"
- android:layout_height="match_parent" />
\ No newline at end of file
+ android:layout_height="match_parent">
+
+ <ImageView
+ android:id="@+id/task_thumbnail"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:importantForAccessibility="no"
+ android:visibility="gone"/>
+
+ <com.android.quickstep.task.thumbnail.LiveTileView
+ android:id="@+id/task_thumbnail_live_tile"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone"/>
+
+ <View
+ android:id="@+id/task_thumbnail_scrim"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/overview_foreground_scrim_color"
+ android:alpha="0" />
+
+</com.android.quickstep.task.thumbnail.TaskThumbnailView>
\ No newline at end of file
diff --git a/quickstep/res/layout/task_thumbnail_deprecated.xml b/quickstep/res/layout/task_thumbnail_deprecated.xml
new file mode 100644
index 0000000..f1a3d62
--- /dev/null
+++ b/quickstep/res/layout/task_thumbnail_deprecated.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+<com.android.quickstep.views.TaskThumbnailViewDeprecated
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/snapshot"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
\ No newline at end of file
diff --git a/quickstep/res/raw-h480dp/all_set_page_bg.json b/quickstep/res/raw-h480dp/all_set_page_bg.json
deleted file mode 100644
index f2998a0..0000000
--- a/quickstep/res/raw-h480dp/all_set_page_bg.json
+++ /dev/null
@@ -1 +0,0 @@
-{"v":"5.8.1","fr":60,"ip":0,"op":181,"w":701,"h":841,"nm":"SUW_WelcomeScreen_FoldableOpen_Portrait_Dynamic","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".primary","cl":"primary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":180,"s":[55]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.07,"y":0.949},"o":{"x":0.167,"y":0.167},"t":0,"s":[181.172,148.425,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.773,"y":0.038},"t":95,"s":[181.172,-68.377,0],"to":[0,0,0],"ti":[0,0,0]},{"t":180,"s":[181.172,148.425,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-3514.717,-358.642,0],"ix":1,"l":2},"s":{"a":0,"k":[21.6,21.6,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[100.594,-128.921],[118.85,-93.491],[148.984,-67.406],[148.684,-27.55],[163.244,9.552],[144.457,44.702],[140.106,84.321],[107.136,106.715],[84.872,139.773],[45.271,144.279],[10.194,163.205],[-26.964,148.792],[-66.818,149.249],[-93.023,119.218],[-128.524,101.101],[-137.771,62.332],[-160.786,29.793],[-150.957,-8.833],[-156.215,-48.341],[-129.561,-77.974],[-115.856,-115.401],[-78.484,-129.253],[-48.956,-156.023],[-9.427,-150.921],[29.159,-160.903],[61.789,-138.015]],"o":[[-100.594,128.921],[-118.85,93.491],[-148.984,67.406],[-148.684,27.55],[-163.244,-9.552],[-144.456,-44.702],[-140.106,-84.321],[-107.136,-106.715],[-84.872,-139.773],[-45.271,-144.279],[-10.194,-163.205],[26.964,-148.792],[66.818,-149.249],[93.023,-119.218],[128.524,-101.101],[137.771,-62.332],[160.786,-29.793],[150.957,8.833],[156.215,48.341],[129.561,77.974],[115.856,115.4],[78.484,129.253],[48.956,156.023],[9.427,150.921],[-29.159,160.903],[-61.789,138.015]],"v":[[975.226,761.299],[707.165,899.424],[509.8,1127.42],[208.253,1125.148],[-72.46,1235.308],[-338.41,1093.162],[-638.164,1060.25],[-807.592,810.792],[-1057.715,642.348],[-1091.808,342.727],[-1235.002,77.338],[-1125.948,-203.807],[-1129.407,-505.342],[-902.191,-703.604],[-765.123,-972.207],[-471.796,-1042.166],[-225.603,-1216.305],[66.637,-1141.935],[365.557,-1181.715],[589.761,-980.053],[872.928,-876.361],[977.734,-593.606],[1180.277,-370.197],[1141.675,-71.124],[1217.196,220.821],[1044.029,467.699]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[-3509.952,-363.731],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.713725490196,0.556862745098,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9.3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":181,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".tertiary","cl":"tertiary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.07],"y":[0.619]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[67]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[-0.263]},"t":95,"s":[82]},{"t":180,"s":[67]}],"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.07],"y":[0.927]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[458.803]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[-0.05]},"t":95,"s":[536.803]},{"t":180,"s":[458.803]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[707.143]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.069]},"t":95,"s":[639.643]},{"t":180,"s":[707.143]}],"ix":4}},"a":{"k":[{"s":[164.438,1433.781,0],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[164.438,1433.781,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"s":{"a":0,"k":[-30,30,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[2361.125,4541.989],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tr","p":{"a":0,"k":[164.438,1481.781],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.901960784314,0.764705882353,0.423529411765,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":181,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".black","cl":"black","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[350.5,420.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[420.5,-350.5],[-420.5,-350.5],[-420.5,350.5],[420.5,350.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":181,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/quickstep/res/raw-sw600dp-land/all_set_page_bg.json b/quickstep/res/raw-sw600dp-land/all_set_page_bg.json
deleted file mode 100644
index 63b64da..0000000
--- a/quickstep/res/raw-sw600dp-land/all_set_page_bg.json
+++ /dev/null
@@ -1 +0,0 @@
-{"v":"5.8.1","fr":60,"ip":0,"op":181,"w":841,"h":701,"nm":"SUW_WelcomeScreen_FoldableOpen_Dynamic","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".primary","cl":"primary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":180,"s":[55]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.07,"y":0.939},"o":{"x":0.167,"y":0.167},"t":0,"s":[140.975,228.318,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.773,"y":0.045},"t":95,"s":[140.975,47.65,0],"to":[0,0,0],"ti":[0,0,0]},{"t":180,"s":[140.975,228.318,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-3514.717,-358.642,0],"ix":1,"l":2},"s":{"a":0,"k":[18,18,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[100.594,-128.921],[118.85,-93.491],[148.984,-67.406],[148.684,-27.55],[163.244,9.552],[144.457,44.702],[140.106,84.321],[107.136,106.715],[84.872,139.773],[45.271,144.279],[10.194,163.205],[-26.964,148.792],[-66.818,149.249],[-93.023,119.218],[-128.524,101.101],[-137.771,62.332],[-160.786,29.793],[-150.957,-8.833],[-156.215,-48.341],[-129.561,-77.974],[-115.856,-115.401],[-78.484,-129.253],[-48.956,-156.023],[-9.427,-150.921],[29.159,-160.903],[61.789,-138.015]],"o":[[-100.594,128.921],[-118.85,93.491],[-148.984,67.406],[-148.684,27.55],[-163.244,-9.552],[-144.456,-44.702],[-140.106,-84.321],[-107.136,-106.715],[-84.872,-139.773],[-45.271,-144.279],[-10.194,-163.205],[26.964,-148.792],[66.818,-149.249],[93.023,-119.218],[128.524,-101.101],[137.771,-62.332],[160.786,-29.793],[150.957,8.833],[156.215,48.341],[129.561,77.974],[115.856,115.4],[78.484,129.253],[48.956,156.023],[9.427,150.921],[-29.159,160.903],[-61.789,138.015]],"v":[[975.226,761.299],[707.165,899.424],[509.8,1127.42],[208.253,1125.148],[-72.46,1235.308],[-338.41,1093.162],[-638.164,1060.25],[-807.592,810.792],[-1057.715,642.348],[-1091.808,342.727],[-1235.002,77.338],[-1125.948,-203.807],[-1129.407,-505.342],[-902.191,-703.604],[-765.123,-972.207],[-471.796,-1042.166],[-225.603,-1216.305],[66.637,-1141.935],[365.557,-1181.715],[589.761,-980.053],[872.928,-876.361],[977.734,-593.606],[1180.277,-370.197],[1141.675,-71.124],[1217.196,220.821],[1044.029,467.699]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[-3509.952,-363.731],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.713725490196,0.556862745098,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"k":[{"s":[11.111],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[11.111],"t":180,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":181,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".tertiary","cl":"tertiary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.07],"y":[0.619]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[67]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[-0.263]},"t":95,"s":[82]},{"t":180,"s":[67]}],"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.07],"y":[0.913]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[639]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[-0.06]},"t":95,"s":[704]},{"t":180,"s":[639]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.12]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[527.25]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.083]},"t":95,"s":[471]},{"t":180,"s":[527.25]}],"ix":4}},"a":{"k":[{"s":[164.438,1433.781,0],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[164.438,1433.781,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"s":{"a":0,"k":[-25,25,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[2361.125,4541.989],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tr","p":{"a":0,"k":[164.438,1481.781],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.901960784314,0.764705882353,0.423529411765,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"k":[{"s":[6],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[6],"t":180,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":181,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".black","cl":"black","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[420.5,350.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[420.5,-350.5],[-420.5,-350.5],[-420.5,350.5],[420.5,350.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":181,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/quickstep/res/raw-sw600dp/all_set_page_bg.json b/quickstep/res/raw-sw600dp/all_set_page_bg.json
deleted file mode 100644
index f2998a0..0000000
--- a/quickstep/res/raw-sw600dp/all_set_page_bg.json
+++ /dev/null
@@ -1 +0,0 @@
-{"v":"5.8.1","fr":60,"ip":0,"op":181,"w":701,"h":841,"nm":"SUW_WelcomeScreen_FoldableOpen_Portrait_Dynamic","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".primary","cl":"primary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":180,"s":[55]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.07,"y":0.949},"o":{"x":0.167,"y":0.167},"t":0,"s":[181.172,148.425,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.773,"y":0.038},"t":95,"s":[181.172,-68.377,0],"to":[0,0,0],"ti":[0,0,0]},{"t":180,"s":[181.172,148.425,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-3514.717,-358.642,0],"ix":1,"l":2},"s":{"a":0,"k":[21.6,21.6,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[100.594,-128.921],[118.85,-93.491],[148.984,-67.406],[148.684,-27.55],[163.244,9.552],[144.457,44.702],[140.106,84.321],[107.136,106.715],[84.872,139.773],[45.271,144.279],[10.194,163.205],[-26.964,148.792],[-66.818,149.249],[-93.023,119.218],[-128.524,101.101],[-137.771,62.332],[-160.786,29.793],[-150.957,-8.833],[-156.215,-48.341],[-129.561,-77.974],[-115.856,-115.401],[-78.484,-129.253],[-48.956,-156.023],[-9.427,-150.921],[29.159,-160.903],[61.789,-138.015]],"o":[[-100.594,128.921],[-118.85,93.491],[-148.984,67.406],[-148.684,27.55],[-163.244,-9.552],[-144.456,-44.702],[-140.106,-84.321],[-107.136,-106.715],[-84.872,-139.773],[-45.271,-144.279],[-10.194,-163.205],[26.964,-148.792],[66.818,-149.249],[93.023,-119.218],[128.524,-101.101],[137.771,-62.332],[160.786,-29.793],[150.957,8.833],[156.215,48.341],[129.561,77.974],[115.856,115.4],[78.484,129.253],[48.956,156.023],[9.427,150.921],[-29.159,160.903],[-61.789,138.015]],"v":[[975.226,761.299],[707.165,899.424],[509.8,1127.42],[208.253,1125.148],[-72.46,1235.308],[-338.41,1093.162],[-638.164,1060.25],[-807.592,810.792],[-1057.715,642.348],[-1091.808,342.727],[-1235.002,77.338],[-1125.948,-203.807],[-1129.407,-505.342],[-902.191,-703.604],[-765.123,-972.207],[-471.796,-1042.166],[-225.603,-1216.305],[66.637,-1141.935],[365.557,-1181.715],[589.761,-980.053],[872.928,-876.361],[977.734,-593.606],[1180.277,-370.197],[1141.675,-71.124],[1217.196,220.821],[1044.029,467.699]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[-3509.952,-363.731],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.713725490196,0.556862745098,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9.3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":181,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".tertiary","cl":"tertiary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.07],"y":[0.619]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[67]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[-0.263]},"t":95,"s":[82]},{"t":180,"s":[67]}],"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.07],"y":[0.927]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[458.803]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[-0.05]},"t":95,"s":[536.803]},{"t":180,"s":[458.803]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[707.143]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.069]},"t":95,"s":[639.643]},{"t":180,"s":[707.143]}],"ix":4}},"a":{"k":[{"s":[164.438,1433.781,0],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[164.438,1433.781,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"s":{"a":0,"k":[-30,30,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[2361.125,4541.989],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tr","p":{"a":0,"k":[164.438,1481.781],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.901960784314,0.764705882353,0.423529411765,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":181,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".black","cl":"black","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[350.5,420.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[420.5,-350.5],[-420.5,-350.5],[-420.5,350.5],[420.5,350.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":181,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/quickstep/res/raw-sw720dp-land/all_set_page_bg.json b/quickstep/res/raw-sw720dp-land/all_set_page_bg.json
deleted file mode 100644
index a994b0f..0000000
--- a/quickstep/res/raw-sw720dp-land/all_set_page_bg.json
+++ /dev/null
@@ -1 +0,0 @@
-{"v":"5.8.1","fr":60,"ip":0,"op":180,"w":1280,"h":800,"nm":"SUW_WelcomeScreen_Tablet_Landscape_Dynamic","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[288,540,0],"ix":2,"l":2},"a":{"a":0,"k":[50,50,0],"ix":1,"l":2},"s":{"a":0,"k":[25,25,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".primary","cl":"primary","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":180,"s":[56]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.07,"y":0.986},"o":{"x":0.167,"y":0.167},"t":0,"s":[375.832,-1006.545,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.773,"y":0.01},"t":95,"s":[375.832,-1811,0],"to":[0,0,0],"ti":[0,0,0]},{"t":180,"s":[375.832,-1006.545,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-3514.717,-358.642,0],"ix":1,"l":2},"s":{"a":0,"k":[110,110,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[75.615,-96.908],[89.338,-70.276],[111.99,-50.668],[111.764,-20.709],[122.709,7.18],[108.586,33.602],[105.316,63.383],[80.533,80.216],[63.797,105.066],[34.03,108.453],[7.663,122.679],[-20.269,111.845],[-50.226,112.189],[-69.924,89.614],[-96.61,75.997],[-103.56,46.854],[-120.861,22.395],[-113.472,-6.639],[-117.425,-36.337],[-97.389,-58.612],[-87.087,-86.745],[-58.996,-97.158],[-36.8,-117.281],[-7.086,-113.445],[21.918,-120.948],[46.446,-103.744]],"o":[[-75.615,96.909],[-89.338,70.276],[-111.99,50.668],[-111.764,20.709],[-122.709,-7.18],[-108.586,-33.602],[-105.316,-63.383],[-80.533,-80.216],[-63.797,-105.066],[-34.03,-108.453],[-7.663,-122.679],[20.269,-111.845],[50.226,-112.188],[69.924,-89.614],[96.61,-75.997],[103.56,-46.854],[120.861,-22.395],[113.472,6.64],[117.425,36.337],[97.389,58.612],[87.088,86.745],[58.995,97.158],[36.8,117.281],[7.087,113.445],[-21.918,120.948],[-46.446,103.744]],"v":[[733.209,572.105],[531.711,675.932],[383.354,847.313],[156.685,845.606],[-54.323,928.412],[-254.235,821.562],[-479.555,796.823],[-606.913,609.309],[-794.927,482.691],[-820.554,257.47],[-928.191,57.981],[-846.217,-153.353],[-848.817,-380.013],[-678.021,-529.044],[-574.99,-730.949],[-354.499,-783.537],[-169.439,-914.435],[50.234,-858.532],[274.928,-888.434],[443.46,-736.847],[656.313,-658.903],[735.094,-446.359],[887.344,-278.426],[858.327,-53.616],[915.095,165.835],[784.928,351.409]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.956862745098,0.729411764706,0.619607843137,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-3509.952,-363.731],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":720,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".tertiary","cl":"tertiary","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.248]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[57]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.172]},"t":95,"s":[75]},{"t":180,"s":[57]}],"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.032]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[2618]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.022]},"t":95,"s":[2442]},{"t":180,"s":[2618]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.034]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[891]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.024]},"t":95,"s":[694]},{"t":180,"s":[891]}],"ix":4}},"a":{"a":0,"k":[164.438,1433.781,0],"ix":1,"l":2},"s":{"a":0,"k":[120,120,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[3079.125,4685.989],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.752941176471,0.788235294118,0.752941176471,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[164.438,1481.781],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/quickstep/res/raw-sw720dp/all_set_page_bg.json b/quickstep/res/raw-sw720dp/all_set_page_bg.json
deleted file mode 100644
index 1030ffa..0000000
--- a/quickstep/res/raw-sw720dp/all_set_page_bg.json
+++ /dev/null
@@ -1 +0,0 @@
-{"v":"5.8.1","fr":60,"ip":0,"op":180,"w":800,"h":1280,"nm":"SUW_WelcomeScreen_Tablet_Portrait_Dynamic","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[288,528,0],"ix":2,"l":2},"a":{"a":0,"k":[50,50,0],"ix":1,"l":2},"s":{"a":0,"k":[25,25,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".primary","cl":"primary","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":180,"s":[56]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.07,"y":0.986},"o":{"x":0.167,"y":0.167},"t":0,"s":[999.832,-2238.545,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.773,"y":0.01},"t":95,"s":[999.832,-3043,0],"to":[0,0,0],"ti":[0,0,0]},{"t":180,"s":[999.832,-2238.545,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-3514.717,-358.642,0],"ix":1,"l":2},"s":{"a":0,"k":[200,200,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[75.615,-96.908],[89.338,-70.276],[111.99,-50.668],[111.764,-20.709],[122.709,7.18],[108.586,33.602],[105.316,63.383],[80.533,80.216],[63.797,105.066],[34.03,108.453],[7.663,122.679],[-20.269,111.845],[-50.226,112.189],[-69.924,89.614],[-96.61,75.997],[-103.56,46.854],[-120.861,22.395],[-113.472,-6.639],[-117.425,-36.337],[-97.389,-58.612],[-87.087,-86.745],[-58.996,-97.158],[-36.8,-117.281],[-7.086,-113.445],[21.918,-120.948],[46.446,-103.744]],"o":[[-75.615,96.909],[-89.338,70.276],[-111.99,50.668],[-111.764,20.709],[-122.709,-7.18],[-108.586,-33.602],[-105.316,-63.383],[-80.533,-80.216],[-63.797,-105.066],[-34.03,-108.453],[-7.663,-122.679],[20.269,-111.845],[50.226,-112.188],[69.924,-89.614],[96.61,-75.997],[103.56,-46.854],[120.861,-22.395],[113.472,6.64],[117.425,36.337],[97.389,58.612],[87.088,86.745],[58.995,97.158],[36.8,117.281],[7.087,113.445],[-21.918,120.948],[-46.446,103.744]],"v":[[733.209,572.105],[531.711,675.932],[383.354,847.313],[156.685,845.606],[-54.323,928.412],[-254.235,821.562],[-479.555,796.823],[-606.913,609.309],[-794.927,482.691],[-820.554,257.47],[-928.191,57.981],[-846.217,-153.353],[-848.817,-380.013],[-678.021,-529.044],[-574.99,-730.949],[-354.499,-783.537],[-169.439,-914.435],[50.234,-858.532],[274.928,-888.434],[443.46,-736.847],[656.313,-658.903],[735.094,-446.359],[887.344,-278.426],[858.327,-53.616],[915.095,165.835],[784.928,351.409]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.956862745098,0.729411764706,0.619607843137,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-3509.952,-363.731],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":720,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".tertiary","cl":"tertiary","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.248]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[-39]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.172]},"t":95,"s":[-21]},{"t":180,"s":[-39]}],"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.032]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[1490]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.022]},"t":95,"s":[1314]},{"t":180,"s":[1490]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.034]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[2967]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.024]},"t":95,"s":[2770]},{"t":180,"s":[2967]}],"ix":4}},"a":{"a":0,"k":[164.438,1433.781,0],"ix":1,"l":2},"s":{"a":0,"k":[168,168,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[3079.125,4685.989],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.752941176471,0.788235294118,0.752941176471,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[164.438,1481.781],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/quickstep/res/raw-w600dp-h900dp/all_set_page_bg.json b/quickstep/res/raw-w600dp-h900dp/all_set_page_bg.json
new file mode 100644
index 0000000..b1a3bbe
--- /dev/null
+++ b/quickstep/res/raw-w600dp-h900dp/all_set_page_bg.json
@@ -0,0 +1 @@
+{"v":"5.8.1","fr":60,"ip":0,"op":180,"w":800,"h":1280,"nm":"SUW_WelcomeScreen_Portrait_Dynamic","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[288,528,0],"ix":2,"l":2},"a":{"a":0,"k":[50,50,0],"ix":1,"l":2},"s":{"a":0,"k":[25,25,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".primary","cl":"primary","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":180,"s":[56]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.07,"y":0.986},"o":{"x":0.167,"y":0.167},"t":0,"s":[999.832,-2238.545,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.773,"y":0.01},"t":95,"s":[999.832,-3043,0],"to":[0,0,0],"ti":[0,0,0]},{"t":180,"s":[999.832,-2238.545,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-3514.717,-358.642,0],"ix":1,"l":2},"s":{"a":0,"k":[200,200,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[75.615,-96.908],[89.338,-70.276],[111.99,-50.668],[111.764,-20.709],[122.709,7.18],[108.586,33.602],[105.316,63.383],[80.533,80.216],[63.797,105.066],[34.03,108.453],[7.663,122.679],[-20.269,111.845],[-50.226,112.189],[-69.924,89.614],[-96.61,75.997],[-103.56,46.854],[-120.861,22.395],[-113.472,-6.639],[-117.425,-36.337],[-97.389,-58.612],[-87.087,-86.745],[-58.996,-97.158],[-36.8,-117.281],[-7.086,-113.445],[21.918,-120.948],[46.446,-103.744]],"o":[[-75.615,96.909],[-89.338,70.276],[-111.99,50.668],[-111.764,20.709],[-122.709,-7.18],[-108.586,-33.602],[-105.316,-63.383],[-80.533,-80.216],[-63.797,-105.066],[-34.03,-108.453],[-7.663,-122.679],[20.269,-111.845],[50.226,-112.188],[69.924,-89.614],[96.61,-75.997],[103.56,-46.854],[120.861,-22.395],[113.472,6.64],[117.425,36.337],[97.389,58.612],[87.088,86.745],[58.995,97.158],[36.8,117.281],[7.087,113.445],[-21.918,120.948],[-46.446,103.744]],"v":[[733.209,572.105],[531.711,675.932],[383.354,847.313],[156.685,845.606],[-54.323,928.412],[-254.235,821.562],[-479.555,796.823],[-606.913,609.309],[-794.927,482.691],[-820.554,257.47],[-928.191,57.981],[-846.217,-153.353],[-848.817,-380.013],[-678.021,-529.044],[-574.99,-730.949],[-354.499,-783.537],[-169.439,-914.435],[50.234,-858.532],[274.928,-888.434],[443.46,-736.847],[656.313,-658.903],[735.094,-446.359],[887.344,-278.426],[858.327,-53.616],[915.095,165.835],[784.928,351.409]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.956862745098,0.729411764706,0.619607843137,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-3509.952,-363.731],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":720,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".tertiary","cl":"tertiary","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.248]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[-39]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.172]},"t":95,"s":[-21]},{"t":180,"s":[-39]}],"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.032]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[1490]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.022]},"t":95,"s":[1314]},{"t":180,"s":[1490]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.034]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[2967]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.024]},"t":95,"s":[2770]},{"t":180,"s":[2967]}],"ix":4}},"a":{"a":0,"k":[164.438,1433.781,0],"ix":1,"l":2},"s":{"a":0,"k":[168,168,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[3079.125,4685.989],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.752941176471,0.788235294118,0.752941176471,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[164.438,1481.781],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/quickstep/res/raw-w840dp-h480dp/all_set_page_bg.json b/quickstep/res/raw-w840dp-h480dp/all_set_page_bg.json
index ae1b560..81de7a2 100644
--- a/quickstep/res/raw-w840dp-h480dp/all_set_page_bg.json
+++ b/quickstep/res/raw-w840dp-h480dp/all_set_page_bg.json
@@ -1 +1 @@
-{"v":"5.8.1","fr":60,"ip":0,"op":181,"w":841,"h":701,"nm":"SUW_WelcomeScreen_FelixOpen_Dynamic","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".primary","cl":"primary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":180,"s":[55]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.07,"y":0.939},"o":{"x":0.167,"y":0.167},"t":0,"s":[140.975,228.318,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.773,"y":0.045},"t":95,"s":[140.975,47.65,0],"to":[0,0,0],"ti":[0,0,0]},{"t":180,"s":[140.975,228.318,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-3514.717,-358.642,0],"ix":1,"l":2},"s":{"a":0,"k":[18,18,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[100.594,-128.921],[118.85,-93.491],[148.984,-67.406],[148.684,-27.55],[163.244,9.552],[144.457,44.702],[140.106,84.321],[107.136,106.715],[84.872,139.773],[45.271,144.279],[10.194,163.205],[-26.964,148.792],[-66.818,149.249],[-93.023,119.218],[-128.524,101.101],[-137.771,62.332],[-160.786,29.793],[-150.957,-8.833],[-156.215,-48.341],[-129.561,-77.974],[-115.856,-115.401],[-78.484,-129.253],[-48.956,-156.023],[-9.427,-150.921],[29.159,-160.903],[61.789,-138.015]],"o":[[-100.594,128.921],[-118.85,93.491],[-148.984,67.406],[-148.684,27.55],[-163.244,-9.552],[-144.456,-44.702],[-140.106,-84.321],[-107.136,-106.715],[-84.872,-139.773],[-45.271,-144.279],[-10.194,-163.205],[26.964,-148.792],[66.818,-149.249],[93.023,-119.218],[128.524,-101.101],[137.771,-62.332],[160.786,-29.793],[150.957,8.833],[156.215,48.341],[129.561,77.974],[115.856,115.4],[78.484,129.253],[48.956,156.023],[9.427,150.921],[-29.159,160.903],[-61.789,138.015]],"v":[[975.226,761.299],[707.165,899.424],[509.8,1127.42],[208.253,1125.148],[-72.46,1235.308],[-338.41,1093.162],[-638.164,1060.25],[-807.592,810.792],[-1057.715,642.348],[-1091.808,342.727],[-1235.002,77.338],[-1125.948,-203.807],[-1129.407,-505.342],[-902.191,-703.604],[-765.123,-972.207],[-471.796,-1042.166],[-225.603,-1216.305],[66.637,-1141.935],[365.557,-1181.715],[589.761,-980.053],[872.928,-876.361],[977.734,-593.606],[1180.277,-370.197],[1141.675,-71.124],[1217.196,220.821],[1044.029,467.699]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[-3509.952,-363.731],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.713725490196,0.556862745098,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"k":[{"s":[11.111],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[11.111],"t":180,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":181,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".tertiary","cl":"tertiary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.07],"y":[0.619]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[67]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[-0.263]},"t":95,"s":[82]},{"t":180,"s":[67]}],"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.07],"y":[0.913]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[639]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[-0.06]},"t":95,"s":[704]},{"t":180,"s":[639]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.12]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[527.25]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.083]},"t":95,"s":[471]},{"t":180,"s":[527.25]}],"ix":4}},"a":{"k":[{"s":[164.438,1433.781,0],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[164.438,1433.781,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"s":{"a":0,"k":[-25,25,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[2361.125,4541.989],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tr","p":{"a":0,"k":[164.438,1481.781],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.901960784314,0.764705882353,0.423529411765,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"k":[{"s":[6],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[6],"t":180,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":181,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".black","cl":"black","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[420.5,350.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[420.5,-350.5],[-420.5,-350.5],[-420.5,350.5],[420.5,350.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":181,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
+{"v":"5.8.1","fr":60,"ip":0,"op":180,"w":1280,"h":800,"nm":"SUW_WelcomeScreen_Landscape_Dynamic","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[288,540,0],"ix":2,"l":2},"a":{"a":0,"k":[50,50,0],"ix":1,"l":2},"s":{"a":0,"k":[25,25,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".primary","cl":"primary","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":180,"s":[56]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.07,"y":0.986},"o":{"x":0.167,"y":0.167},"t":0,"s":[375.832,-1006.545,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.773,"y":0.01},"t":95, "s":[375.832,-1811,0],"to":[0,0,0],"ti":[0,0,0]},{"t":180,"s":[375.832,-1006.545,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-3514.717,-358.642,0],"ix":1,"l":2},"s":{"a":0,"k":[110,110,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[75.615,-96.908],[89.338,-70.276],[111.99,-50.668],[111.764,-20.709],[122.709,7.18],[108.586,33.602],[105.316,63.383],[80.533,80.216],[63.797,105.066],[34.03,108.453],[7.663,122.679],[-20.269,111.845],[-50.226,112.189],[-69.924,89.614],[-96.61,75.997],[-103.56,46.854],[-120.861,22.395],[-113.472,-6.639],[-117.425,-36.337],[-97.389,-58.612],[-87.087,-86.745],[-58.996,-97.158],[-36.8,-117.281],[-7.086,-113.445],[21.918,-120.948],[46.446,-103.744]],"o":[[-75.615,96.909],[-89.338,70.276],[-111.99,50.668],[-111.764,20.709],[-122.709,-7.18],[-108.586,-33.602],[-105.316,-63.383],[-80.533,-80.216],[-63.797,-105.066],[-34.03,-108.453],[-7.663,-122.679],[20.269,-111.845],[50.226,-112.188],[69.924,-89.614],[96.61,-75.997],[103.56,-46.854],[120.861,-22.395],[113.472,6.64],[117.425,36.337],[97.389,58.612],[87.088,86.745],[58.995,97.158],[36.8,117.281],[7.087,113.445],[-21.918,120.948],[-46.446,103.744]],"v":[[733.209,572.105],[531.711,675.932],[383.354,847.313],[156.685,845.606],[-54.323,928.412],[-254.235,821.562],[-479.555,796.823],[-606.913,609.309],[-794.927,482.691],[-820.554,257.47],[-928.191,57.981],[-846.217,-153.353],[-848.817,-380.013],[-678.021,-529.044],[-574.99,-730.949],[-354.499,-783.537],[-169.439,-914.435],[50.234,-858.532],[274.928,-888.434],[443.46,-736.847],[656.313,-658.903],[735.094,-446.359],[887.344,-278.426],[858.327,-53.616],[915.095,165.835],[784.928,351.409]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.956862745098,0.729411764706,0.619607843137,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-3509.952,-363.731],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":720,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".tertiary","cl":"tertiary","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.248]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[57]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.172]},"t":95,"s":[75]},{"t":180,"s":[57]}],"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.032]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[2618]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.022]},"t":95,"s":[2442]},{"t":180,"s":[2618]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.034]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[891]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.024]},"t":95,"s":[694]},{"t":180,"s":[891]}],"ix":4}},"a":{"a":0,"k":[164.438,1433.781,0],"ix":1,"l":2},"s":{"a":0,"k":[120,120,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[3079.125,4685.989],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.752941176471,0.788235294118,0.752941176471,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[164.438,1481.781],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/quickstep/res/raw-land/all_set_page_bg.json b/quickstep/res/raw-w840dp/all_set_page_bg.json
similarity index 100%
rename from quickstep/res/raw-land/all_set_page_bg.json
rename to quickstep/res/raw-w840dp/all_set_page_bg.json
diff --git a/quickstep/res/values-fr-rCA/strings.xml b/quickstep/res/values-fr-rCA/strings.xml
index edfb59e..9510494 100644
--- a/quickstep/res/values-fr-rCA/strings.xml
+++ b/quickstep/res/values-fr-rCA/strings.xml
@@ -23,34 +23,34 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Forme libre"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"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\'application"</string>
+ <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Paramètres d\'utilisation de l\'appli"</string>
<string name="recents_clear_all" msgid="5328176793634888831">"Tout effacer"</string>
- <string name="accessibility_recent_apps" msgid="4058661986695117371">"Applications récentes"</string>
+ <string name="accessibility_recent_apps" msgid="4058661986695117371">"Applis récentes"</string>
<string name="task_view_closed" msgid="9170038230110856166">"Tâche fermée"</string>
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g> : <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 min"</string>
<string name="time_left_for_app" msgid="3111996412933644358">"Il reste <xliff:g id="TIME">%1$s</xliff:g> aujourd\'hui"</string>
- <string name="title_app_suggestions" msgid="4185902664111965088">"Suggestions d\'applications"</string>
- <string name="all_apps_prediction_tip" msgid="2672336544844936186">"Vos prédictions d\'applications"</string>
- <string name="hotseat_edu_title_migrate" msgid="306578144424489980">"Obtenir des suggestions d\'applications dans la rangée du bas de votre écran d\'accueil"</string>
- <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Retrouvez des suggestions d\'applications dans la rangée des favoris de votre écran d\'accueil"</string>
- <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Accédez facilement aux applications que vous utilisez le plus, directement à l\'écran d\'accueil. Les suggestions changeront en fonction de vos habitudes. Les applications dans la rangée du bas seront déplacées vers votre écran d\'accueil."</string>
- <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Accédez facilement aux applications que vous utilisez le plus, directement à l\'écran d\'accueil. Les suggestions changeront en fonction de vos habitudes. Les applications dans la rangée des favoris seront déplacées vers votre écran d\'accueil."</string>
- <string name="hotseat_edu_accept" msgid="1611544083278999837">"Obtenir des suggestions d\'applications"</string>
+ <string name="title_app_suggestions" msgid="4185902664111965088">"Suggestions d\'applis"</string>
+ <string name="all_apps_prediction_tip" msgid="2672336544844936186">"Vos prédictions d\'applis"</string>
+ <string name="hotseat_edu_title_migrate" msgid="306578144424489980">"Obtenir des suggestions d\'applis dans la rangée du bas de votre écran d\'accueil"</string>
+ <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Retrouvez des suggestions d\'applis dans la rangée des favoris de votre écran d\'accueil"</string>
+ <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Accédez facilement aux applis que vous utilisez le plus, directement à l\'écran d\'accueil. Les suggestions changeront en fonction de vos habitudes. Les applis dans la rangée du bas seront déplacées vers votre écran d\'accueil."</string>
+ <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Accédez facilement aux applis que vous utilisez le plus, directement à l\'écran d\'accueil. Les suggestions changeront en fonction de vos habitudes. Les applis dans la rangée des favoris seront déplacées vers votre écran d\'accueil."</string>
+ <string name="hotseat_edu_accept" msgid="1611544083278999837">"Obtenir des suggestions d\'applis"</string>
<string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Non merci"</string>
<string name="hotseat_prediction_settings" msgid="6246554993566070818">"Paramètres"</string>
- <string name="hotseat_auto_enrolled" msgid="522100018967146807">"Les applications les plus utilisées s\'affichent ici et changent en fonction des habitudes"</string>
- <string name="hotseat_tip_no_empty_slots" msgid="1325212677738179185">"Faites glisser des applications hors de la rangée du bas pour obtenir des suggestions d\'applications"</string>
- <string name="hotseat_tip_gaps_filled" msgid="3035673010274223538">"Applications suggérées ajoutées à l\'espace vide"</string>
- <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Les suggestions d\'applications sont activées"</string>
- <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Les suggestions d\'applications sont désactivées"</string>
- <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Application prédite : <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+ <string name="hotseat_auto_enrolled" msgid="522100018967146807">"Les applis les plus utilisées s\'affichent ici et changent en fonction des habitudes"</string>
+ <string name="hotseat_tip_no_empty_slots" msgid="1325212677738179185">"Faites glisser des applis hors de la rangée du bas pour obtenir des suggestions d\'applis"</string>
+ <string name="hotseat_tip_gaps_filled" msgid="3035673010274223538">"Applis suggérées ajoutées à l\'espace vide"</string>
+ <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Les suggestions d\'applis sont activées"</string>
+ <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Les suggestions d\'applis sont désactivées"</string>
+ <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Appli prédite : <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Faites pivoter votre appareil"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Veuillez faire pivoter votre appareil pour terminer le tutoriel de navigation par gestes"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Assurez-vous de balayer l\'écran à partir de l\'extrémité droite ou gauche"</string>
<string name="back_gesture_feedback_cancelled" msgid="762621530959111290">"Assurez-vous de balayer l\'écran à partir de l\'extrémité droite ou gauche vers le centre, puis allons-y"</string>
<string name="back_gesture_feedback_complete_with_overview_follow_up" msgid="9176400654037014471">"Vous avez appris à balayer de la droite pour revenir en arrière. Apprenez comment changer d\'appli."</string>
- <string name="back_gesture_feedback_complete_with_follow_up" msgid="8653374779579748392">"Vous avez appris le geste de retour en arrière. Maintenant, apprenez comment changer d\'application."</string>
+ <string name="back_gesture_feedback_complete_with_follow_up" msgid="8653374779579748392">"Vous avez appris le geste de retour en arrière. Maintenant, apprenez comment changer d\'appli."</string>
<string name="back_gesture_feedback_complete_without_follow_up" msgid="197189945858268342">"Vous avez appris le geste de retour en arrière"</string>
<string name="back_gesture_feedback_swipe_in_nav_bar" msgid="9157480023651452969">"Assurez-vous de ne pas balayer trop près du bas de l\'écran"</string>
<string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Modifiez la sensibilité du geste de retour dans Paramètres"</string>
@@ -74,11 +74,11 @@
<string name="overview_gesture_feedback_home_detected" msgid="663432226180397138">"Essayez de tenir la fenêtre plus longtemps avant de relâcher"</string>
<string name="overview_gesture_feedback_wrong_swipe_direction" msgid="1191055451018584958">"Assurez-vous de balayer l\'écran vers le haut, puis de faire une pause"</string>
<string name="overview_gesture_feedback_complete_with_follow_up" msgid="3544611727467765026">"Vous avez appris à utiliser les gestes. Pour les désactiver, accédez au menu Paramètres."</string>
- <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Vous avez appris le geste de changement d\'application"</string>
- <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Balayez pour basculer entre les applications"</string>
- <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Pour changer d\'application, balayez l\'écran de bas en haut, maintenez le doigt dessus, puis relâchez-le."</string>
+ <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Vous avez appris le geste de changement d\'appli"</string>
+ <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Balayez pour basculer entre les applis"</string>
+ <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Pour changer d\'appli, balayez l\'écran de bas en haut, maintenez le doigt dessus, puis relâchez-le."</string>
<string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Pour changer d\'appli, balayez l\'écran de bas en haut avec deux doigts, maintenez-les et relâchez-les."</string>
- <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Changer d\'application"</string>
+ <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Changer d\'appli"</string>
<string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Balayez l\'écran de bas en haut, maintenez le doigt en place, puis relâchez-le"</string>
<string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Bien joué!"</string>
<string name="gesture_tutorial_confirm_title" msgid="6201516182040074092">"Terminé"</string>
@@ -98,14 +98,14 @@
<string name="action_split" msgid="2098009717623550676">"Partager"</string>
<string name="action_save_app_pair" msgid="5974823919237645229">"Enr. paire d\'applis"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Toucher une autre appli pour partager l\'écran"</string>
- <string name="toast_contextual_split_select_app" msgid="433510957123687090">"Choisir une autre application pour utiliser l\'Écran divisé"</string>
+ <string name="toast_contextual_split_select_app" msgid="433510957123687090">"Choisir une autre appli pour utiliser l\'Écran divisé"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Annuler"</b></string>
<string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Quitter la sélection d\'écran divisé"</string>
- <string name="toast_split_app_unsupported" msgid="2360229567007828914">"Choisir une autre application pour utiliser l\'écran partagé"</string>
- <string name="blocked_by_policy" msgid="2071401072261365546">"L\'application ou votre organisation n\'autorise pas cette action"</string>
- <string name="split_widgets_not_supported" msgid="1355743038053053866">"Les widgets ne sont actuellement pas pris en charge. Veuillez sélectionner une autre application"</string>
+ <string name="toast_split_app_unsupported" msgid="2360229567007828914">"Choisir une autre appli pour utiliser l\'écran partagé"</string>
+ <string name="blocked_by_policy" msgid="2071401072261365546">"L\'appli ou votre organisation n\'autorise pas cette action"</string>
+ <string name="split_widgets_not_supported" msgid="1355743038053053866">"Les widgets ne sont actuellement pas pris en charge. Veuillez sélectionner une autre appli"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Ignorer le tutoriel sur la navigation?"</string>
- <string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Vous trouverez le tutoriel dans l\'application <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Vous trouverez le tutoriel dans l\'appli <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Annuler"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Ignorer"</string>
<string name="accessibility_rotate_button" msgid="4771825231336502943">"Faire pivoter l\'écran"</string>
@@ -137,7 +137,7 @@
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Séparateur de la barre des tâches"</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="6935266023013283353">"{count,plural, =1{Afficher # autre application.}one{Afficher # autre application.}other{Afficher # autres applications.}}"</string>
+ <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Afficher # autre appli.}one{Afficher # autre appli.}other{Afficher # autres applis.}}"</string>
<string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Afficher # appli de bureau.}one{Afficher # appli de bureau.}other{Afficher # applis de bureau.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> et <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
<string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bulle"</string>
diff --git a/quickstep/res/values-nb/strings.xml b/quickstep/res/values-nb/strings.xml
index dd3d16e..6aa755a 100644
--- a/quickstep/res/values-nb/strings.xml
+++ b/quickstep/res/values-nb/strings.xml
@@ -94,7 +94,7 @@
<string name="default_device_name" msgid="6660656727127422487">"enheten"</string>
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Innstillinger for systemnavigasjon"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Del"</string>
- <string name="action_screenshot" msgid="8171125848358142917">"Skjermdump"</string>
+ <string name="action_screenshot" msgid="8171125848358142917">"Skjermbilde"</string>
<string name="action_split" msgid="2098009717623550676">"Del opp"</string>
<string name="action_save_app_pair" msgid="5974823919237645229">"Lagre apptilkobling"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Trykk på en annen app for å bruke delt skjerm"</string>
diff --git a/quickstep/res/values-ta/strings.xml b/quickstep/res/values-ta/strings.xml
index ed3ebee..47d8055 100644
--- a/quickstep/res/values-ta/strings.xml
+++ b/quickstep/res/values-ta/strings.xml
@@ -122,7 +122,7 @@
<string name="taskbar_edu_close" msgid="887022990168191073">"மூடுக"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"முடிந்தது"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"முகப்பு"</string>
- <string name="taskbar_button_a11y" msgid="5241161324875094465">"அணுகல்தன்மை"</string>
+ <string name="taskbar_button_a11y" msgid="5241161324875094465">"மாற்றுத்திறன் வசதி"</string>
<string name="taskbar_button_back" msgid="8558862226461164514">"பின்செல்லும்"</string>
<string name="taskbar_button_ime_switcher" msgid="1730244360907588541">"IME சுவிட்ச்சர்"</string>
<string name="taskbar_button_recents" msgid="7273376136216613134">"சமீபத்தியவை"</string>
diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
index 7235b63..44d8a5c 100644
--- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
+++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
@@ -20,6 +20,7 @@
import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.statusBars;
+import static com.android.launcher3.Flags.enablePredictiveBackGesture;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -33,6 +34,9 @@
import android.view.View;
import android.view.WindowInsetsController;
import android.view.WindowManager;
+import android.window.BackEvent;
+import android.window.OnBackAnimationCallback;
+import android.window.OnBackInvokedDispatcher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -44,7 +48,6 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.widget.BaseWidgetSheet;
import com.android.launcher3.widget.WidgetCell;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
@@ -81,6 +84,15 @@
*/
private static final String EXTRA_ADDED_APP_WIDGETS = "added_app_widgets";
/**
+ * Intent extra for the string representing the title displayed within the picker header.
+ */
+ private static final String EXTRA_PICKER_TITLE = "picker_title";
+ /**
+ * Intent extra for the string representing the description displayed within the picker header.
+ */
+ private static final String EXTRA_PICKER_DESCRIPTION = "picker_description";
+
+ /**
* A unique identifier of the surface hosting the widgets;
* <p>"widgets" is reserved for home screen surface.</p>
* <p>"widgets_hub" is reserved for glanceable hub surface.</p>
@@ -108,10 +120,16 @@
// Widgets existing on the host surface.
@NonNull
private List<AppWidgetProviderInfo> mAddedWidgets = new ArrayList<>();
+ @Nullable
+ private String mTitle;
+ @Nullable
+ private String mDescription;
/** A set of user ids that should be filtered out from the selected widgets. */
@NonNull
Set<Integer> mFilteredUserIds = new HashSet<>();
+ @Nullable
+ private WidgetsFullSheet mWidgetSheet;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -136,7 +154,22 @@
refreshAndBindWidgets();
}
+ @Override
+ protected void registerBackDispatcher() {
+ if (!enablePredictiveBackGesture()) {
+ super.registerBackDispatcher();
+ return;
+ }
+
+ getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+ new BackAnimationCallback());
+ }
+
private void parseIntentExtras() {
+ mTitle = getIntent().getStringExtra(EXTRA_PICKER_TITLE);
+ mDescription = getIntent().getStringExtra(EXTRA_PICKER_DESCRIPTION);
+
// A value of 0 for either size means that no filtering will occur in that dimension. If
// both values are 0, then no size filtering will occur.
mDesiredWidgetWidth =
@@ -241,7 +274,7 @@
/** Updates the model with widgets, applies filters and launches the widgets sheet once
* widgets are available */
private void refreshAndBindWidgets() {
- MODEL_EXECUTOR.getHandler().postDelayed(() -> {
+ MODEL_EXECUTOR.execute(() -> {
LauncherAppState app = LauncherAppState.getInstance(this);
mModel.update(app, null);
final List<WidgetsListBaseEntry> allWidgets =
@@ -271,18 +304,19 @@
mUiSurface, allWidgetItems);
mWidgetPredictionsRequester.request(mAddedWidgets, this::bindRecommendedWidgets);
}
- }, mDeviceProfile.bottomSheetOpenDuration);
+ });
}
private void bindWidgets(List<WidgetsListBaseEntry> widgets) {
MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setAllWidgets(widgets));
}
- private void openWidgetsSheet() {
+ private void openWidgetsSheet() {
MAIN_EXECUTOR.execute(() -> {
- BaseWidgetSheet widgetSheet = WidgetsFullSheet.show(this, true);
- widgetSheet.disableNavBarScrim(true);
- widgetSheet.addOnCloseListener(this::finish);
+ mWidgetSheet = WidgetsFullSheet.show(this, true);
+ mWidgetSheet.mayUpdateTitleAndDescription(mTitle, mDescription);
+ mWidgetSheet.disableNavBarScrim(true);
+ mWidgetSheet.addOnCloseListener(this::finish);
});
}
@@ -301,6 +335,51 @@
}
}
+ /**
+ * Animation callback for different predictive back animation states for the widget picker.
+ */
+ private class BackAnimationCallback implements OnBackAnimationCallback {
+ @Nullable
+ OnBackAnimationCallback mActiveOnBackAnimationCallback;
+
+ @Override
+ public void onBackStarted(@NonNull BackEvent backEvent) {
+ if (mActiveOnBackAnimationCallback != null) {
+ mActiveOnBackAnimationCallback.onBackCancelled();
+ }
+ if (mWidgetSheet != null) {
+ mActiveOnBackAnimationCallback = mWidgetSheet;
+ mActiveOnBackAnimationCallback.onBackStarted(backEvent);
+ }
+ }
+
+ @Override
+ public void onBackInvoked() {
+ if (mActiveOnBackAnimationCallback == null) {
+ return;
+ }
+ mActiveOnBackAnimationCallback.onBackInvoked();
+ mActiveOnBackAnimationCallback = null;
+ }
+
+ @Override
+ public void onBackProgressed(@NonNull BackEvent backEvent) {
+ if (mActiveOnBackAnimationCallback == null) {
+ return;
+ }
+ mActiveOnBackAnimationCallback.onBackProgressed(backEvent);
+ }
+
+ @Override
+ public void onBackCancelled() {
+ if (mActiveOnBackAnimationCallback == null) {
+ return;
+ }
+ mActiveOnBackAnimationCallback.onBackCancelled();
+ mActiveOnBackAnimationCallback = null;
+ }
+ };
+
private WidgetAcceptabilityVerdict isWidgetAcceptable(WidgetItem widget) {
final AppWidgetProviderInfo info = widget.widgetInfo;
if (info == null) {
diff --git a/quickstep/src/com/android/launcher3/model/WellbeingModel.java b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
index 28bc01c..fb17f15 100644
--- a/quickstep/src/com/android/launcher3/model/WellbeingModel.java
+++ b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
@@ -83,10 +83,8 @@
private final Handler mWorkerHandler;
private final ContentObserver mContentObserver;
- private final SimpleBroadcastReceiver mWellbeingAppChangeReceiver =
- new SimpleBroadcastReceiver(t -> restartObserver());
- private final SimpleBroadcastReceiver mAppAddRemoveReceiver =
- new SimpleBroadcastReceiver(this::onAppPackageChanged);
+ private final SimpleBroadcastReceiver mWellbeingAppChangeReceiver;
+ private final SimpleBroadcastReceiver mAppAddRemoveReceiver;
private final Object mModelLock = new Object();
// Maps the action Id to the corresponding RemoteAction
@@ -101,6 +99,11 @@
mWorkerHandler = new Handler(TextUtils.isEmpty(mWellbeingProviderPkg)
? Executors.UI_HELPER_EXECUTOR.getLooper()
: Executors.getPackageExecutor(mWellbeingProviderPkg).getLooper());
+ mWellbeingAppChangeReceiver =
+ new SimpleBroadcastReceiver(mWorkerHandler, t -> restartObserver());
+ mAppAddRemoveReceiver =
+ new SimpleBroadcastReceiver(mWorkerHandler, this::onAppPackageChanged);
+
mContentObserver = new ContentObserver(mWorkerHandler) {
@Override
@@ -135,8 +138,8 @@
public void close() {
if (!TextUtils.isEmpty(mWellbeingProviderPkg)) {
mWorkerHandler.post(() -> {
- mWellbeingAppChangeReceiver.unregisterReceiverSafelySync(mContext);
- mAppAddRemoveReceiver.unregisterReceiverSafelySync(mContext);
+ mWellbeingAppChangeReceiver.unregisterReceiverSafely(mContext);
+ mAppAddRemoveReceiver.unregisterReceiverSafely(mContext);
mContext.getContentResolver().unregisterContentObserver(mContentObserver);
});
}
diff --git a/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java b/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java
index 41fcf61..8c98bab 100644
--- a/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java
+++ b/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java
@@ -59,6 +59,11 @@
public class WidgetPredictionsRequester {
private static final int NUM_OF_RECOMMENDED_WIDGETS_PREDICATION = 20;
private static final String BUNDLE_KEY_ADDED_APP_WIDGETS = "added_app_widgets";
+ // container/screenid/[positionx,positiony]/[spanx,spany]
+ // Matches the format passed used by PredictionHelper; But, position and size values aren't
+ // used, so, we pass default values.
+ @VisibleForTesting
+ static final String LAUNCH_LOCATION = "workspace/1/[0,0]/[2,2]";
@Nullable
private AppPredictor mAppPredictor;
@@ -86,7 +91,7 @@
*/
public void request(List<AppWidgetProviderInfo> existingWidgets,
Consumer<List<ItemInfo>> callback) {
- Bundle bundle = buildBundleForPredictionSession(existingWidgets, mUiSurface);
+ Bundle bundle = buildBundleForPredictionSession(existingWidgets);
Predicate<WidgetItem> filter = notOnUiSurfaceFilter(existingWidgets);
MODEL_EXECUTOR.execute(() -> {
@@ -112,17 +117,14 @@
* Returns a bundle that can be passed in a prediction session
*
* @param addedWidgets widgets that are already added by the user in the ui surface
- * @param uiSurface a unique identifier of the surface hosting widgets; format
- * "widgets_xx"; note - "widgets" is reserved for home screen surface.
*/
@VisibleForTesting
- static Bundle buildBundleForPredictionSession(List<AppWidgetProviderInfo> addedWidgets,
- String uiSurface) {
+ static Bundle buildBundleForPredictionSession(List<AppWidgetProviderInfo> addedWidgets) {
Bundle bundle = new Bundle();
ArrayList<AppTargetEvent> addedAppTargetEvents = new ArrayList<>();
for (AppWidgetProviderInfo info : addedWidgets) {
ComponentName componentName = info.provider;
- AppTargetEvent appTargetEvent = buildAppTargetEvent(uiSurface, info, componentName);
+ AppTargetEvent appTargetEvent = buildAppTargetEvent(info, componentName);
addedAppTargetEvents.add(appTargetEvent);
}
bundle.putParcelableArrayList(BUNDLE_KEY_ADDED_APP_WIDGETS, addedAppTargetEvents);
@@ -134,13 +136,13 @@
* predictor.
* Also see {@link PredictionHelper}
*/
- private static AppTargetEvent buildAppTargetEvent(String uiSurface, AppWidgetProviderInfo info,
+ private static AppTargetEvent buildAppTargetEvent(AppWidgetProviderInfo info,
ComponentName componentName) {
AppTargetId appTargetId = new AppTargetId("widget:" + componentName.getPackageName());
AppTarget appTarget = new AppTarget.Builder(appTargetId, componentName.getPackageName(),
/*user=*/ info.getProfile()).setClassName(componentName.getClassName()).build();
- return new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_PIN)
- .setLaunchLocation(uiSurface).build();
+ return new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_PIN).setLaunchLocation(
+ LAUNCH_LOCATION).build();
}
/**
diff --git a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
index 64bb05e..0395d32 100644
--- a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
+++ b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
@@ -65,7 +65,7 @@
Collectors.toSet());
Predicate<WidgetItem> notOnWorkspace = w -> !widgetsInWorkspace.contains(w);
Map<ComponentKey, WidgetItem> allWidgets =
- dataModel.widgetsModel.getAllWidgetComponentsWithoutShortcuts();
+ dataModel.widgetsModel.getWidgetsByComponentKey();
List<WidgetItem> servicePredictedItems = new ArrayList<>();
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
index 5d47212..0ba5de1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
@@ -46,6 +46,7 @@
import androidx.core.content.res.ResourcesCompat;
import com.android.app.animation.Interpolators;
+import com.android.internal.jank.Cuj;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatedFloat;
@@ -53,6 +54,7 @@
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.quickstep.util.DesktopTask;
import com.android.quickstep.util.GroupTask;
+import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import java.util.HashMap;
import java.util.List;
@@ -331,6 +333,8 @@
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
+ InteractionJankMonitorWrapper.begin(
+ KeyboardQuickSwitchView.this, Cuj.CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN);
setClipToPadding(false);
setOutlineProvider(new ViewOutlineProvider() {
@Override
@@ -366,12 +370,19 @@
}
@Override
+ public void onAnimationCancel(Animator animation) {
+ super.onAnimationCancel(animation);
+ InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN);
+ }
+
+ @Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
setClipToPadding(true);
setOutlineProvider(outlineProvider);
invalidateOutline();
mOpenAnimation = null;
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN);
}
});
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
index 73819b3..d411ba6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
@@ -16,6 +16,7 @@
package com.android.launcher3.taskbar;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.view.KeyEvent;
import android.view.animation.AnimationUtils;
import android.window.RemoteTransition;
@@ -23,6 +24,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.jank.Cuj;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
@@ -30,6 +32,7 @@
import com.android.quickstep.util.SlideInRemoteTransition;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import com.android.systemui.shared.system.QuickStepContract;
import java.io.PrintWriter;
@@ -93,18 +96,28 @@
protected void closeQuickSwitchView(boolean animate) {
if (isCloseAnimationRunning()) {
- // Let currently-running animation finish.
if (!animate) {
mCloseAnimation.end();
}
+ // Let currently-running animation finish.
return;
}
if (!animate) {
+ InteractionJankMonitorWrapper.begin(
+ mKeyboardQuickSwitchView, Cuj.CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE);
onCloseComplete();
return;
}
mCloseAnimation = mKeyboardQuickSwitchView.getCloseAnimation();
+ mCloseAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ InteractionJankMonitorWrapper.begin(
+ mKeyboardQuickSwitchView, Cuj.CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE);
+ }
+ });
mCloseAnimation.addListener(AnimatorListeners.forEndCallback(this::onCloseComplete));
mCloseAnimation.start();
}
@@ -142,16 +155,26 @@
return -1;
}
+ Runnable onStartCallback = () -> InteractionJankMonitorWrapper.begin(
+ mKeyboardQuickSwitchView, Cuj.CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH);
+ Runnable onFinishCallback = () -> InteractionJankMonitorWrapper.end(
+ Cuj.CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH);
TaskbarActivityContext context = mControllers.taskbarActivityContext;
RemoteTransition remoteTransition = new RemoteTransition(new SlideInRemoteTransition(
Utilities.isRtl(mControllers.taskbarActivityContext.getResources()),
context.getDeviceProfile().overviewPageSpacing,
QuickStepContract.getWindowCornerRadius(context),
AnimationUtils.loadInterpolator(
- context, android.R.interpolator.fast_out_extra_slow_in)),
+ context, android.R.interpolator.fast_out_extra_slow_in),
+ onStartCallback,
+ onFinishCallback),
"SlideInTransition");
mControllers.taskbarActivityContext.handleGroupTaskLaunch(
- task, remoteTransition, mOnDesktop);
+ task,
+ remoteTransition,
+ mOnDesktop,
+ onStartCallback,
+ onFinishCallback);
return -1;
}
@@ -159,6 +182,7 @@
mCloseAnimation = null;
mOverlayContext.getDragLayer().removeView(mKeyboardQuickSwitchView);
mControllerCallbacks.onCloseComplete();
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE);
}
protected void onDestroy() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 0fa3fbc..5c1e638 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -80,6 +80,7 @@
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
+import android.view.inputmethod.Flags;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -274,7 +275,10 @@
InputMethodService.canImeRenderGesturalNavButtons() && mContext.imeDrawsImeNavBar();
if (!mIsImeRenderingNavButtons) {
// IME switcher
- mImeSwitcherButton = addButton(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH,
+ final int switcherResId = Flags.imeSwitcherRevamp()
+ ? com.android.internal.R.drawable.ic_ime_switcher_new
+ : R.drawable.ic_ime_switcher;
+ mImeSwitcherButton = addButton(switcherResId, BUTTON_IME_SWITCH,
isThreeButtonNav ? mStartContextualContainer : mEndContextualContainer,
mControllers.navButtonController, R.id.ime_switcher);
mPropertyHolders.add(new StatePropertyHolder(mImeSwitcherButton,
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 6c3b4ad..3048243 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -95,6 +95,7 @@
import com.android.launcher3.model.data.AppPairInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.TaskItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.popup.PopupDataProvider;
@@ -111,6 +112,8 @@
import com.android.launcher3.taskbar.bubbles.BubblePinController;
import com.android.launcher3.taskbar.bubbles.BubbleStashController;
import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController;
+import com.android.launcher3.taskbar.customization.TaskbarFeatureEvaluator;
+import com.android.launcher3.taskbar.customization.TaskbarSpecsEvaluator;
import com.android.launcher3.taskbar.navbutton.NearestTouchFrame;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayController;
import com.android.launcher3.testing.TestLogging;
@@ -204,6 +207,10 @@
private final LauncherPrefs mLauncherPrefs;
+ private final TaskbarFeatureEvaluator mTaskbarFeatureEvaluator;
+
+ private final TaskbarSpecsEvaluator mTaskbarSpecsEvaluator;
+
public TaskbarActivityContext(Context windowContext,
@Nullable Context navigationBarPanelContext, DeviceProfile launcherDp,
TaskbarNavButtonController buttonController, ScopedUnfoldTransitionProgressProvider
@@ -214,6 +221,13 @@
applyDeviceProfile(launcherDp);
final Resources resources = getResources();
+ mTaskbarFeatureEvaluator = TaskbarFeatureEvaluator.getInstance(this);
+ mTaskbarSpecsEvaluator = new TaskbarSpecsEvaluator(
+ this,
+ mTaskbarFeatureEvaluator,
+ mDeviceProfile.inv.numRows,
+ mDeviceProfile.inv.numColumns);
+
mImeDrawsImeNavBar = getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME, resources, false);
mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
() -> getPackageManager().isSafeMode());
@@ -455,7 +469,12 @@
* Show Taskbar upon receiving broadcast
*/
public void showTaskbarFromBroadcast() {
- mControllers.taskbarStashController.showTaskbarFromBroadcast();
+ // If user is in middle of taskbar education handle go to next step of education
+ if (mControllers.taskbarEduTooltipController.isBeforeTooltipFeaturesStep()) {
+ mControllers.taskbarEduTooltipController.hide();
+ mControllers.taskbarEduTooltipController.maybeShowFeaturesEdu();
+ }
+ mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(false);
}
/** Toggles Taskbar All Apps overlay. */
@@ -794,6 +813,11 @@
*/
public void setUIController(@NonNull TaskbarUIController uiController) {
mControllers.setUiController(uiController);
+ if (mControllers.bubbleControllers.isEmpty()) {
+ // if the bubble bar was visible in a previous configuration of taskbar and is being
+ // recreated now without bubbles, clean up any bubble bar adjustments from hotseat
+ bubbleBarVisibilityChanged(/* isVisible= */ false);
+ }
}
/**
@@ -1132,6 +1156,11 @@
mControllers.uiController.onTaskbarIconLaunched(api);
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
}
+ } else if (tag instanceof TaskItemInfo info) {
+ UI_HELPER_EXECUTOR.execute(() ->
+ SystemUiProxy.INSTANCE.get(this).showDesktopApp(info.getTaskId()));
+ mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(
+ /* stash= */ true);
} else if (tag instanceof WorkspaceItemInfo) {
// Tapping a launchable icon on Taskbar
WorkspaceItemInfo info = (WorkspaceItemInfo) tag;
@@ -1215,22 +1244,44 @@
}
}
+ public void handleGroupTaskLaunch(
+ GroupTask task,
+ @Nullable RemoteTransition remoteTransition,
+ boolean onDesktop) {
+ handleGroupTaskLaunch(task, remoteTransition, onDesktop, null, null);
+ }
+
/**
* Launches the given GroupTask with the following behavior:
* - If the GroupTask is a DesktopTask, launch the tasks in that Desktop.
* - If {@code onDesktop}, bring the given GroupTask to the front.
* - If the GroupTask is a single task, launch it via startActivityFromRecents.
* - Otherwise, we assume the GroupTask is a Split pair and launch them together.
+ * <p>
+ * Given start and/or finish callbacks, they will be run before an after the app launch
+ * respectively in cases where we can't use the remote transition, otherwise we will assume that
+ * these callbacks are included in the remote transition.
*/
- public void handleGroupTaskLaunch(GroupTask task, @Nullable RemoteTransition remoteTransition,
- boolean onDesktop) {
+ public void handleGroupTaskLaunch(
+ GroupTask task,
+ @Nullable RemoteTransition remoteTransition,
+ boolean onDesktop,
+ @Nullable Runnable onStartCallback,
+ @Nullable Runnable onFinishCallback) {
if (task instanceof DesktopTask) {
UI_HELPER_EXECUTOR.execute(() ->
SystemUiProxy.INSTANCE.get(this).showDesktopApps(getDisplay().getDisplayId(),
remoteTransition));
} else if (onDesktop) {
- UI_HELPER_EXECUTOR.execute(() ->
- SystemUiProxy.INSTANCE.get(this).showDesktopApp(task.task1.key.id));
+ UI_HELPER_EXECUTOR.execute(() -> {
+ if (onStartCallback != null) {
+ onStartCallback.run();
+ }
+ SystemUiProxy.INSTANCE.get(this).showDesktopApp(task.task1.key.id);
+ if (onFinishCallback != null) {
+ onFinishCallback.run();
+ }
+ });
} else if (task.task2 == null) {
UI_HELPER_EXECUTOR.execute(() -> {
ActivityOptions activityOptions =
@@ -1611,6 +1662,14 @@
return mControllers.taskbarStashController.isInStashedLauncherState();
}
+ public TaskbarFeatureEvaluator getTaskbarFeatureEvaluator() {
+ return mTaskbarFeatureEvaluator;
+ }
+
+ public TaskbarSpecsEvaluator getTaskbarSpecsEvaluator() {
+ return mTaskbarSpecsEvaluator;
+ }
+
protected void dumpLogs(String prefix, PrintWriter pw) {
pw.println(prefix + "TaskbarActivityContext:");
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index 58c5e83..0645972 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -282,6 +282,11 @@
}
uiController.dumpLogs(prefix + "\t", pw);
rotationButtonController.dumpLogs(prefix + "\t", pw);
+ if (bubbleControllers.isPresent()) {
+ bubbleControllers.get().dump(pw);
+ } else {
+ pw.println(String.format("%s\t%s", prefix, "Bubble controllers are empty."));
+ }
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index b90e5fd..f411e79 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -39,7 +39,6 @@
import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.hardware.display.DisplayManager;
@@ -120,7 +119,7 @@
private final ComponentCallbacks mComponentCallbacks;
private final SimpleBroadcastReceiver mShutdownReceiver =
- new SimpleBroadcastReceiver(i -> destroyExistingTaskbar());
+ new SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, i -> destroyExistingTaskbar());
// The source for this provider is set when Launcher is available
// We use 'non-destroyable' version here so the original provider won't be destroyed
@@ -157,7 +156,7 @@
private boolean mUserUnlocked = false;
private final SimpleBroadcastReceiver mTaskbarBroadcastReceiver =
- new SimpleBroadcastReceiver(this::showTaskbarFromBroadcast);
+ new SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, this::showTaskbarFromBroadcast);
private final AllAppsActionManager mAllAppsActionManager;
@@ -306,17 +305,15 @@
.register(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
Log.d(TASKBAR_NOT_DESTROYED_TAG, "registering component callbacks from constructor.");
mContext.registerComponentCallbacks(mComponentCallbacks);
- mShutdownReceiver.registerAsync(mContext, Intent.ACTION_SHUTDOWN);
+ mShutdownReceiver.register(mContext, Intent.ACTION_SHUTDOWN);
UI_HELPER_EXECUTOR.execute(() -> {
mSharedState.taskbarSystemActionPendingIntent = PendingIntent.getBroadcast(
mContext,
SYSTEM_ACTION_ID_TASKBAR,
new Intent(ACTION_SHOW_TASKBAR).setPackage(mContext.getPackageName()),
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
- mContext.registerReceiver(
- mTaskbarBroadcastReceiver,
- new IntentFilter(ACTION_SHOW_TASKBAR),
- RECEIVER_NOT_EXPORTED);
+ mTaskbarBroadcastReceiver.register(
+ mContext, RECEIVER_NOT_EXPORTED, ACTION_SHOW_TASKBAR);
});
debugWhyTaskbarNotDestroyed("TaskbarManager created");
@@ -623,7 +620,7 @@
public void destroy() {
debugWhyTaskbarNotDestroyed("TaskbarManager#destroy()");
removeActivityCallbacksAndListeners();
- mTaskbarBroadcastReceiver.unregisterReceiverSafelyAsync(mContext);
+ mTaskbarBroadcastReceiver.unregisterReceiverSafely(mContext);
destroyExistingTaskbar();
removeTaskbarRootViewFromWindow();
if (mUserUnlocked) {
@@ -635,7 +632,7 @@
.unregister(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
Log.d(TASKBAR_NOT_DESTROYED_TAG, "unregistering component callbacks from destroy().");
mContext.unregisterComponentCallbacks(mComponentCallbacks);
- mShutdownReceiver.unregisterReceiverSafelyAsync(mContext);
+ mShutdownReceiver.unregisterReceiverSafely(mContext);
}
public @Nullable TaskbarActivityContext getCurrentActivityContext() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
index 0b7ae39..5024cd8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
@@ -196,26 +196,26 @@
final TaskbarRecentAppsController recentAppsController =
mControllers.taskbarRecentAppsController;
hotseatItemInfos = recentAppsController.updateHotseatItemInfos(hotseatItemInfos);
- Set<String> runningPackages = recentAppsController.getRunningAppPackages();
- Set<String> minimizedPackages = recentAppsController.getMinimizedAppPackages();
+ Set<Integer> runningTaskIds = recentAppsController.getRunningTaskIds();
+ Set<Integer> minimizedTaskIds = recentAppsController.getMinimizedTaskIds();
if (mDeferUpdatesForSUW) {
ItemInfo[] finalHotseatItemInfos = hotseatItemInfos;
mDeferredUpdates = () ->
commitHotseatItemUpdates(finalHotseatItemInfos,
- recentAppsController.getShownTasks(), runningPackages,
- minimizedPackages);
+ recentAppsController.getShownTasks(), runningTaskIds,
+ minimizedTaskIds);
} else {
commitHotseatItemUpdates(hotseatItemInfos,
- recentAppsController.getShownTasks(), runningPackages, minimizedPackages);
+ recentAppsController.getShownTasks(), runningTaskIds, minimizedTaskIds);
}
}
private void commitHotseatItemUpdates(ItemInfo[] hotseatItemInfos, List<GroupTask> recentTasks,
- Set<String> runningPackages, Set<String> minimizedPackages) {
+ Set<Integer> runningTaskIds, Set<Integer> minimizedTaskIds) {
mContainer.updateHotseatItems(hotseatItemInfos, recentTasks);
mControllers.taskbarViewController.updateIconViewsRunningStates(
- runningPackages, minimizedPackages);
+ runningTaskIds, minimizedTaskIds);
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
index ea091ca..872a4d0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -24,6 +24,7 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_BACK_BUTTON_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_HOME_BUTTON_TAP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_LONGPRESS;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_LONGPRESS;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_TAP;
@@ -37,6 +38,7 @@
import android.util.Log;
import android.view.HapticFeedbackConstants;
import android.view.View;
+import android.view.inputmethod.Flags;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
@@ -147,7 +149,7 @@
break;
case BUTTON_IME_SWITCH:
logEvent(LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_TAP);
- showIMESwitcher();
+ onImeSwitcherPress();
break;
case BUTTON_A11Y:
logEvent(LAUNCHER_TASKBAR_A11Y_BUTTON_TAP);
@@ -190,6 +192,12 @@
backRecentsLongpress(buttonType);
return true;
case BUTTON_IME_SWITCH:
+ if (Flags.imeSwitcherRevamp()) {
+ logEvent(LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_LONGPRESS);
+ onImeSwitcherLongPress();
+ return true;
+ }
+ return false;
default:
return false;
}
@@ -305,10 +313,14 @@
mSystemUiProxy.onBackPressed();
}
- private void showIMESwitcher() {
+ private void onImeSwitcherPress() {
mSystemUiProxy.onImeSwitcherPressed();
}
+ private void onImeSwitcherLongPress() {
+ mSystemUiProxy.onImeSwitcherLongPress();
+ }
+
private void notifyA11yClick(boolean longClick) {
if (longClick) {
mSystemUiProxy.notifyAccessibilityButtonLongClicked();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
index 36828a8..5c08116 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
@@ -18,6 +18,8 @@
import androidx.annotation.VisibleForTesting
import com.android.launcher3.Flags.enableRecentsInTaskbar
import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.TaskItemInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.statehandlers.DesktopVisibilityController
import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
import com.android.launcher3.util.CancellableTask
@@ -58,9 +60,13 @@
// Initialized in init.
private lateinit var controllers: TaskbarControllers
- private var shownHotseatItems: List<ItemInfo> = emptyList()
+ var shownHotseatItems: List<ItemInfo> = emptyList()
+ private set
+
private var allRecentTasks: List<GroupTask> = emptyList()
private var desktopTask: DesktopTask? = null
+ // Keeps track of the order in which running tasks appear.
+ private var orderedRunningTaskIds = emptyList<Int>()
var shownTasks: List<GroupTask> = emptyList()
private set
@@ -70,9 +76,9 @@
private val isInDesktopMode: Boolean
get() = desktopVisibilityController?.areDesktopTasksVisible() ?: false
- val runningAppPackages: Set<String>
+ val runningTaskIds: Set<Int>
/**
- * Returns the package names of apps that should be indicated as "running" to the user.
+ * Returns the task IDs of apps that should be indicated as "running" to the user.
* Specifically, we return all the open tasks if we are in Desktop mode, else emptySet().
*/
get() {
@@ -80,22 +86,19 @@
return emptySet()
}
val tasks = desktopTask?.tasks ?: return emptySet()
- return tasks.map { task -> task.key.packageName }.toSet()
+ return tasks.map { task -> task.key.id }.toSet()
}
- val minimizedAppPackages: Set<String>
+ val minimizedTaskIds: Set<Int>
/**
- * Returns the package names of apps that should be indicated as "minimized" to the user.
- * Specifically, we return all the running packages where all the tasks in that package are
- * minimized (not visible).
+ * Returns the task IDs for the tasks that should be indicated as "minimized" to the user.
*/
get() {
if (!canShowRunningApps || !isInDesktopMode) {
return emptySet()
}
val desktopTasks = desktopTask?.tasks ?: return emptySet()
- val packageToTasks = desktopTasks.groupBy { it.key.packageName }
- return packageToTasks.filterValues { tasks -> tasks.all { !it.isVisible } }.keys
+ return desktopTasks.filter { !it.isVisible }.map { task -> task.key.id }.toSet()
}
private val recentTasksChangedListener =
@@ -137,25 +140,39 @@
.filter { itemInfo -> !itemInfo.isPredictedItem }
.toMutableList()
+ if (isInDesktopMode && canShowRunningApps) {
+ shownHotseatItems =
+ updateHotseatItemsFromRunningTasks(
+ getOrderedAndWrappedDesktopTasks(),
+ shownHotseatItems
+ )
+ }
+
onRecentsOrHotseatChanged()
return shownHotseatItems.toTypedArray()
}
+ private fun getOrderedAndWrappedDesktopTasks(): List<GroupTask> {
+ val tasks = desktopTask?.tasks ?: emptyList()
+ // Kind of hacky, we wrap each single task in the Desktop as a GroupTask.
+ val orderFromId = orderedRunningTaskIds.withIndex().associate { (index, id) -> id to index }
+ val sortedTasks = tasks.sortedWith(compareBy(nullsLast()) { orderFromId[it.key.id] })
+ return sortedTasks.map { GroupTask(it) }
+ }
+
private fun reloadRecentTasksIfNeeded() {
if (!recentsModel.isTaskListValid(taskListChangeId)) {
taskListChangeId =
recentsModel.getTasks { tasks ->
allRecentTasks = tasks
- val oldRunningPackages = runningAppPackages
- val oldMinimizedPackages = minimizedAppPackages
+ val oldRunningTaskdIds = runningTaskIds
+ val oldMinimizedTaskIds = minimizedTaskIds
desktopTask = allRecentTasks.filterIsInstance<DesktopTask>().firstOrNull()
- val runningPackagesChanged = oldRunningPackages != runningAppPackages
- val minimizedPackagessChanged = oldMinimizedPackages != minimizedAppPackages
+ val runningTasksChanged = oldRunningTaskdIds != runningTaskIds
+ val minimizedTasksChanged = oldMinimizedTaskIds != minimizedTaskIds
if (
- onRecentsOrHotseatChanged() ||
- runningPackagesChanged ||
- minimizedPackagessChanged
+ onRecentsOrHotseatChanged() || runningTasksChanged || minimizedTasksChanged
) {
controllers.taskbarViewController.commitRunningAppsToUI()
}
@@ -170,6 +187,7 @@
*/
private fun onRecentsOrHotseatChanged(): Boolean {
val oldShownTasks = shownTasks
+ orderedRunningTaskIds = updateOrderedRunningTaskIds()
shownTasks =
if (isInDesktopMode) {
computeShownRunningTasks()
@@ -201,22 +219,39 @@
return shownTasksChanged
}
+ private fun updateOrderedRunningTaskIds(): MutableList<Int> {
+ val desktopTaskAsList = getOrderedAndWrappedDesktopTasks()
+ val desktopTaskIds = desktopTaskAsList.map { it.task1.key.id }
+ var newOrder =
+ orderedRunningTaskIds
+ .filter { it in desktopTaskIds } // Only keep the tasks that are still running
+ .toMutableList()
+ // Add new tasks not already listed
+ newOrder.addAll(desktopTaskIds.filter { it !in newOrder })
+ return newOrder
+ }
+
private fun computeShownRunningTasks(): List<GroupTask> {
if (!canShowRunningApps) {
return emptyList()
}
- val tasks = desktopTask?.tasks ?: emptyList()
- // Kind of hacky, we wrap each single task in the Desktop as a GroupTask.
- var desktopTaskAsList = tasks.map { GroupTask(it) }
- // TODO(b/315344726 Multi-instance support): dedupe Tasks of the same package too.
- desktopTaskAsList = dedupeHotseatTasks(desktopTaskAsList, shownHotseatItems)
- val desktopPackages = desktopTaskAsList.map { it.packageNames }
- // Remove any missing Tasks.
- val newShownTasks = shownTasks.filter { it.packageNames in desktopPackages }.toMutableList()
- val newShownPackages = newShownTasks.map { it.packageNames }
+ val desktopTaskAsList = getOrderedAndWrappedDesktopTasks()
+ val desktopTaskIds = desktopTaskAsList.map { it.task1.key.id }
+ val shownTaskIds = shownTasks.map { it.task1.key.id }
+ // TODO(b/315344726 Multi-instance support): only show one icon per package once we support
+ // taskbar multi-instance menus
+ val shownHotseatItemTaskIds =
+ shownHotseatItems.mapNotNull { it as? TaskItemInfo }.map { it.taskId }
+ // Remove any newly-missing Tasks, and actual group-tasks
+ val newShownTasks =
+ shownTasks
+ .filter { !it.hasMultipleTasks() }
+ .filter { it.task1.key.id in desktopTaskIds }
+ .toMutableList()
// Add any new Tasks, maintaining the order from previous shownTasks.
- newShownTasks.addAll(desktopTaskAsList.filter { it.packageNames !in newShownPackages })
- return newShownTasks.toList()
+ newShownTasks.addAll(desktopTaskAsList.filter { it.task1.key.id !in shownTaskIds })
+ // Remove any tasks already covered by Hotseat icons
+ return newShownTasks.filter { it.task1.key.id !in shownHotseatItemTaskIds }
}
private fun computeShownRecentTasks(): List<GroupTask> {
@@ -245,6 +280,25 @@
}
}
+ /**
+ * Returns the hotseat items updated so that any item that points to a package with a running
+ * task also references that task.
+ */
+ private fun updateHotseatItemsFromRunningTasks(
+ groupTasks: List<GroupTask>,
+ shownHotseatItems: List<ItemInfo>
+ ): List<ItemInfo> =
+ shownHotseatItems.map { itemInfo ->
+ if (itemInfo is TaskItemInfo) {
+ itemInfo
+ } else {
+ val foundTask =
+ groupTasks.find { task -> task.task1.key.packageName == itemInfo.targetPackage }
+ ?: return@map itemInfo
+ TaskItemInfo(foundTask.task1.key.id, itemInfo as WorkspaceItemInfo)
+ }
+ }
+
override fun dumpLogs(prefix: String, pw: PrintWriter) {
pw.println("$prefix TaskbarRecentAppsController:")
pw.println("$prefix\tcanShowRunningApps=$canShowRunningApps")
@@ -253,8 +307,8 @@
pw.println("$prefix\tallRecentTasks=${allRecentTasks.map { it.packageNames }}")
pw.println("$prefix\tdesktopTask=${desktopTask?.packageNames}")
pw.println("$prefix\tshownTasks=${shownTasks.map { it.packageNames }}")
- pw.println("$prefix\trunningTasks=$runningAppPackages")
- pw.println("$prefix\tminimizedTasks=$minimizedAppPackages")
+ pw.println("$prefix\trunningTaskIds=$runningTaskIds")
+ pw.println("$prefix\tminimizedTaskIds=$minimizedTaskIds")
}
private val GroupTask.packageNames: List<String>
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 64fb04b..430c003 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -286,18 +286,6 @@
}
/**
- * Show Taskbar upon receiving broadcast
- */
- public void showTaskbarFromBroadcast() {
- // If user is in middle of taskbar education handle go to next step of education
- if (mControllers.taskbarEduTooltipController.isBeforeTooltipFeaturesStep()) {
- mControllers.taskbarEduTooltipController.hide();
- mControllers.taskbarEduTooltipController.maybeShowFeaturesEdu();
- }
- updateAndAnimateTransientTaskbar(false);
- }
-
- /**
* Initializes the controller
*/
public void init(
@@ -343,9 +331,10 @@
applyState(/* duration = */ 0);
// Hide the background while stashed so it doesn't show on fast swipes home
- boolean shouldHideTaskbarBackground = enableScalingRevealHomeAnimation()
- && DisplayController.isTransientTaskbar(mActivity)
- && isStashed();
+ boolean shouldHideTaskbarBackground = mActivity.isPhoneMode() ||
+ (enableScalingRevealHomeAnimation()
+ && DisplayController.isTransientTaskbar(mActivity)
+ && isStashed());
mTaskbarBackgroundAlphaForStash.setValue(shouldHideTaskbarBackground ? 0 : 1);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 170e018..a2278ec 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -270,7 +270,7 @@
foundTask,
taskContainer.getIconView().getDrawable(),
taskContainer.getSnapshotView(),
- taskContainer.getThumbnail(),
+ taskContainer.getSplitAnimationThumbnail(),
null /* intent */,
null /* user */,
info);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index e59a016..b21c414 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -46,6 +46,7 @@
import android.view.animation.Interpolator;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import androidx.core.view.OneShotPreDrawListener;
import com.android.app.animation.Interpolators;
@@ -63,6 +64,7 @@
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.TaskItemInfo;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.LauncherBindableItemsContainer;
@@ -95,6 +97,8 @@
public static final int ALPHA_INDEX_SMALL_SCREEN = 6;
private static final int NUM_ALPHA_CHANNELS = 7;
+ private static boolean sEnableModelLoadingForTests = true;
+
private final TaskbarActivityContext mActivity;
private final TaskbarView mTaskbarView;
private final MultiValueAlpha mTaskbarIconAlpha;
@@ -191,7 +195,7 @@
mTaskbarIconTranslationXForPinning.updateValue(pinningValue);
mModelCallbacks.init(controllers);
- if (mActivity.isUserSetupComplete()) {
+ if (mActivity.isUserSetupComplete() && sEnableModelLoadingForTests) {
// Only load the callbacks if user setup is completed
LauncherAppState.getInstance(mActivity).getModel().addCallbacksAndLoad(mModelCallbacks);
}
@@ -515,35 +519,38 @@
return mTaskbarView.getTaskbarDividerView();
}
- /** Updates which icons are marked as running given the Set of currently running packages. */
- public void updateIconViewsRunningStates(Set<String> runningPackages,
- Set<String> minimizedPackages) {
+ /**
+ * Updates which icons are marked as running or minimized given the Sets of currently running
+ * and minimized tasks.
+ */
+ public void updateIconViewsRunningStates(Set<Integer> runningTaskIds,
+ Set<Integer> minimizedTaskIds) {
for (View iconView : getIconViews()) {
if (iconView instanceof BubbleTextView btv) {
btv.updateRunningState(
- getRunningAppState(btv, runningPackages, minimizedPackages));
+ getRunningAppState(btv, runningTaskIds, minimizedTaskIds));
}
}
}
private BubbleTextView.RunningAppState getRunningAppState(
BubbleTextView btv,
- Set<String> runningPackages,
- Set<String> minimizedPackages) {
+ Set<Integer> runningTaskIds,
+ Set<Integer> minimizedTaskIds) {
Object tag = btv.getTag();
- if (tag instanceof ItemInfo itemInfo) {
- if (minimizedPackages.contains(itemInfo.getTargetPackage())) {
+ if (tag instanceof TaskItemInfo itemInfo) {
+ if (minimizedTaskIds.contains(itemInfo.getTaskId())) {
return BubbleTextView.RunningAppState.MINIMIZED;
}
- if (runningPackages.contains(itemInfo.getTargetPackage())) {
+ if (runningTaskIds.contains(itemInfo.getTaskId())) {
return BubbleTextView.RunningAppState.RUNNING;
}
}
if (tag instanceof GroupTask groupTask && !groupTask.hasMultipleTasks()) {
- if (minimizedPackages.contains(groupTask.task1.key.getPackageName())) {
+ if (minimizedTaskIds.contains(groupTask.task1.key.id)) {
return BubbleTextView.RunningAppState.MINIMIZED;
}
- if (runningPackages.contains(groupTask.task1.key.getPackageName())) {
+ if (runningTaskIds.contains(groupTask.task1.key.id)) {
return BubbleTextView.RunningAppState.RUNNING;
}
}
@@ -920,4 +927,10 @@
mModelCallbacks.dumpLogs(prefix + "\t", pw);
}
+
+ /** Enables model loading for tests. */
+ @VisibleForTesting
+ public static void enableModelLoadingForTests(boolean enable) {
+ sEnableModelLoadingForTests = enable;
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index 4100e51..7426dc7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -356,6 +356,13 @@
}
}
+ // if a bubble was updated upstream, but removed before the update was received, add it back
+ if (update.updatedBubble != null && !mBubbles.containsKey(update.updatedBubble.getKey())) {
+ mBubbles.put(update.updatedBubble.getKey(), update.updatedBubble);
+ mBubbleBarViewController.addBubble(
+ update.updatedBubble, isExpanding, suppressAnimation);
+ }
+
if (update.addedBubble != null && isCollapsed) {
// If we're collapsed, the most recently added bubble will be selected.
bubbleToSelect = update.addedBubble;
@@ -394,7 +401,8 @@
BubbleBarBubble bb = mBubbles.get(update.updatedBubble.getKey());
// If we're not stashed, we're visible so animate
bb.getView().updateDotVisibility(!mBubbleStashController.isStashed() /* animate */);
- mBubbleBarViewController.animateBubbleNotification(bb, /* isExpanding= */ false);
+ mBubbleBarViewController.animateBubbleNotification(
+ bb, /* isExpanding= */ false, /* isUpdate= */ true);
}
if (update.bubbleKeysInOrder != null && !update.bubbleKeysInOrder.isEmpty()) {
// Create the new list
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 402b091..fd989b1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -46,9 +46,10 @@
import com.android.launcher3.anim.SpringAnimationBuilder;
import com.android.launcher3.taskbar.bubbles.animation.BubbleAnimator;
import com.android.launcher3.util.DisplayController;
-import com.android.wm.shell.Flags;
import com.android.wm.shell.common.bubbles.BubbleBarLocation;
+import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
@@ -258,10 +259,6 @@
if (!isIconSizeOrPaddingUpdated(newIconSize, newBubbleBarPadding)) {
return;
}
- if (!Flags.animateBubbleSizeChange()) {
- setIconSizeAndPadding(newIconSize, newBubbleBarPadding);
- return;
- }
if (mScalePaddingAnimator != null && mScalePaddingAnimator.isRunning()) {
mScalePaddingAnimator.cancel();
}
@@ -899,6 +896,13 @@
float fullElevationForChild = (MAX_BUBBLES * mBubbleElevation) - i;
bv.setZ(fullElevationForChild * elevationState);
+ // only update the dot scale if we're expanding or collapsing
+ // TODO b/351904597: update the dot for the first bubble after removal and reorder
+ // since those might happen when the bar is collapsed and will need their dot back
+ if (mWidthAnimator.isRunning()) {
+ bv.setDotScale(widthState);
+ }
+
if (mIsBarExpanded) {
// If bar is on the right, account for bubble bar expanding and shifting left
final float expandedBarShift = onLeft ? 0 : currentWidth - expandedWidth;
@@ -907,7 +911,6 @@
bv.setTranslationX(widthState * (targetX - collapsedX) + collapsedX);
// When we're expanded, the badge is visible for all bubbles
bv.updateBadgeVisibility(/* show= */ true);
- bv.setDotScale(widthState);
bv.setAlpha(1);
} else {
// If bar is on the right, account for bubble bar expanding and shifting left
@@ -916,7 +919,6 @@
bv.setTranslationX(widthState * (expandedX - targetX) + targetX);
// The badge is always visible for the first bubble
bv.updateBadgeVisibility(/* show= */ i == 0);
- bv.setDotScale(widthState);
// If we're fully collapsed, hide all bubbles except for the first 2. If there are
// only 2 bubbles, hide the second bubble as well because it's the overflow.
if (widthState == 0) {
@@ -979,8 +981,7 @@
return translationX - getScaleIconShift();
}
- private float getCollapsedBubbleTranslationX(int bubbleIndex, int bubbleCount,
- boolean onLeft) {
+ private float getCollapsedBubbleTranslationX(int bubbleIndex, int bubbleCount, boolean onLeft) {
if (bubbleIndex < 0 || bubbleIndex >= bubbleCount) {
return 0;
}
@@ -991,7 +992,9 @@
bubbleIndex == 0 && bubbleCount > MAX_VISIBLE_BUBBLES_COLLAPSED
? mIconOverlapAmount : 0);
} else {
- translationX = mBubbleBarPadding + (bubbleIndex == 0 ? 0 : mIconOverlapAmount);
+ translationX = mBubbleBarPadding + (
+ bubbleIndex == 0 || bubbleCount <= MAX_VISIBLE_BUBBLES_COLLAPSED
+ ? 0 : mIconOverlapAmount);
}
return translationX - getScaleIconShift();
}
@@ -1309,6 +1312,37 @@
});
}
+ /** Dumps the current state of BubbleBarView. */
+ public void dump(PrintWriter pw) {
+ pw.println("BubbleBarView state:");
+ pw.println(" visibility: " + getVisibility());
+ pw.println(" translation Y: " + getTranslationY());
+ pw.println(" bubbles in bar (childCount = " + getChildCount() + ")");
+ for (BubbleView bubbleView: getBubbles()) {
+ BubbleBarItem bubble = bubbleView.getBubble();
+ String key = bubble == null ? "null" : bubble.getKey();
+ pw.println(" bubble key: " + key);
+ }
+ pw.println(" isExpanded: " + isExpanded());
+ pw.println(" mIsAnimatingNewBubble: " + mIsAnimatingNewBubble);
+ if (mBubbleAnimator != null) {
+ pw.println(" mBubbleAnimator.isRunning(): " + mBubbleAnimator.isRunning());
+ pw.println(" mBubbleAnimator is null");
+ }
+ pw.println(" mDragging: " + mDragging);
+ }
+
+ private List<BubbleView> getBubbles() {
+ List<BubbleView> bubbles = new ArrayList<>();
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ if (child instanceof BubbleView bubble) {
+ bubbles.add(bubble);
+ }
+ }
+ return bubbles;
+ }
+
/** 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 40e5b64..24b9139 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -43,6 +43,7 @@
import com.android.quickstep.SystemUiProxy;
import com.android.wm.shell.common.bubbles.BubbleBarLocation;
+import java.io.PrintWriter;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
@@ -88,6 +89,8 @@
private BubbleBarViewAnimator mBubbleBarViewAnimator;
+ private TimeSource mTimeSource = System::currentTimeMillis;
+
@Nullable
private BubbleBarBoundsChangeListener mBoundsChangeListener;
@@ -399,7 +402,7 @@
addedBubble.getView().setOnClickListener(mBubbleClickListener);
mBubbleDragController.setupBubbleView(addedBubble.getView());
if (!suppressAnimation) {
- animateBubbleNotification(addedBubble, isExpanding);
+ animateBubbleNotification(addedBubble, isExpanding, /* isUpdate= */ false);
}
}
@@ -427,18 +430,19 @@
}
return;
}
- animateBubbleNotification(bubble, isExpanding);
+ animateBubbleNotification(bubble, isExpanding, /* isUpdate= */ false);
} else {
Log.w(TAG, "addBubble, bubble was null!");
}
}
/** Animates the bubble bar to notify the user about a bubble change. */
- public void animateBubbleNotification(BubbleBarBubble bubble, boolean isExpanding) {
+ public void animateBubbleNotification(BubbleBarBubble bubble, boolean isExpanding,
+ boolean isUpdate) {
boolean isInApp = mTaskbarStashController.isInApp();
// if this is the first bubble, animate to the initial state. one bubble is the overflow
// so check for at most 2 children.
- if (mBarView.getChildCount() <= 2) {
+ if (mBarView.getChildCount() <= 2 && !isUpdate) {
mBubbleBarViewAnimator.animateToInitialState(bubble, isInApp, isExpanding);
return;
}
@@ -574,7 +578,7 @@
* @param bubble dismissed bubble item
*/
public void onDismissBubbleWhileDragging(@NonNull BubbleBarItem bubble) {
- mSystemUiProxy.dragBubbleToDismiss(bubble.getKey());
+ mSystemUiProxy.dragBubbleToDismiss(bubble.getKey(), mTimeSource.currentTimeMillis());
}
/**
@@ -598,4 +602,24 @@
/** Called when bounds have changed */
void onBoundsChanged();
}
+
+ /** Interface for getting the current timestamp. */
+ interface TimeSource {
+ long currentTimeMillis();
+ }
+
+ /** Dumps the state of BubbleBarViewController. */
+ public void dump(PrintWriter pw) {
+ pw.println("Bubble bar view controller state:");
+ pw.println(" mHiddenForSysui: " + mHiddenForSysui);
+ pw.println(" mHiddenForNoBubbles: " + mHiddenForNoBubbles);
+ pw.println(" mShouldShowEducation: " + mShouldShowEducation);
+ pw.println(" mBubbleBarTranslationY.value: " + mBubbleBarTranslationY.value);
+ pw.println(" mBubbleBarSwipeUpTranslationY: " + mBubbleBarSwipeUpTranslationY);
+ if (mBarView != null) {
+ mBarView.dump(pw);
+ } else {
+ pw.println(" Bubble bar view is null!");
+ }
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
index 32d6375..03140fe 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
@@ -18,6 +18,8 @@
import com.android.launcher3.taskbar.TaskbarControllers;
import com.android.launcher3.util.RunnableList;
+import java.io.PrintWriter;
+
/**
* Hosts various bubble controllers to facilitate passing between one another.
*/
@@ -94,4 +96,9 @@
bubbleStashedHandleViewController.onDestroy();
bubbleBarController.onDestroy();
}
+
+ /** Dumps bubble controllers state. */
+ public void dump(PrintWriter pw) {
+ bubbleBarViewController.dump(pw);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
index 185f85f..74f58ac 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
@@ -36,6 +36,8 @@
import com.android.wm.shell.common.bubbles.BubbleBarLocation;
import com.android.wm.shell.shared.animation.PhysicsAnimator;
+import java.io.PrintWriter;
+
/**
* Coordinates between controllers such as BubbleBarView and BubbleHandleViewController to
* create a cohesive animation between stashed/unstashed states.
@@ -456,4 +458,13 @@
public void setHandleTranslationY(float ty) {
mHandleViewController.setTranslationYForSwipe(ty);
}
+
+ /** Dumps the state of BubbleStashController. */
+ public void dump(PrintWriter pw) {
+ pw.println("Bubble stash controller state:");
+ pw.println(" mIsStashed: " + mIsStashed);
+ pw.println(" mBubblesShowingOnOverview: " + mBubblesShowingOnOverview);
+ pw.println(" mBubblesShowingOnHome: " + mBubblesShowingOnHome);
+ pw.println(" mIsSysuiLocked: " + mIsSysuiLocked);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
index 0e26c54..4c468bb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
@@ -20,6 +20,7 @@
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Outline;
+import android.graphics.Path;
import android.graphics.Rect;
import android.text.TextUtils;
import android.util.AttributeSet;
@@ -47,7 +48,7 @@
private final ImageView mBubbleIcon;
private final ImageView mAppIcon;
- private final int mBubbleSize;
+ private int mBubbleSize;
private float mDragTranslationX;
private float mOffsetX;
@@ -89,8 +90,6 @@
setLayoutDirection(LAYOUT_DIRECTION_LTR);
LayoutInflater.from(context).inflate(R.layout.bubble_view, this);
-
- mBubbleSize = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_size);
mBubbleIcon = findViewById(R.id.icon_view);
mAppIcon = findViewById(R.id.app_icon_view);
@@ -107,11 +106,21 @@
}
private void getOutline(Outline outline) {
+ updateBubbleSizeAndDotRender();
final int normalizedSize = IconNormalizer.getNormalizedCircleSize(mBubbleSize);
final int inset = (mBubbleSize - normalizedSize) / 2;
outline.setOval(inset, inset, inset + normalizedSize, inset + normalizedSize);
}
+ private void updateBubbleSizeAndDotRender() {
+ int updatedBubbleSize = Math.min(getWidth(), getHeight());
+ if (updatedBubbleSize == mBubbleSize) return;
+ mBubbleSize = updatedBubbleSize;
+ if (mBubble == null || mBubble instanceof BubbleBarOverflow) return;
+ Path dotPath = ((BubbleBarBubble) mBubble).getDotPath();
+ mDotRenderer = new DotRenderer(mBubbleSize, dotPath, DEFAULT_PATH_SIZE);
+ }
+
/**
* Set translation-x while this bubble is being dragged.
* Translation applied to the view is a sum of {@code translationX} and offset defined by
@@ -141,6 +150,12 @@
applyDragTranslation();
}
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ updateBubbleSizeAndDotRender();
+ }
+
private void applyDragTranslation() {
setTranslationX(mDragTranslationX + mOffsetX);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/CustomizableTaskbarView.kt b/quickstep/src/com/android/launcher3/taskbar/customization/CustomizableTaskbarView.kt
new file mode 100644
index 0000000..e384586
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/CustomizableTaskbarView.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.customization
+
+import android.content.Context
+import android.graphics.Rect
+import android.util.AttributeSet
+import androidx.constraintlayout.widget.ConstraintLayout
+import com.android.launcher3.Insettable
+import com.android.launcher3.R
+import com.android.launcher3.taskbar.TaskbarActivityContext
+import com.android.launcher3.views.ActivityContext
+
+/** TaskbarView that is customizeable via Taskbar containers. */
+class CustomizableTaskbarView(context: Context, attrs: AttributeSet? = null) :
+ ConstraintLayout(context, attrs), Insettable {
+ private val activityContext: TaskbarActivityContext = ActivityContext.lookupContext(context)
+
+ init {
+ inflate(context, R.layout.customizable_taskbar_view, this)
+ }
+
+ override fun setInsets(insets: Rect?) {
+ // Ignore, we just implement Insettable to draw behind system insets.
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarAllAppsButtonContainer.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarAllAppsButtonContainer.kt
new file mode 100644
index 0000000..415a051
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarAllAppsButtonContainer.kt
@@ -0,0 +1,97 @@
+/*
+ * 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.customization
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.widget.LinearLayout
+import androidx.annotation.DimenRes
+import androidx.annotation.DrawableRes
+import androidx.core.view.setPadding
+import com.android.launcher3.R
+import com.android.launcher3.Utilities.dpToPx
+import com.android.launcher3.config.FeatureFlags
+import com.android.launcher3.taskbar.TaskbarActivityContext
+import com.android.launcher3.views.ActivityContext
+import com.android.launcher3.views.IconButtonView
+
+/** Taskbar all apps button container for customizable taskbar. */
+class TaskbarAllAppsButtonContainer
+@JvmOverloads
+constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+) : LinearLayout(context, attrs), TaskbarContainer {
+
+ private val allAppsButton: IconButtonView =
+ LayoutInflater.from(context).inflate(R.layout.taskbar_all_apps_button, this, false)
+ as IconButtonView
+ private val activityContext: TaskbarActivityContext = ActivityContext.lookupContext(context)
+
+ override val spaceNeeded: Int
+ get() {
+ return dpToPx(activityContext.taskbarSpecsEvaluator.taskbarIconSize.size.toFloat())
+ }
+
+ init {
+ setUpIcon()
+ }
+
+ @SuppressLint("UseCompatLoadingForDrawables", "ResourceAsColor")
+ private fun setUpIcon() {
+ val drawable =
+ resources.getDrawable(
+ getAllAppsButton(activityContext.taskbarFeatureEvaluator.isTransient)
+ )
+ val padding = activityContext.taskbarSpecsEvaluator.taskbarIconPadding
+
+ allAppsButton.setIconDrawable(drawable)
+ allAppsButton.setPadding(/* left= */ padding)
+ allAppsButton.setForegroundTint(activityContext.getColor(R.color.all_apps_button_color))
+
+ // TODO(jagrutdesai) : add click listeners in future cl
+ addView(allAppsButton)
+ }
+
+ @DrawableRes
+ private fun getAllAppsButton(isTransientTaskbar: Boolean): Int {
+ val shouldSelectTransientIcon =
+ isTransientTaskbar ||
+ (FeatureFlags.enableTaskbarPinning() && !activityContext.isThreeButtonNav)
+ return if (FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
+ if (shouldSelectTransientIcon) R.drawable.ic_transient_taskbar_all_apps_search_button
+ else R.drawable.ic_taskbar_all_apps_search_button
+ } else {
+ if (shouldSelectTransientIcon) R.drawable.ic_transient_taskbar_all_apps_button
+ else R.drawable.ic_taskbar_all_apps_button
+ }
+ }
+
+ @DimenRes
+ fun getAllAppsButtonTranslationXOffset(isTransientTaskbar: Boolean): Int {
+ return if (isTransientTaskbar) {
+ R.dimen.transient_taskbar_all_apps_button_translation_x_offset
+ } else if (FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
+ R.dimen.taskbar_all_apps_search_button_translation_x_offset
+ } else {
+ R.dimen.taskbar_all_apps_button_translation_x_offset
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarContainer.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarContainer.kt
index 3c4b63a..35ae43c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarContainer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarContainer.kt
@@ -13,15 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.launcher3.taskbar.customization
-/** Enums for all feature container that taskbar supports. */
-enum class TaskbarContainer {
- ALL_APPS,
- DIVIDER,
- APP_ICONS,
- RECENTS,
- NAV_BUTTONS,
- BUBBLES,
+import androidx.annotation.Dimension
+
+/**
+ * Interface to be implemented by all taskbar container to expose [spaceNeeded] for each container.
+ */
+interface TaskbarContainer {
+ @get:Dimension(unit = Dimension.DP) val spaceNeeded: Int
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarContainers.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarContainers.kt
new file mode 100644
index 0000000..d4548f5
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarContainers.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.customization
+
+/** Enums for all feature container that taskbar supports. */
+enum class TaskbarContainers {
+ ALL_APPS,
+ DIVIDER,
+ APP_ICONS,
+ RECENTS,
+ NAV_BUTTONS,
+ BUBBLES,
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarFeatureEvaluator.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarFeatureEvaluator.kt
index ac7dd06..c83ac50 100644
--- a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarFeatureEvaluator.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarFeatureEvaluator.kt
@@ -16,27 +16,43 @@
package com.android.launcher3.taskbar.customization
+import com.android.launcher3.Flags.enableRecentsInTaskbar
import com.android.launcher3.config.FeatureFlags.enableTaskbarPinning
import com.android.launcher3.taskbar.TaskbarActivityContext
-import com.android.launcher3.taskbar.TaskbarControllers
import com.android.launcher3.util.DisplayController
/** Evaluates all the features taskbar can have. */
-class TaskbarFeatureEvaluator(
+class TaskbarFeatureEvaluator
+private constructor(
private val taskbarActivityContext: TaskbarActivityContext,
- private val taskbarControllers: TaskbarControllers,
) {
+ companion object {
+ @Volatile private lateinit var taskbarFeatureEvaluator: TaskbarFeatureEvaluator
+
+ @JvmStatic
+ fun getInstance(
+ taskbarActivityContext: TaskbarActivityContext,
+ ): TaskbarFeatureEvaluator {
+ synchronized(this) {
+ if (!::taskbarFeatureEvaluator.isInitialized) {
+ taskbarFeatureEvaluator = TaskbarFeatureEvaluator(taskbarActivityContext)
+ }
+ return taskbarFeatureEvaluator
+ }
+ }
+ }
+
val hasAllApps = true
val hasAppIcons = true
val hasBubbles = false
val hasNavButtons = taskbarActivityContext.isThreeButtonNav
- val hasRecents: Boolean
- get() = taskbarControllers.taskbarRecentAppsController.shownTasks.isNotEmpty()
+ val isRecentsEnabled: Boolean
+ get() = enableRecentsInTaskbar()
val hasDivider: Boolean
- get() = enableTaskbarPinning() || hasRecents
+ get() = enableTaskbarPinning() || isRecentsEnabled
val isTransient: Boolean
get() = DisplayController.isTransientTaskbar(taskbarActivityContext)
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarIconSpecs.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarIconSpecs.kt
index 67bbcce..887eb01 100644
--- a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarIconSpecs.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarIconSpecs.kt
@@ -29,6 +29,13 @@
val defaultPersistentIconSize = iconSize40dp
val defaultTransientIconSize = iconSize44dp
+ val minimumIconSize = iconSize40dp
+
+ val defaultPersistentIconMargin = TaskbarIconMarginSize(6)
+ val defaultTransientIconMargin = TaskbarIconMarginSize(12)
+
+ val minimumTaskbarIconTouchSize = TaskbarIconSize(48)
+
val transientTaskbarIconSizeByGridSize =
mapOf(
TransientTaskbarIconSizeKey(6, 5, false) to iconSize52dp,
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt
index 0b7be40..761b47e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt
@@ -16,8 +16,33 @@
package com.android.launcher3.taskbar.customization
+import com.android.launcher3.taskbar.TaskbarActivityContext
+
/** Evaluates the taskbar specs based on the taskbar grid size and the taskbar icon size. */
-class TaskbarSpecsEvaluator(private val taskbarFeatureEvaluator: TaskbarFeatureEvaluator) {
+class TaskbarSpecsEvaluator(
+ private val taskbarActivityContext: TaskbarActivityContext,
+ private val taskbarFeatureEvaluator: TaskbarFeatureEvaluator,
+ numRows: Int = taskbarActivityContext.deviceProfile.inv.numRows,
+ numColumns: Int = taskbarActivityContext.deviceProfile.inv.numColumns,
+) {
+ var taskbarIconSize: TaskbarIconSize = getIconSizeByGrid(numRows, numColumns)
+
+ // TODO(b/341146605) : initialize it to taskbar container in later cl.
+ private var taskbarContainer: List<TaskbarContainer> = emptyList()
+
+ val taskbarIconPadding: Int =
+ if (TaskbarIconSpecs.minimumTaskbarIconTouchSize.size > taskbarIconSize.size) {
+ (TaskbarIconSpecs.minimumTaskbarIconTouchSize.size - taskbarIconSize.size) / 2
+ } else {
+ 0
+ }
+
+ val taskbarIconMargin: TaskbarIconMarginSize =
+ if (taskbarFeatureEvaluator.isTransient) {
+ TaskbarIconSpecs.defaultTransientIconMargin
+ } else {
+ TaskbarIconSpecs.defaultPersistentIconMargin
+ }
fun getIconSizeByGrid(row: Int, column: Int): TaskbarIconSize {
return if (taskbarFeatureEvaluator.isTransient) {
@@ -36,8 +61,11 @@
val currentIconSizeIndex = TaskbarIconSpecs.transientTaskbarIconSizes.indexOf(iconSize)
// return the current icon size if supplied icon size is unknown or we have reached the
// min icon size.
- return if (currentIconSizeIndex == -1 || currentIconSizeIndex == 0) iconSize
- else TaskbarIconSpecs.transientTaskbarIconSizes[currentIconSizeIndex - 1]
+ return if (currentIconSizeIndex == -1 || currentIconSizeIndex == 0) {
+ iconSize
+ } else {
+ TaskbarIconSpecs.transientTaskbarIconSizes[currentIconSizeIndex - 1]
+ }
}
fun getIconSizeStepUp(iconSize: TaskbarIconSize): TaskbarIconSize {
@@ -52,11 +80,28 @@
) {
iconSize
} else {
- TaskbarIconSpecs.transientTaskbarIconSizes.get(currentIconSizeIndex + 1)
+ TaskbarIconSpecs.transientTaskbarIconSizes[currentIconSizeIndex + 1]
}
}
+
+ // TODO(jagrutdesai) : Call this in init once the containers are ready.
+ private fun calculateTaskbarIconSize() {
+ while (
+ taskbarIconSize != TaskbarIconSpecs.minimumIconSize &&
+ taskbarActivityContext.transientTaskbarBounds.width() <
+ calculateSpaceNeeded(taskbarContainer)
+ ) {
+ taskbarIconSize = getIconSizeStepDown(taskbarIconSize)
+ }
+ }
+
+ private fun calculateSpaceNeeded(containers: List<TaskbarContainer>): Int {
+ return containers.sumOf { it.spaceNeeded }
+ }
}
data class TaskbarIconSize(val size: Int)
data class TransientTaskbarIconSizeKey(val row: Int, val column: Int, val isLandscape: Boolean)
+
+data class TaskbarIconMarginSize(val size: Int)
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index 0368f3a..3a39cf2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -110,6 +110,7 @@
// taskbar icons disappearing before hotseat icons show up.
float scrimUpperBoundFromSplit =
QuickstepTransitionManager.getTaskbarToHomeDuration() / (float) config.duration;
+ scrimUpperBoundFromSplit = Math.min(scrimUpperBoundFromSplit, 1f);
config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, clampToProgress(LINEAR, 0, 0.25f));
config.setInterpolator(ANIM_SCRIM_FADE,
fromState == OVERVIEW_SPLIT_SELECT
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 867533c..f020c8f 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -152,8 +152,6 @@
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.startingsurface.SplashScreenExitAnimationUtils;
-import kotlin.Unit;
-
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -163,6 +161,8 @@
import java.util.OptionalInt;
import java.util.function.Consumer;
+import kotlin.Unit;
+
/**
* Handles the navigation gestures when Launcher is the default home activity.
*/
@@ -2423,34 +2423,42 @@
if (mRecentsAnimationController == null) {
return;
}
+ final Runnable onFinishComplete = () -> {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "AbsSwipeUpHandler.onTasksAppeared: ")
+ .append("force finish recents animation complete; clearing state callback."));
+ mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
+ };
+ ActiveGestureLog.CompoundString forceFinishReason = new ActiveGestureLog.CompoundString(
+ "Forcefully finishing recents animation: ");
if (!mStateCallback.hasStates(STATE_GESTURE_COMPLETED)
&& !hasStartedTaskBefore(appearedTaskTargets)) {
// This is a special case, if a task is started mid-gesture that wasn't a part of a
// previous quickswitch task launch, then cancel the animation back to the app
RemoteAnimationTarget appearedTaskTarget = appearedTaskTargets[0];
TaskInfo taskInfo = appearedTaskTarget.taskInfo;
- ActiveGestureLog.INSTANCE.addLog(
- new ActiveGestureLog.CompoundString("Unexpected task appeared")
- .append(" id=")
+ ActiveGestureLog.INSTANCE.addLog(forceFinishReason
+ .append("Unexpected task appeared id=")
.append(taskInfo.taskId)
.append(" pkg=")
.append(taskInfo.baseIntent.getComponent().getPackageName()));
- finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
+ finishRecentsAnimationOnTasksAppeared(onFinishComplete);
return;
}
ActiveGestureLog.CompoundString handleTaskFailureReason =
new ActiveGestureLog.CompoundString("handleTaskAppeared check failed: ");
if (!handleTaskAppeared(appearedTaskTargets, handleTaskFailureReason)) {
- ActiveGestureLog.INSTANCE.addLog(handleTaskFailureReason);
- finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
+ ActiveGestureLog.INSTANCE.addLog(forceFinishReason.append(handleTaskFailureReason));
+ finishRecentsAnimationOnTasksAppeared(onFinishComplete);
return;
}
RemoteAnimationTarget[] taskTargets = Arrays.stream(appearedTaskTargets)
.filter(mGestureState.mLastStartedTaskIdPredicate)
.toArray(RemoteAnimationTarget[]::new);
if (taskTargets.length == 0) {
- ActiveGestureLog.INSTANCE.addLog("No appeared task matching started task id");
- finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
+ ActiveGestureLog.INSTANCE.addLog(
+ forceFinishReason.append("No appeared task matching started task id"));
+ finishRecentsAnimationOnTasksAppeared(onFinishComplete);
return;
}
RemoteAnimationTarget taskTarget = taskTargets[0];
@@ -2458,13 +2466,13 @@
? null : mRecentsView.getTaskViewByTaskId(taskTarget.taskId);
if (taskView == null || taskView.getTaskContainers().stream().noneMatch(
TaskContainer::getShouldShowSplashView)) {
- ActiveGestureLog.INSTANCE.addLog("Splash not needed");
- finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
+ ActiveGestureLog.INSTANCE.addLog(forceFinishReason.append("Splash not needed"));
+ finishRecentsAnimationOnTasksAppeared(onFinishComplete);
return;
}
if (mContainer == null) {
- ActiveGestureLog.INSTANCE.addLog("Activity destroyed");
- finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
+ ActiveGestureLog.INSTANCE.addLog(forceFinishReason.append("Activity destroyed"));
+ finishRecentsAnimationOnTasksAppeared(onFinishComplete);
return;
}
animateSplashScreenExit(mContainer, appearedTaskTargets, taskTargets);
diff --git a/quickstep/src/com/android/quickstep/BaseContainerInterface.java b/quickstep/src/com/android/quickstep/BaseContainerInterface.java
index b1d511c..3a8c141 100644
--- a/quickstep/src/com/android/quickstep/BaseContainerInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseContainerInterface.java
@@ -42,6 +42,7 @@
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.taskbar.TaskbarUIController;
import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.WindowBounds;
import com.android.launcher3.views.ScrimView;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.util.ActivityInitListener;
@@ -51,6 +52,7 @@
import com.android.systemui.shared.recents.model.ThumbnailData;
import java.util.HashMap;
+import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -269,8 +271,11 @@
} else {
Rect portraitInsets = dp.getInsets();
DisplayController displayController = DisplayController.INSTANCE.get(context);
- Rect deviceRotationInsets = displayController.getInfo().getCurrentBounds().get(
- orientationHandler.getRotation()).insets;
+ @Nullable List<WindowBounds> windowBounds =
+ displayController.getInfo().getCurrentBounds();
+ Rect deviceRotationInsets = windowBounds != null
+ ? windowBounds.get(orientationHandler.getRotation()).insets
+ : new Rect();
// Obtain the landscape/seascape insets, and rotate it to portrait perspective.
orientationHandler.rotateInsets(deviceRotationInsets, outRect);
// Then combine with portrait's insets to leave space for status bar/nav bar in
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 1048ea1..b564fa7 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -212,6 +212,9 @@
if (launcher.isStarted() && (isInLiveTileMode() || launcher.hasBeenResumed())) {
return launcher;
}
+ if (isInMinusOne()) {
+ return launcher;
+ }
return null;
}
@@ -289,6 +292,15 @@
&& TopTaskTracker.INSTANCE.get(launcher).getCachedTopTask(false).isHomeTask();
}
+ private boolean isInMinusOne() {
+ QuickstepLauncher launcher = getCreatedContainer();
+
+ return launcher != null
+ && launcher.getStateManager().getState() == NORMAL
+ && !launcher.isStarted()
+ && TopTaskTracker.INSTANCE.get(launcher).getCachedTopTask(false).isHomeTask();
+ }
+
@Override
public void onLaunchTaskFailed() {
QuickstepLauncher launcher = getCreatedContainer();
diff --git a/quickstep/src/com/android/quickstep/OrientationRectF.java b/quickstep/src/com/android/quickstep/OrientationRectF.java
index aa01b05..2b7ecb2 100644
--- a/quickstep/src/com/android/quickstep/OrientationRectF.java
+++ b/quickstep/src/com/android/quickstep/OrientationRectF.java
@@ -67,13 +67,15 @@
}
public boolean applyTransform(MotionEvent event, int deltaRotation, boolean forceTransform) {
+ if (deltaRotation == 0) {
+ return contains(event.getX(), event.getY());
+ }
mTmpMatrix.reset();
postDisplayRotation(deltaRotation, mHeight, mWidth, mTmpMatrix);
if (forceTransform) {
if (DEBUG) {
Log.d(TAG, "Transforming rotation due to forceTransform, "
+ "deltaRotation: " + deltaRotation
- + "mRotation: " + mRotation
+ " this: " + this);
}
event.applyTransform(mTmpMatrix);
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 9c64576..d82426f 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -21,6 +21,7 @@
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.systemui.shared.system.PackageManagerWrapper.ACTION_PREFERRED_ACTIVITY_CHANGED;
import android.content.ActivityNotFoundException;
@@ -55,10 +56,11 @@
public final class OverviewComponentObserver {
private static final String TAG = "OverviewComponentObserver";
+ // We register broadcast receivers on main thread to avoid missing updates.
private final SimpleBroadcastReceiver mUserPreferenceChangeReceiver =
- new SimpleBroadcastReceiver(this::updateOverviewTargets);
+ new SimpleBroadcastReceiver(MAIN_EXECUTOR, this::updateOverviewTargets);
private final SimpleBroadcastReceiver mOtherHomeAppUpdateReceiver =
- new SimpleBroadcastReceiver(this::updateOverviewTargets);
+ new SimpleBroadcastReceiver(MAIN_EXECUTOR, this::updateOverviewTargets);
private final Context mContext;
private final RecentsAnimationDeviceState mDeviceState;
@@ -102,7 +104,7 @@
mConfigChangesMap.append(fallbackComponent.hashCode(), fallbackInfo.configChanges);
} catch (PackageManager.NameNotFoundException ignored) { /* Impossible */ }
- mUserPreferenceChangeReceiver.registerAsync(mContext, ACTION_PREFERRED_ACTIVITY_CHANGED);
+ mUserPreferenceChangeReceiver.register(mContext, ACTION_PREFERRED_ACTIVITY_CHANGED);
updateOverviewTargets();
}
@@ -191,7 +193,7 @@
unregisterOtherHomeAppUpdateReceiver();
mUpdateRegisteredPackage = defaultHome.getPackageName();
- mOtherHomeAppUpdateReceiver.registerPkgActionsAsync(
+ mOtherHomeAppUpdateReceiver.registerPkgActions(
mContext, mUpdateRegisteredPackage, ACTION_PACKAGE_ADDED,
ACTION_PACKAGE_CHANGED, ACTION_PACKAGE_REMOVED);
}
@@ -203,13 +205,13 @@
* Clean up any registered receivers.
*/
public void onDestroy() {
- mUserPreferenceChangeReceiver.unregisterReceiverSafelyAsync(mContext);
+ mUserPreferenceChangeReceiver.unregisterReceiverSafely(mContext);
unregisterOtherHomeAppUpdateReceiver();
}
private void unregisterOtherHomeAppUpdateReceiver() {
if (mUpdateRegisteredPackage != null) {
- mOtherHomeAppUpdateReceiver.unregisterReceiverSafelyAsync(mContext);
+ mOtherHomeAppUpdateReceiver.unregisterReceiverSafely(mContext);
mUpdateRegisteredPackage = null;
}
}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index a7d3890..cd62265 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -33,7 +33,6 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
@@ -302,6 +301,10 @@
return mNavBarPosition;
}
+ public NavigationMode getMode() {
+ return mMode;
+ }
+
/**
* @return whether the current nav mode is fully gestural.
*/
@@ -413,8 +416,7 @@
| SYSUI_STATE_QUICK_SETTINGS_EXPANDED
| SYSUI_STATE_MAGNIFICATION_OVERLAP
| SYSUI_STATE_DEVICE_DREAMING
- | SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION
- | SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING;
+ | SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION;
return (gestureDisablingStates & mSystemUiStateFlags) == 0 && homeOrOverviewEnabled;
}
diff --git a/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java
index 29a57fc..5264643 100644
--- a/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java
@@ -34,12 +34,18 @@
private final Context mContext;
private OrientationRectF mOrientationRectF;
+ private OrientationRectF mTouchingOrientationRectF;
+ private int mViewRotation;
public SimpleOrientationTouchTransformer(Context context) {
+ this(context, DisplayController.INSTANCE.get(context));
+ }
+
+ @androidx.annotation.VisibleForTesting
+ public SimpleOrientationTouchTransformer(Context context, DisplayController displayController) {
mContext = context;
- DisplayController.INSTANCE.get(context).addChangeListener(this);
- onDisplayInfoChanged(context, DisplayController.INSTANCE.get(context).getInfo(),
- CHANGE_ALL);
+ displayController.addChangeListener(this);
+ onDisplayInfoChanged(context, displayController.getInfo(), CHANGE_ALL);
}
@Override
@@ -56,7 +62,29 @@
info.rotation);
}
+ /**
+ * Called when the touch is started. This preserves the touching orientation until the touch is
+ * done (i.e. ACTION_CANCEL or ACTION_UP). So the transform won't produce inconsistent position
+ * if display is changed during the touch.
+ */
+ public void updateTouchingOrientation(int viewRotation) {
+ mViewRotation = viewRotation;
+ mTouchingOrientationRectF = new OrientationRectF(mOrientationRectF.left,
+ mOrientationRectF.top, mOrientationRectF.right, mOrientationRectF.bottom,
+ mOrientationRectF.getRotation());
+ }
+
+ /** Called when the touch is finished. */
+ public void clearTouchingOrientation() {
+ mTouchingOrientationRectF = null;
+ }
+
public void transform(MotionEvent ev, int rotation) {
+ if (mTouchingOrientationRectF != null) {
+ mTouchingOrientationRectF.applyTransformToRotation(ev, mViewRotation,
+ true /* forceTransform */);
+ return;
+ }
mOrientationRectF.applyTransformToRotation(ev, rotation, true /* forceTransform */);
}
}
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index ba33c62..f813d9a 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -26,6 +26,7 @@
import android.graphics.Matrix.ScaleToFit;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.util.Log;
import android.view.RemoteAnimationTarget;
import androidx.annotation.NonNull;
@@ -381,6 +382,8 @@
protected class SpringAnimationRunner extends AnimationSuccessListener
implements RectFSpringAnim.OnUpdateListener, BuilderProxy {
+ private static final String TAG = "SpringAnimationRunner";
+
final Rect mCropRect = new Rect();
final Matrix mMatrix = new Matrix();
@@ -481,10 +484,26 @@
return;
}
mTargetTaskView.setAlpha(mAnimationFactory.isAnimatingIntoIcon() ? 1f : alpha);
- float width = mThumbnailStartBounds.width();
- float height = mThumbnailStartBounds.height();
- float scale = Math.min(currentRect.width(), currentRect.height())
- / Math.min(width, height);
+ float startWidth = mThumbnailStartBounds.width();
+ float startHeight = mThumbnailStartBounds.height();
+ float currentWidth = currentRect.width();
+ float currentHeight = currentRect.height();
+ float scale;
+
+ boolean isStartWidthValid = Float.compare(startWidth, 0f) > 0;
+ boolean isStartHeightValid = Float.compare(startHeight, 0f) > 0;
+ if (isStartWidthValid && isStartHeightValid) {
+ scale = Math.min(currentWidth, currentHeight) / Math.min(startWidth, startHeight);
+ } else {
+ Log.e(TAG, "TaskView starting bounds are invalid: " + mThumbnailStartBounds);
+ if (isStartWidthValid) {
+ scale = currentWidth / startWidth;
+ } else if (isStartHeightValid) {
+ scale = currentHeight / startHeight;
+ } else {
+ scale = 1f;
+ }
+ }
mTargetTaskView.setScaleX(scale);
mTargetTaskView.setScaleY(scale);
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 433baa9..66aa897 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -229,6 +229,17 @@
}
@Override
+ public void onImeSwitcherLongPress() {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.onImeSwitcherLongPress();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call onImeSwitcherLongPress");
+ }
+ }
+ }
+
+ @Override
public void setHomeRotationEnabled(boolean enabled) {
if (mSystemUiProxy != null) {
try {
@@ -831,12 +842,14 @@
/**
* Tells SysUI to dismiss the bubble with the provided key.
+ *
* @param key the key of the bubble to dismiss.
+ * @param timestamp the timestamp when the removal happened.
*/
- public void dragBubbleToDismiss(String key) {
+ public void dragBubbleToDismiss(String key, long timestamp) {
if (mBubbles == null) return;
try {
- mBubbles.dragBubbleToDismiss(key);
+ mBubbles.dragBubbleToDismiss(key, timestamp);
} catch (RemoteException e) {
Log.w(TAG, "Failed call dragBubbleToDismiss");
}
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java
index b3a9199..1f6c02c 100644
--- a/quickstep/src/com/android/quickstep/TaskIconCache.java
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.java
@@ -33,6 +33,7 @@
import android.text.TextUtils;
import android.util.SparseArray;
+import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import com.android.launcher3.R;
@@ -48,6 +49,7 @@
import com.android.launcher3.util.DisplayController.Info;
import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.Preconditions;
+import com.android.quickstep.task.thumbnail.data.TaskIconDataSource;
import com.android.quickstep.util.TaskKeyLruCache;
import com.android.quickstep.util.TaskVisualsChangeListener;
import com.android.systemui.shared.recents.model.Task;
@@ -59,7 +61,7 @@
/**
* Manages the caching of task icons and related data.
*/
-public class TaskIconCache implements DisplayInfoChangeListener {
+public class TaskIconCache implements TaskIconDataSource, DisplayInfoChangeListener {
private final Executor mBgExecutor;
@@ -102,7 +104,8 @@
* @param callback The callback to receive the task after its data has been populated.
* @return A cancelable handle to the request
*/
- public CancellableTask getIconInBackground(Task task, GetTaskIconCallback callback) {
+ @Override
+ public CancellableTask getIconInBackground(Task task, @NonNull GetTaskIconCallback callback) {
Preconditions.assertUIThread();
if (task.icon != null) {
// Nothing to load, the icon is already loaded
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index ee93cd6..2896979 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -42,6 +42,7 @@
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_DOWN;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_MOVE;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_UP;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.NAVIGATION_MODE_SWITCHED;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.RECENTS_ANIMATION_START_PENDING;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
@@ -70,7 +71,6 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
-import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
import android.util.ArraySet;
@@ -102,6 +102,7 @@
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.LockedUserState;
+import com.android.launcher3.util.NavigationMode;
import com.android.launcher3.util.PluginManagerWrapper;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.ScreenOnTracker;
@@ -622,6 +623,8 @@
private InputManager mInputManager;
private final Set<Integer> mTrackpadsConnected = new ArraySet<>();
+ private NavigationMode mGestureStartNavMode = null;
+
@Override
public void onCreate() {
super.onCreate();
@@ -836,14 +839,26 @@
TestLogging.recordMotionEvent(
TestProtocol.SEQUENCE_TIS, "TouchInteractionService.onInputEvent", event);
- boolean isUserUnlocked = LockedUserState.get(this).isUserUnlocked();
- if (!isUserUnlocked || (mDeviceState.isButtonNavMode()
- && !isTrackpadMotionEvent(event))) {
+ if (!LockedUserState.get(this).isUserUnlocked()) {
+ ActiveGestureLog.INSTANCE.addLog(new CompoundString("TIS.onInputEvent: ")
+ .append("Cannot process input event: user is locked"));
+ return;
+ }
+
+ NavigationMode currentNavMode = mDeviceState.getMode();
+ if (mGestureStartNavMode != null && mGestureStartNavMode != currentNavMode) {
+ ActiveGestureLog.INSTANCE.addLog(new CompoundString("TIS.onInputEvent: ")
+ .append("Navigation mode switched mid-gesture (")
+ .append(mGestureStartNavMode.name())
+ .append(" -> ")
+ .append(currentNavMode.name())
+ .append("); cancelling gesture."),
+ NAVIGATION_MODE_SWITCHED);
+ event.setAction(ACTION_CANCEL);
+ } else if (mDeviceState.isButtonNavMode() && !isTrackpadMotionEvent(event)) {
ActiveGestureLog.INSTANCE.addLog(new CompoundString("TIS.onInputEvent: ")
.append("Cannot process input event: ")
- .append(!isUserUnlocked
- ? "user is locked"
- : "using 3-button nav and event is not a trackpad event"));
+ .append("using 3-button nav and event is not a trackpad event"));
return;
}
@@ -870,6 +885,12 @@
}
}
+ if (action == ACTION_DOWN || isHoverActionWithoutConsumer) {
+ mGestureStartNavMode = currentNavMode;
+ } else if (action == ACTION_UP || action == ACTION_CANCEL) {
+ mGestureStartNavMode = null;
+ }
+
SafeCloseable traceToken = TraceHelper.INSTANCE.allowIpcs("TIS.onInputEvent");
CompoundString reasonString = action == ACTION_DOWN
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 0d450c6..13b6447 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -70,6 +70,9 @@
*/
public class OtherActivityInputConsumer extends ContextWrapper implements InputConsumer {
+ private static final String TAG = "OtherActivityInputConsumer";
+ private static final boolean DEBUG = true;
+
public static final String DOWN_EVT = "OtherActivityInputConsumer.DOWN";
private static final String UP_EVT = "OtherActivityInputConsumer.UP";
@@ -230,6 +233,9 @@
// Start the window animation on down to give more time for launcher to draw if the
// user didn't start the gesture over the back button
+ if (DEBUG) {
+ Log.d(TAG, "ACTION_DOWN: mIsDeferredDownTarget=" + mIsDeferredDownTarget);
+ }
if (!mIsDeferredDownTarget) {
startTouchTrackingForWindowAnimation(ev.getEventTime());
}
@@ -284,9 +290,18 @@
float horizontalDist = Math.abs(displacementX);
float upDist = -displacement;
- boolean passedSlop = mGestureState.isTrackpadGesture()
- || (squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop
- && !mGestureState.isInExtendedSlopRegion());
+ boolean isTrackpadGesture = mGestureState.isTrackpadGesture();
+ float squaredHypot = squaredHypot(displacementX, displacementY);
+ boolean isInExtendedSlopRegion = !mGestureState.isInExtendedSlopRegion();
+ boolean passedSlop = isTrackpadGesture
+ || (squaredHypot >= mSquaredTouchSlop
+ && isInExtendedSlopRegion);
+ if (DEBUG) {
+ Log.d(TAG, "ACTION_MOVE: passedSlop=" + passedSlop
+ + " ( " + isTrackpadGesture
+ + " || (" + squaredHypot + " >= " + mSquaredTouchSlop
+ + " && " + isInExtendedSlopRegion + " ))");
+ }
if (!mPassedSlopOnThisGesture && passedSlop) {
mPassedSlopOnThisGesture = true;
@@ -306,6 +321,9 @@
boolean isLikelyToStartNewTask =
haveNotPassedSlopOnContinuedGesture || swipeWithinQuickSwitchRange;
+ if (DEBUG) {
+ Log.d(TAG, "ACTION_MOVE: mPassedPilferInputSlop=" + mPassedPilferInputSlop);
+ }
if (!mPassedPilferInputSlop) {
if (passedSlop) {
// Horizontal gesture is not allowed in this region
@@ -394,6 +412,10 @@
mInteractionHandler.initWhenReady(
"OtherActivityInputConsumer.startTouchTrackingForWindowAnimation");
+ if (DEBUG) {
+ Log.d(TAG, "startTouchTrackingForWindowAnimation: isRecentsAnimationRunning="
+ + mTaskAnimationManager.isRecentsAnimationRunning());
+ }
if (mTaskAnimationManager.isRecentsAnimationRunning()) {
mActiveCallbacks = mTaskAnimationManager.continueRecentsAnimation(mGestureState);
mActiveCallbacks.removeListener(mCleanupHandler);
@@ -422,6 +444,11 @@
*/
private void finishTouchTracking(MotionEvent ev) {
TraceHelper.INSTANCE.beginSection(UP_EVT);
+ if (DEBUG) {
+ Log.d(TAG, "finishTouchTracking: mPassedWindowMoveSlop=" + mPassedWindowMoveSlop);
+ Log.d(TAG, "finishTouchTracking: mInteractionHandler=" + mInteractionHandler);
+ Log.d(TAG, "finishTouchTracking: ev=" + ev);
+ }
boolean isCanceled = ev.getActionMasked() == ACTION_CANCEL;
if (mPassedWindowMoveSlop && mInteractionHandler != null) {
@@ -444,7 +471,14 @@
// Since we start touch tracking on DOWN, we may reach this state without actually
// starting the gesture. In that case, we need to clean-up an unfinished or un-started
// animation.
+ if (DEBUG) {
+ Log.d(TAG, "finishTouchTracking: mActiveCallbacks=" + mActiveCallbacks);
+ }
if (mActiveCallbacks != null && mInteractionHandler != null) {
+ if (DEBUG) {
+ Log.d(TAG, "finishTouchTracking: isRecentsAnimationRunning="
+ + mTaskAnimationManager.isRecentsAnimationRunning());
+ }
if (mTaskAnimationManager.isRecentsAnimationRunning()) {
// The animation started, but with no movement, in this case, there will be no
// animateToProgress so we have to manually finish here. In the case of
@@ -535,7 +569,13 @@
public void onRecentsAnimationStart(RecentsAnimationController controller,
RecentsAnimationTargets targets) {
+ if (DEBUG) {
+ Log.d(TAG, "FinishImmediatelyHandler: queuing callback");
+ }
Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
+ if (DEBUG) {
+ Log.d(TAG, "FinishImmediatelyHandler: running callback");
+ }
controller.finish(false /* toRecents */, null);
});
}
diff --git a/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt
index c1eef0b..9c4248c 100644
--- a/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt
@@ -17,6 +17,7 @@
package com.android.quickstep.recents.data
import com.android.systemui.shared.recents.model.Task
+import com.android.systemui.shared.recents.model.ThumbnailData
import kotlinx.coroutines.flow.Flow
interface RecentTasksRepository {
@@ -25,11 +26,17 @@
/**
* Gets the data associated with a task that has id [taskId]. Flow will settle on null if the
- * task was not found.
+ * task was not found. [Task.thumbnail] will settle on null if task is invisible.
*/
fun getTaskDataById(taskId: Int): Flow<Task?>
/**
+ * Gets the [ThumbnailData] associated with a task that has id [taskId]. Flow will settle on
+ * null if the task was not found or is invisible.
+ */
+ fun getThumbnailById(taskId: Int): Flow<ThumbnailData?>
+
+ /**
* Sets the tasks that are visible, indicating that properties relating to visuals need to be
* populated e.g. icons/thumbnails etc.
*/
diff --git a/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepository.kt b/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepository.kt
new file mode 100644
index 0000000..adf904c
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepository.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.data
+
+import com.android.quickstep.views.RecentsViewContainer
+
+/**
+ * Repository for shrink down version of [com.android.launcher3.DeviceProfile] that only contains
+ * data related to Recents.
+ */
+class RecentsDeviceProfileRepository(private val container: RecentsViewContainer) {
+
+ fun getRecentsDeviceProfile() =
+ with(container.deviceProfile) { RecentsDeviceProfile(isLargeScreen = isTablet) }
+
+ /**
+ * Container to hold [com.android.launcher3.DeviceProfile] related to Recents.
+ *
+ * @property isLargeScreen whether the current device posture has a large screen
+ */
+ data class RecentsDeviceProfile(val isLargeScreen: Boolean)
+}
diff --git a/quickstep/src/com/android/quickstep/recents/data/RecentsRotationStateRepository.kt b/quickstep/src/com/android/quickstep/recents/data/RecentsRotationStateRepository.kt
new file mode 100644
index 0000000..6ead704
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/data/RecentsRotationStateRepository.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.data
+
+import com.android.quickstep.util.RecentsOrientedState
+
+/**
+ * Repository for [RecentsRotationState] which holds orientation/rotation related information
+ * related to Recents
+ */
+class RecentsRotationStateRepository(private val state: RecentsOrientedState) {
+ fun getRecentsRotationState() =
+ with(state) { RecentsRotationState(activityRotation = recentsActivityRotation) }
+
+ /**
+ * Container to hold orientation/rotation related information related to Recents.
+ *
+ * @property activityRotation rotation of the activity hosting RecentsView
+ */
+ data class RecentsRotationState(val activityRotation: Int)
+}
diff --git a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
index 9f3ef4a..f73db5a 100644
--- a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
@@ -16,7 +16,8 @@
package com.android.quickstep.recents.data
-import com.android.quickstep.TaskIconCache
+import android.graphics.drawable.Drawable
+import com.android.quickstep.task.thumbnail.data.TaskIconDataSource
import com.android.quickstep.task.thumbnail.data.TaskThumbnailDataSource
import com.android.quickstep.util.GroupTask
import com.android.systemui.shared.recents.model.Task
@@ -27,6 +28,7 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
@@ -37,7 +39,7 @@
class TasksRepository(
private val recentsModel: RecentTasksDataSource,
private val taskThumbnailDataSource: TaskThumbnailDataSource,
- private val taskIconCache: TaskIconCache,
+ private val taskIconDataSource: TaskIconDataSource,
) : RecentTasksRepository {
private val groupedTaskData = MutableStateFlow(emptyList<GroupTask>())
private val _taskData =
@@ -45,10 +47,19 @@
private val visibleTaskIds = MutableStateFlow(emptySet<Int>())
private val taskData: Flow<List<Task>> =
- combine(_taskData, getThumbnailQueryResults()) { tasks, results ->
+ combine(_taskData, getThumbnailQueryResults(), getIconQueryResults()) {
+ tasks,
+ thumbnailQueryResults,
+ iconQueryResults ->
tasks.forEach { task ->
// Add retrieved thumbnails + remove unnecessary thumbnails
- task.thumbnail = results[task.key.id]
+ task.thumbnail = thumbnailQueryResults[task.key.id]
+
+ // TODO(b/352331675) don't load icons for DesktopTaskView
+ // Add retrieved icons + remove unnecessary icons
+ task.icon = iconQueryResults[task.key.id]?.icon
+ task.titleDescription = iconQueryResults[task.key.id]?.contentDescription
+ task.title = iconQueryResults[task.key.id]?.title
}
tasks
}
@@ -63,6 +74,9 @@
override fun getTaskDataById(taskId: Int): Flow<Task?> =
taskData.map { taskList -> taskList.firstOrNull { it.key.id == taskId } }
+ override fun getThumbnailById(taskId: Int): Flow<ThumbnailData?> =
+ getTaskDataById(taskId).map { it?.thumbnail }.distinctUntilChangedBy { it?.snapshotId }
+
override fun setVisibleTasks(visibleTaskIdList: List<Int>) {
this.visibleTaskIds.value = visibleTaskIdList.toSet()
}
@@ -75,7 +89,6 @@
suspendCancellableCoroutine { continuation ->
val cancellableTask =
taskThumbnailDataSource.getThumbnailInBackground(task) {
- task.thumbnail = it
continuation.resume(it)
}
continuation.invokeOnCancellation { cancellableTask?.cancel() }
@@ -105,6 +118,59 @@
}
}
}
+
+ /** Flow wrapper for [TaskThumbnailDataSource.getThumbnailInBackground] api */
+ private fun getIconDataRequest(task: Task): IconDataRequest =
+ flow {
+ emit(task.key.id to task.getTaskIconQueryResponse())
+ val iconDataResponse: TaskIconQueryResponse? =
+ suspendCancellableCoroutine { continuation ->
+ val cancellableTask =
+ taskIconDataSource.getIconInBackground(task) {
+ icon,
+ contentDescription,
+ title ->
+ continuation.resume(
+ TaskIconQueryResponse(icon, contentDescription, title)
+ )
+ }
+ continuation.invokeOnCancellation { cancellableTask?.cancel() }
+ }
+ emit(task.key.id to iconDataResponse)
+ }
+ .distinctUntilChanged()
+
+ private fun getIconQueryResults(): Flow<Map<Int, TaskIconQueryResponse?>> {
+ val visibleTasks =
+ combine(_taskData, visibleTaskIds) { tasks, visibleIds ->
+ tasks.filter { it.key.id in visibleIds }
+ }
+ val visibleIconDataRequests: Flow<List<IconDataRequest>> =
+ visibleTasks.map { visibleTasksList -> visibleTasksList.map(::getIconDataRequest) }
+ return visibleIconDataRequests.flatMapLatest { iconRequestFlows: List<IconDataRequest> ->
+ if (iconRequestFlows.isEmpty()) {
+ flowOf(emptyMap())
+ } else {
+ combine(iconRequestFlows) { it.toMap() }
+ }
+ }
+ }
}
-typealias ThumbnailDataRequest = Flow<Pair<Int, ThumbnailData?>>
+private data class TaskIconQueryResponse(
+ val icon: Drawable,
+ val contentDescription: String,
+ val title: String
+)
+
+private fun Task.getTaskIconQueryResponse(): TaskIconQueryResponse? {
+ val iconVal = icon ?: return null
+ val titleDescriptionVal = titleDescription ?: return null
+ val titleVal = title ?: return null
+
+ return TaskIconQueryResponse(iconVal, titleDescriptionVal, titleVal)
+}
+
+private typealias ThumbnailDataRequest = Flow<Pair<Int, ThumbnailData?>>
+
+private typealias IconDataRequest = Flow<Pair<Int, TaskIconQueryResponse?>>
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/LiveTileView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/LiveTileView.kt
new file mode 100644
index 0000000..45b3687
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/LiveTileView.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.Canvas
+import android.graphics.Paint
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffXfermode
+import android.util.AttributeSet
+import android.view.View
+
+class LiveTileView : View {
+ constructor(context: Context) : super(context)
+
+ constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+
+ constructor(
+ context: Context,
+ attrs: AttributeSet?,
+ defStyleAttr: Int,
+ ) : super(context, attrs, defStyleAttr)
+
+ override fun onDraw(canvas: Canvas) {
+ canvas.drawRect(0f, 0f, measuredWidth.toFloat(), measuredHeight.toFloat(), CLEAR_PAINT)
+ }
+
+ companion object {
+ private val CLEAR_PAINT =
+ Paint().apply { xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
index 20a081b..d22fc94 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
@@ -18,17 +18,17 @@
import android.content.Context
import android.content.res.Configuration
-import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Outline
-import android.graphics.Paint
-import android.graphics.PorterDuff
-import android.graphics.PorterDuffXfermode
import android.graphics.Rect
import android.util.AttributeSet
import android.view.View
import android.view.ViewOutlineProvider
+import android.widget.FrameLayout
+import android.widget.ImageView
import androidx.annotation.ColorInt
+import androidx.core.view.isVisible
+import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.util.ViewPool
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
@@ -40,10 +40,15 @@
import com.android.quickstep.views.RecentsViewContainer
import com.android.quickstep.views.TaskView
import com.android.systemui.shared.system.QuickStepContract
-import kotlinx.coroutines.MainScope
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.CoroutineName
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
-class TaskThumbnailView : View, ViewPool.Reusable {
+class TaskThumbnailView : FrameLayout, ViewPool.Reusable {
// TODO(b/335649589): Ideally create and obtain this from DI. This ViewModel should be scoped
// to [TaskView], and also shared between [TaskView] and [TaskThumbnailView]
// This is using a lazy for now because the dependencies cannot be obtained without DI.
@@ -52,19 +57,20 @@
RecentsViewContainer.containerFromContext<RecentsViewContainer>(context)
.getOverviewPanel<RecentsView<*, *>>()
TaskThumbnailViewModel(
- recentsView.mRecentsViewData,
+ recentsView.mRecentsViewData!!,
(parent as TaskView).taskViewData,
(parent as TaskView).getTaskContainerForTaskThumbnailView(this)!!.taskContainerData,
- recentsView.mTasksRepository,
+ recentsView.mTasksRepository!!,
)
}
+ private lateinit var viewAttachedScope: CoroutineScope
- private var uiState: TaskThumbnailUiState = Uninitialized
+ private val scrimView: View by lazy { findViewById(R.id.task_thumbnail_scrim) }
+ private val liveTileView: LiveTileView by lazy { findViewById(R.id.task_thumbnail_live_tile) }
+ private val thumbnail: ImageView by lazy { findViewById(R.id.task_thumbnail) }
+
private var inheritedScale: Float = 1f
- private var dimProgress: Float = 0f
- private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG)
- private val scrimPaint = Paint().apply { color = Color.BLACK }
private val _measuredBounds = Rect()
private val measuredBounds: Rect
get() {
@@ -75,39 +81,44 @@
private var overviewCornerRadius: Float = TaskCornerRadius.get(context)
private var fullscreenCornerRadius: Float = QuickStepContract.getWindowCornerRadius(context)
- constructor(context: Context?) : super(context)
+ constructor(context: Context) : super(context)
- constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
+ constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(
- context: Context?,
+ context: Context,
attrs: AttributeSet?,
defStyleAttr: Int,
) : super(context, attrs, defStyleAttr)
override fun onAttachedToWindow() {
super.onAttachedToWindow()
- // TODO(b/335396935) replace MainScope with shorter lifecycle.
- MainScope().launch {
- viewModel.uiState.collect { viewModelUiState ->
- uiState = viewModelUiState
- invalidate()
+ viewAttachedScope =
+ CoroutineScope(SupervisorJob() + Dispatchers.Main + CoroutineName("TaskThumbnailView"))
+ viewModel.uiState
+ .onEach { viewModelUiState ->
+ resetViews()
+ when (viewModelUiState) {
+ is Uninitialized -> {}
+ is LiveTile -> drawLiveWindow()
+ is Snapshot -> drawSnapshot(viewModelUiState)
+ is BackgroundOnly -> drawBackground(viewModelUiState.backgroundColor)
+ }
}
- }
- MainScope().launch {
- viewModel.dimProgress.collect { dimProgress ->
+ .launchIn(viewAttachedScope)
+ viewModel.dimProgress
+ .onEach { dimProgress ->
// TODO(b/348195366) Add fade in/out for scrim
- this@TaskThumbnailView.dimProgress = dimProgress
- invalidate()
+ scrimView.alpha = dimProgress * MAX_SCRIM_ALPHA
}
- }
- MainScope().launch { viewModel.cornerRadiusProgress.collect { invalidateOutline() } }
- MainScope().launch {
- viewModel.inheritedScale.collect { viewModelInheritedScale ->
+ .launchIn(viewAttachedScope)
+ viewModel.cornerRadiusProgress.onEach { invalidateOutline() }.launchIn(viewAttachedScope)
+ viewModel.inheritedScale
+ .onEach { viewModelInheritedScale ->
inheritedScale = viewModelInheritedScale
invalidateOutline()
}
- }
+ .launchIn(viewAttachedScope)
clipToOutline = true
outlineProvider =
@@ -118,27 +129,13 @@
}
}
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ viewAttachedScope.cancel("TaskThumbnailView detaching from window")
+ }
+
override fun onRecycle() {
// Do nothing
- uiState = Uninitialized
- }
-
- override fun onDraw(canvas: Canvas) {
- when (val uiStateVal = uiState) {
- is Uninitialized -> drawBackgroundOnly(canvas, Color.BLACK)
- is LiveTile -> drawTransparentUiState(canvas)
- is Snapshot -> drawSnapshotState(canvas, uiStateVal)
- is BackgroundOnly -> drawBackgroundOnly(canvas, uiStateVal.backgroundColor)
- }
-
- if (dimProgress > 0) {
- drawScrim(canvas)
- }
- }
-
- private fun drawBackgroundOnly(canvas: Canvas, @ColorInt backgroundColor: Int) {
- backgroundPaint.color = backgroundColor
- canvas.drawRect(measuredBounds, backgroundPaint)
}
override fun onConfigurationChanged(newConfig: Configuration?) {
@@ -149,18 +146,25 @@
invalidateOutline()
}
- private fun drawTransparentUiState(canvas: Canvas) {
- canvas.drawRect(measuredBounds, CLEAR_PAINT)
+ private fun resetViews() {
+ liveTileView.isVisible = false
+ thumbnail.isVisible = false
+ scrimView.alpha = 0f
+ setBackgroundColor(Color.BLACK)
}
- private fun drawSnapshotState(canvas: Canvas, snapshot: Snapshot) {
- drawBackgroundOnly(canvas, snapshot.backgroundColor)
- canvas.drawBitmap(snapshot.bitmap, snapshot.drawnRect, measuredBounds, null)
+ private fun drawBackground(@ColorInt background: Int) {
+ setBackgroundColor(background)
}
- private fun drawScrim(canvas: Canvas) {
- scrimPaint.alpha = (dimProgress * MAX_SCRIM_ALPHA).toInt()
- canvas.drawRect(measuredBounds, scrimPaint)
+ private fun drawLiveWindow() {
+ liveTileView.isVisible = true
+ }
+
+ private fun drawSnapshot(snapshot: Snapshot) {
+ drawBackground(snapshot.backgroundColor)
+ thumbnail.setImageBitmap(snapshot.bitmap)
+ thumbnail.isVisible = true
}
private fun getCurrentCornerRadius() =
@@ -170,9 +174,7 @@
fullscreenCornerRadius
) / inheritedScale
- companion object {
- private val CLEAR_PAINT =
- Paint().apply { xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) }
- private const val MAX_SCRIM_ALPHA = (0.4f * 255).toInt()
+ private companion object {
+ const val MAX_SCRIM_ALPHA = 0.4f
}
}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/data/TaskIconDataSource.kt b/quickstep/src/com/android/quickstep/task/thumbnail/data/TaskIconDataSource.kt
new file mode 100644
index 0000000..ab699c6
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/data/TaskIconDataSource.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.data
+
+import com.android.launcher3.util.CancellableTask
+import com.android.quickstep.TaskIconCache.GetTaskIconCallback
+import com.android.systemui.shared.recents.model.Task
+
+interface TaskIconDataSource {
+ fun getIconInBackground(task: Task, callback: GetTaskIconCallback): CancellableTask<*>?
+}
diff --git a/quickstep/src/com/android/quickstep/task/util/GetThumbnailUseCase.kt b/quickstep/src/com/android/quickstep/task/util/GetThumbnailUseCase.kt
new file mode 100644
index 0000000..e8dd04c3
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/task/util/GetThumbnailUseCase.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.util
+
+import android.graphics.Bitmap
+import com.android.quickstep.recents.data.RecentTasksRepository
+import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.runBlocking
+
+/** Use case for retrieving thumbnail. */
+class GetThumbnailUseCase(private val taskRepository: RecentTasksRepository) {
+ /** Returns the latest thumbnail associated with [taskId] if loaded, or null otherwise */
+ fun run(taskId: Int): Bitmap? = runBlocking {
+ taskRepository.getThumbnailById(taskId).firstOrNull()?.thumbnail
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt b/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt
index de39584..a9f7041 100644
--- a/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt
+++ b/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt
@@ -25,16 +25,20 @@
import com.android.quickstep.views.RecentsView
import com.android.quickstep.views.RecentsViewContainer
import com.android.systemui.shared.recents.model.Task
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.MainScope
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.CoroutineName
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
/**
* Helper for [TaskOverlayFactory.TaskOverlay] to interact with [TaskOverlayViewModel], this helper
* should merge with [TaskOverlayFactory.TaskOverlay] when it's migrated to MVVM.
*/
class TaskOverlayHelper(val task: Task, val overlay: TaskOverlayFactory.TaskOverlay<*>) {
- private lateinit var job: Job
+ private lateinit var overlayInitializedScope: CoroutineScope
private var uiState: TaskOverlayUiState = Disabled
// TODO(b/335649589): Ideally create and obtain this from DI. This ViewModel should be scoped
@@ -46,7 +50,7 @@
overlay.taskView.context
)
.getOverviewPanel<RecentsView<*, *>>()
- TaskOverlayViewModel(task, recentsView.mRecentsViewData, recentsView.mTasksRepository)
+ TaskOverlayViewModel(task, recentsView.mRecentsViewData!!, recentsView.mTasksRepository!!)
}
// TODO(b/331753115): TaskOverlay should listen for state changes and react.
@@ -54,32 +58,29 @@
get() = uiState as Enabled
fun init() {
- // TODO(b/335396935): This should be changed to TaskView's scope.
- job =
- MainScope().launch {
- taskOverlayViewModel.overlayState.collect {
- uiState = it
- if (it is Enabled) {
- Log.d(
- TAG,
- "initOverlay - taskId: ${task.key.id}, thumbnail: ${it.thumbnail}"
- )
- overlay.initOverlay(
- task,
- it.thumbnail,
- it.thumbnailMatrix,
- /* rotated= */ false
- )
- } else {
- Log.d(TAG, "reset - taskId: ${task.key.id}")
- overlay.reset()
- }
+ overlayInitializedScope =
+ CoroutineScope(SupervisorJob() + Dispatchers.Main + CoroutineName("TaskOverlayHelper"))
+ taskOverlayViewModel.overlayState
+ .onEach {
+ uiState = it
+ if (it is Enabled) {
+ Log.d(TAG, "initOverlay - taskId: ${task.key.id}, thumbnail: ${it.thumbnail}")
+ overlay.initOverlay(
+ task,
+ it.thumbnail,
+ it.thumbnailMatrix,
+ /* rotated= */ false
+ )
+ } else {
+ Log.d(TAG, "reset - taskId: ${task.key.id}")
+ overlay.reset()
}
}
+ .launchIn(overlayInitializedScope)
}
fun destroy() {
- job.cancel()
+ overlayInitializedScope.cancel()
uiState = Disabled
overlay.reset()
}
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt
index 4682323..47f32fb 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt
@@ -24,7 +24,6 @@
import com.android.systemui.shared.recents.model.Task
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.map
/** View model for TaskOverlay */
@@ -37,10 +36,7 @@
combine(
recentsViewData.overlayEnabled,
recentsViewData.settledFullyVisibleTaskIds.map { it.contains(task.key.id) },
- tasksRepository
- .getTaskDataById(task.key.id)
- .map { it?.thumbnail }
- .distinctUntilChangedBy { it?.snapshotId }
+ tasksRepository.getThumbnailById(task.key.id)
) { isOverlayEnabled, isFullyVisible, thumbnailData ->
if (isOverlayEnabled && isFullyVisible) {
Enabled(
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
index 3140fff..2398e66 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
@@ -40,7 +40,7 @@
SCROLLER_ANIMATION_ABORTED, TASK_APPEARED, EXPECTING_TASK_APPEARED,
FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER, LAUNCHER_DESTROYED, RECENT_TASKS_MISSING,
INVALID_VELOCITY_ON_SWIPE_UP, RECENTS_ANIMATION_START_PENDING,
- QUICK_SWITCH_FROM_HOME_FALLBACK, QUICK_SWITCH_FROM_HOME_FAILED,
+ QUICK_SWITCH_FROM_HOME_FALLBACK, QUICK_SWITCH_FROM_HOME_FAILED, NAVIGATION_MODE_SWITCHED,
/**
* These GestureEvents are specifically associated to state flags that get set in
@@ -299,6 +299,13 @@
+ "the current page index and index 0 were missing.",
writer);
break;
+ case NAVIGATION_MODE_SWITCHED:
+ errorDetected |= printErrorIfTrue(
+ true,
+ prefix,
+ /* errorMessage= */ "Navigation mode switched mid-gesture.",
+ writer);
+ break;
case EXPECTING_TASK_APPEARED:
case MOTION_DOWN:
case SET_END_TARGET:
diff --git a/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java b/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java
index c26fc0c5..38ae303 100644
--- a/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java
+++ b/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java
@@ -18,6 +18,8 @@
import static android.content.Intent.ACTION_TIMEZONE_CHANGED;
import static android.content.Intent.ACTION_TIME_CHANGED;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -50,7 +52,7 @@
private final Context mContext;
private final SimpleBroadcastReceiver mReceiver =
- new SimpleBroadcastReceiver(this::onClockEventReceived);
+ new SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, this::onClockEventReceived);
private final ArrayMap<BroadcastReceiver, Handler> mTimeEventReceivers = new ArrayMap<>();
private final List<ContentObserver> mFormatObservers = new ArrayList<>();
@@ -62,7 +64,7 @@
private AsyncClockEventDelegate(Context context) {
super(context);
mContext = context;
- mReceiver.registerAsync(mContext, ACTION_TIME_CHANGED, ACTION_TIMEZONE_CHANGED);
+ mReceiver.register(mContext, ACTION_TIME_CHANGED, ACTION_TIMEZONE_CHANGED);
}
@Override
@@ -123,6 +125,6 @@
public void close() {
mDestroyed = true;
SettingsCache.INSTANCE.get(mContext).unregister(mFormatUri, this);
- mReceiver.unregisterReceiverSafelyAsync(mContext);
+ mReceiver.unregisterReceiverSafely(mContext);
}
}
diff --git a/quickstep/src/com/android/quickstep/util/BorderAnimator.kt b/quickstep/src/com/android/quickstep/util/BorderAnimator.kt
index 85238ed..7e51fcf 100644
--- a/quickstep/src/com/android/quickstep/util/BorderAnimator.kt
+++ b/quickstep/src/com/android/quickstep/util/BorderAnimator.kt
@@ -50,7 +50,7 @@
private val disappearanceDurationMs: Long,
private val interpolator: Interpolator,
) {
- private val borderAnimationProgress = AnimatedFloat { updateOutline() }
+ private val borderAnimationProgress = AnimatedFloat { _ -> updateOutline() }
private val borderPaint =
Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = borderColor
@@ -224,6 +224,7 @@
val borderWidth: Float
get() = borderWidthPx * animationProgress
+
val alignmentAdjustment: Float
// Outset the border by half the width to create an outwards-growth animation
get() = -borderWidth / 2f + alignmentAdjustmentInset
diff --git a/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java b/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java
index cb44a1a..fcf9ab1 100644
--- a/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java
+++ b/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java
@@ -108,19 +108,28 @@
return false;
}
+ final SimpleOrientationTouchTransformer touchTransformer =
+ SimpleOrientationTouchTransformer.INSTANCE.get(mContext);
+ final int viewRotation = mRotationSupplier.get();
+ final boolean needTransform = viewRotation != ev.getSurfaceRotation();
if (action == ACTION_DOWN) {
mTouchInProgress = true;
+ if (needTransform) {
+ touchTransformer.updateTouchingOrientation(viewRotation);
+ }
initInputConsumerIfNeeded(/* isFromTouchDown= */ true);
} else if (action == ACTION_CANCEL || action == ACTION_UP) {
// Finish any pending actions
mTouchInProgress = false;
+ touchTransformer.clearTouchingOrientation();
if (mDestroyPending) {
destroy();
}
}
if (mInputConsumer != null) {
- SimpleOrientationTouchTransformer.INSTANCE.get(mContext).transform(ev,
- mRotationSupplier.get());
+ if (needTransform) {
+ touchTransformer.transform(ev, viewRotation);
+ }
mInputConsumer.onMotionEvent(ev);
}
diff --git a/quickstep/src/com/android/quickstep/util/SlideInRemoteTransition.kt b/quickstep/src/com/android/quickstep/util/SlideInRemoteTransition.kt
index dbeedd3..ece9583 100644
--- a/quickstep/src/com/android/quickstep/util/SlideInRemoteTransition.kt
+++ b/quickstep/src/com/android/quickstep/util/SlideInRemoteTransition.kt
@@ -36,6 +36,8 @@
private val pageSpacing: Int,
private val cornerRadius: Float,
private val interpolator: TimeInterpolator,
+ private val onStartCallback: Runnable,
+ private val onFinishCallback: Runnable,
) : RemoteTransitionStub() {
private val animationDurationMs = 500L
@@ -68,6 +70,7 @@
startT.setCrop(leash, chg.endAbsBounds).setCornerRadius(leash, cornerRadius)
}
}
+ onStartCallback.run()
startT.apply()
anim.addUpdateListener {
@@ -97,6 +100,7 @@
val t = Transaction()
try {
finishCB.onTransitionFinished(null, t)
+ onFinishCallback.run()
} catch (e: RemoteException) {
// Ignore
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index 49e1c88..e31a828 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -27,8 +27,10 @@
import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.content.Context
import android.graphics.Bitmap
+import android.graphics.Color
import android.graphics.Rect
import android.graphics.RectF
+import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.view.RemoteAnimationTarget
import android.view.SurfaceControl
@@ -120,8 +122,8 @@
val drawable = getDrawable(container.iconView, splitSelectSource)
return SplitAnimInitProps(
container.snapshotView,
- container.thumbnail,
- drawable!!,
+ container.splitAnimationThumbnail,
+ drawable,
fadeWithThumbnail = true,
isStagedTask = true,
iconView = container.iconView.asView()
@@ -139,8 +141,8 @@
val drawable = getDrawable(it.iconView, splitSelectSource)
return SplitAnimInitProps(
it.snapshotView,
- it.thumbnail,
- drawable!!,
+ it.splitAnimationThumbnail,
+ drawable,
fadeWithThumbnail = true,
isStagedTask = true,
iconView = it.iconView.asView()
@@ -152,15 +154,15 @@
/**
* Returns the drawable that's provided in iconView, however if that is null it falls back to
* the drawable that's in splitSelectSource. TaskView's icon drawable can be null if the
- * TaskView is scrolled far enough off screen
+ * TaskView is scrolled far enough off screen.
*
- * @return [Drawable]
+ * @return the [Drawable] icon, or a translucent drawable if none was found
*/
- fun getDrawable(iconView: TaskViewIcon, splitSelectSource: SplitSelectSource?): Drawable? {
- if (iconView.drawable == null && splitSelectSource != null) {
- return splitSelectSource.drawable
- }
- return iconView.drawable
+ fun getDrawable(iconView: TaskViewIcon, splitSelectSource: SplitSelectSource?): Drawable {
+ val drawable =
+ if (iconView.drawable == null && splitSelectSource != null) splitSelectSource.drawable
+ else iconView.drawable
+ return drawable ?: ColorDrawable(Color.TRANSPARENT)
}
/**
@@ -534,7 +536,13 @@
val appPairLaunchingAppIndex = hasChangesForBothAppPairs(launchingIconView, info)
if (appPairLaunchingAppIndex == -1) {
// Launch split app pair animation
- composeIconSplitLaunchAnimator(launchingIconView, info, t, finishCallback)
+ composeIconSplitLaunchAnimator(
+ launchingIconView,
+ info,
+ t,
+ finishCallback,
+ cornerRadius
+ )
} else {
composeFullscreenIconSplitLaunchAnimator(
launchingIconView,
@@ -551,8 +559,14 @@
"unexpected null"
}
- composeFadeInSplitLaunchAnimator(initialTaskId, secondTaskId, info, t, finishCallback,
- cornerRadius)
+ composeFadeInSplitLaunchAnimator(
+ initialTaskId,
+ secondTaskId,
+ info,
+ t,
+ finishCallback,
+ cornerRadius
+ )
}
}
@@ -652,15 +666,15 @@
* To find the root shell leash that we want to fade in, we do the following: The Changes we
* receive in transitionInfo are structured like this
*
- * Root (grandparent)
+ * (0) Root (grandparent)
* |
- * |--> Split Root 1 (left/top side parent) (WINDOWING_MODE_MULTI_WINDOW)
+ * |--> (1) Split Root 1 (left/top side parent) (WINDOWING_MODE_MULTI_WINDOW)
* | |
- * | --> App 1 (left/top side child) (WINDOWING_MODE_MULTI_WINDOW)
+ * | --> (1a) App 1 (left/top side child) (WINDOWING_MODE_MULTI_WINDOW)
* |--> Divider
- * |--> Split Root 2 (right/bottom side parent) (WINDOWING_MODE_MULTI_WINDOW)
+ * |--> (2) Split Root 2 (right/bottom side parent) (WINDOWING_MODE_MULTI_WINDOW)
* |
- * --> App 2 (right/bottom side child) (WINDOWING_MODE_MULTI_WINDOW)
+ * --> (2a) App 2 (right/bottom side child) (WINDOWING_MODE_MULTI_WINDOW)
*
* We want to animate the Root (grandparent) so that it affects both apps and the divider. To do
* this, we find one of the nodes with WINDOWING_MODE_MULTI_WINDOW (one of the left-side ones,
@@ -675,7 +689,8 @@
launchingIconView: AppPairIcon,
transitionInfo: TransitionInfo,
t: Transaction,
- finishCallback: Runnable
+ finishCallback: Runnable,
+ windowRadius: Float
) {
// If launching an app pair from Taskbar inside of an app context (no access to Launcher),
// use the scale-up animation
@@ -695,48 +710,26 @@
// Create an AnimatorSet that will run both shell and launcher transitions together
val launchAnimation = AnimatorSet()
- var rootCandidate: Change? = null
- for (change in transitionInfo.changes) {
- val taskInfo: RunningTaskInfo = change.taskInfo ?: continue
+ val splitRoots: Pair<Change, List<Change>>? =
+ SplitScreenUtils.extractTopParentAndChildren(transitionInfo)
+ check(splitRoots != null) { "Could not find split roots" }
- // TODO (b/316490565): Replace this logic when SplitBounds is available to
- // startAnimation() and we can know the precise taskIds of launching tasks.
- // Find a change that has WINDOWING_MODE_MULTI_WINDOW.
- if (
- taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW &&
- (change.mode == TRANSIT_OPEN || change.mode == TRANSIT_TO_FRONT)
- ) {
- // Check if it is a left/top app.
- val isLeftTopApp =
- (dp.isLeftRightSplit && change.endAbsBounds.left == 0) ||
- (!dp.isLeftRightSplit && change.endAbsBounds.top == 0)
- if (isLeftTopApp) {
- // Found one!
- rootCandidate = change
- break
- }
- }
- }
-
- // If we could not find a proper root candidate, something went wrong.
- check(rootCandidate != null) { "Could not find a split root candidate" }
+ // Will point to change (0) in diagram above
+ val mainRootCandidate = splitRoots.first
+ // Will contain changes (1) and (2) in diagram above
+ val leafRoots: List<Change> = splitRoots.second
// Find the place where our left/top app window meets the divider (used for the
// launcher side animation)
+ val leftTopApp =
+ leafRoots.single { change ->
+ (dp.isLeftRightSplit && change.endAbsBounds.left == 0) ||
+ (!dp.isLeftRightSplit && change.endAbsBounds.top == 0)
+ }
val dividerPos =
- if (dp.isLeftRightSplit) rootCandidate.endAbsBounds.right
- else rootCandidate.endAbsBounds.bottom
-
- // Recurse up the tree until parent is null, then we've found our root.
- var parentToken: WindowContainerToken? = rootCandidate.parent
- while (parentToken != null) {
- rootCandidate = transitionInfo.getChange(parentToken) ?: break
- parentToken = rootCandidate.parent
- }
-
- // Make sure nothing weird happened, like getChange() returning null.
- check(rootCandidate != null) { "Failed to find a root leash" }
+ if (dp.isLeftRightSplit) leftTopApp.endAbsBounds.right
+ else leftTopApp.endAbsBounds.bottom
// Create a new floating view in Launcher, positioned above the launching icon
val drawableArea = launchingIconView.iconDrawableArea
@@ -755,9 +748,26 @@
)
floatingView.bringToFront()
- launchAnimation.play(
- getIconLaunchValueAnimator(t, dp, finishCallback, launcher, floatingView, rootCandidate)
+ val iconLaunchValueAnimator =
+ getIconLaunchValueAnimator(
+ t,
+ dp,
+ finishCallback,
+ launcher,
+ floatingView,
+ mainRootCandidate
+ )
+ iconLaunchValueAnimator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator, isReverse: Boolean) {
+ for (c in leafRoots) {
+ t.setCornerRadius(c.leash, windowRadius)
+ t.apply()
+ }
+ }
+ }
)
+ launchAnimation.play(iconLaunchValueAnimator)
launchAnimation.start()
}
@@ -1026,12 +1036,12 @@
*/
@VisibleForTesting
fun composeFadeInSplitLaunchAnimator(
- initialTaskId: Int,
- secondTaskId: Int,
- transitionInfo: TransitionInfo,
- t: Transaction,
- finishCallback: Runnable,
- cornerRadius: Float
+ initialTaskId: Int,
+ secondTaskId: Int,
+ transitionInfo: TransitionInfo,
+ t: Transaction,
+ finishCallback: Runnable,
+ cornerRadius: Float
) {
var splitRoot1: Change? = null
var splitRoot2: Change? = null
@@ -1109,7 +1119,7 @@
override fun onAnimationStart(animation: Animator) {
for (leash in openingTargets) {
animTransaction.show(leash).setAlpha(leash, 0.0f)
- animTransaction.setCornerRadius(leash, cornerRadius);
+ animTransaction.setCornerRadius(leash, cornerRadius)
}
animTransaction.apply()
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt b/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt
index 4820c35..d58cb91 100644
--- a/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt
@@ -16,11 +16,21 @@
package com.android.quickstep.util
+import android.util.Log
+import android.view.WindowManager
+import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TRANSIT_TO_FRONT
+import android.window.TransitionInfo
+import android.window.TransitionInfo.Change
+import android.window.TransitionInfo.FLAG_FIRST_CUSTOM
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.wm.shell.util.SplitBounds
+import java.lang.IllegalStateException
class SplitScreenUtils {
companion object {
+ private const val TAG = "SplitScreenUtils"
+
// TODO(b/254378592): Remove these methods when the two classes are reunited
/** Converts the shell version of SplitBounds to the launcher version */
@JvmStatic
@@ -39,5 +49,51 @@
)
}
}
+
+ /**
+ * Given a TransitionInfo, generates the tree structure for those changes and extracts out
+ * the top most root and it's two immediate children.
+ * Changes can be provided in any order.
+ *
+ * @return a [Pair] where first -> top most split root,
+ * second -> [List] of 2, leftTop/bottomRight stage roots
+ */
+ fun extractTopParentAndChildren(transitionInfo: TransitionInfo):
+ Pair<Change, List<Change>>? {
+ val parentToChildren = mutableMapOf<Change, MutableList<Change>>()
+ val hasParent = mutableSetOf<Change>()
+ // filter out anything that isn't opening and the divider
+ val taskChanges: List<Change> = transitionInfo.changes
+ .filter { change -> (change.mode == TRANSIT_OPEN ||
+ change.mode == TRANSIT_TO_FRONT) && change.flags < FLAG_FIRST_CUSTOM}
+ .toList()
+
+ // 1. Build Parent-Child Relationships
+ for (change in taskChanges) {
+ // TODO (b/316490565): Replace this logic when SplitBounds is available to
+ // startAnimation() and we can know the precise taskIds of launching tasks.
+ change.parent?.let { parent ->
+ parentToChildren
+ .getOrPut(transitionInfo.getChange(parent)!!) { mutableListOf() }
+ .add(change)
+ hasParent.add(change)
+ }
+ }
+
+ // 2. Find Top Parent
+ val topParent = taskChanges.firstOrNull { it !in hasParent }
+
+ // 3. Extract Immediate Children
+ return if (topParent != null) {
+ val immediateChildren = parentToChildren.getOrDefault(topParent, emptyList())
+ if (immediateChildren.size != 2) {
+ throw IllegalStateException("incorrect split stage root size")
+ }
+ Pair(topParent, immediateChildren)
+ } else {
+ Log.w(TAG, "No top parent found")
+ null
+ }
+ }
}
}
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index 48ed67b..56e91ed 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -139,6 +139,10 @@
final float aspectRatio = destinationBounds.width() / (float) destinationBounds.height();
String reasonForCreateOverlay = null; // For debugging purpose.
+
+ // Slightly larger app bounds to allow for off by 1 pixel source-rect-hint errors.
+ Rect overflowAppBounds = new Rect(appBounds.left - 1, appBounds.top - 1,
+ appBounds.right + 1, appBounds.bottom + 1);
if (sourceRectHint.isEmpty()) {
reasonForCreateOverlay = "Source rect hint is empty";
} else if (sourceRectHint.width() < destinationBounds.width()
@@ -149,7 +153,7 @@
// animation in this case.
reasonForCreateOverlay = "Source rect hint is too small " + sourceRectHint;
sourceRectHint.setEmpty();
- } else if (!appBounds.contains(sourceRectHint)) {
+ } else if (!overflowAppBounds.contains(sourceRectHint)) {
// This is a situation in which the source hint rect is outside the app bounds, so it is
// not a valid rectangle to use for cropping app surface
reasonForCreateOverlay = "Source rect hint exceeds display bounds " + sourceRectHint;
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
index 4333c8b..9ce2277 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
@@ -24,6 +24,7 @@
import android.graphics.drawable.shapes.RoundRectShape
import android.util.AttributeSet
import android.util.Log
+import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.updateLayoutParams
@@ -36,7 +37,6 @@
import com.android.launcher3.util.rects.set
import com.android.quickstep.BaseContainerInterface
import com.android.quickstep.TaskOverlayFactory
-import com.android.quickstep.task.thumbnail.TaskThumbnailView
import com.android.quickstep.util.RecentsOrientedState
import com.android.systemui.shared.recents.model.Task
@@ -54,7 +54,7 @@
ViewPool<TaskThumbnailViewDeprecated>(
context,
this,
- R.layout.task_thumbnail,
+ R.layout.task_thumbnail_deprecated,
VIEW_POOL_MAX_SIZE,
VIEW_POOL_INITIAL_SIZE
)
@@ -108,22 +108,21 @@
tasks.map { task ->
val snapshotView =
if (enableRefactorTaskThumbnail()) {
- TaskThumbnailView(context)
- } else {
- taskThumbnailViewDeprecatedPool.view
- }
- .also { snapshotView ->
- addView(
- snapshotView,
- // Add snapshotView to the front after initial views e.g. icon and
- // background.
- childCountAtInflation,
- LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT,
- ViewGroup.LayoutParams.WRAP_CONTENT
- )
- )
- }
+ LayoutInflater.from(context).inflate(R.layout.task_thumbnail, this, false)
+ } else {
+ taskThumbnailViewDeprecatedPool.view
+ }
+
+ addView(
+ snapshotView,
+ // Add snapshotView to the front after initial views e.g. icon and
+ // background.
+ childCountAtInflation,
+ LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ )
TaskContainer(
this,
task,
diff --git a/quickstep/src/com/android/quickstep/views/FloatingAppPairBackground.kt b/quickstep/src/com/android/quickstep/views/FloatingAppPairBackground.kt
index e024995..6bbd6b2 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingAppPairBackground.kt
+++ b/quickstep/src/com/android/quickstep/views/FloatingAppPairBackground.kt
@@ -57,6 +57,7 @@
private val container: RecentsViewContainer
private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG)
+ private val dividerPaint = Paint(Paint.ANTI_ALIAS_FLAG)
// Animation interpolators
protected val expandXInterpolator: Interpolator
@@ -105,13 +106,15 @@
)
// Find device-specific measurements
- deviceCornerRadius = QuickStepContract.getWindowCornerRadius(container.asContext())
+ val resources = context.resources
+ deviceCornerRadius = QuickStepContract.getWindowCornerRadius(context)
deviceHalfDividerSize =
- container.asContext().resources.getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2f
+ resources.getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2f
val dividerCenterPos = dividerPos + deviceHalfDividerSize
desiredSplitRatio =
if (dp.isLeftRightSplit) dividerCenterPos / dp.widthPx
else dividerCenterPos / dp.heightPx
+ dividerPaint.color = resources.getColor(R.color.taskbar_background_dark, null /*theme*/)
}
override fun draw(canvas: Canvas) {
@@ -153,8 +156,12 @@
val leftSide = RectF(0f, 0f, dividerCenterPos - changingDividerSize, height)
// The right half of the background image
val rightSide = RectF(dividerCenterPos + changingDividerSize, 0f, width, height)
+ // Middle part is for divider background
+ val middleRect = RectF(leftSide.right - deviceHalfDividerSize, 0f,
+ rightSide.left + deviceHalfDividerSize, height)
// Draw background
+ canvas.drawRect(middleRect, dividerPaint)
drawCustomRoundedRect(
canvas,
leftSide,
@@ -251,8 +258,12 @@
val topSide = RectF(0f, 0f, width, dividerCenterPos - changingDividerSize)
// The bottom half of the background image
val bottomSide = RectF(0f, dividerCenterPos + changingDividerSize, width, height)
+ // Middle part is for divider background
+ val middleRect = RectF(0f, topSide.bottom - deviceHalfDividerSize,
+ width, bottomSide.top + deviceHalfDividerSize)
// Draw background
+ canvas.drawRect(middleRect, dividerPaint)
drawCustomRoundedRect(
canvas,
topSide,
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 7b6d383..d888eb9 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -191,6 +191,8 @@
import com.android.quickstep.TopTaskTracker;
import com.android.quickstep.ViewUtils;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
+import com.android.quickstep.recents.data.RecentsDeviceProfileRepository;
+import com.android.quickstep.recents.data.RecentsRotationStateRepository;
import com.android.quickstep.recents.data.TasksRepository;
import com.android.quickstep.recents.viewmodel.RecentsViewData;
import com.android.quickstep.util.ActiveGestureErrorDetector;
@@ -461,8 +463,14 @@
private static final float FOREGROUND_SCRIM_TINT = 0.32f;
+ @Nullable
public final RecentsViewData mRecentsViewData = new RecentsViewData();
+ @Nullable
public final TasksRepository mTasksRepository;
+ @Nullable
+ public final RecentsRotationStateRepository mOrientedStateRepository;
+ @Nullable
+ public final RecentsDeviceProfileRepository mDeviceProfileRepository;
protected final RecentsOrientedState mOrientationState;
protected final BaseContainerInterface<STATE_TYPE, CONTAINER_TYPE> mSizeStrategy;
@@ -793,6 +801,13 @@
private int mOffsetMidpointIndexOverride = INVALID_PAGE;
+ /**
+ * Whether or not any task has been dismissed i.e. swiped away by the user, in the lifetime of
+ * RecentsView being open and displayed to the user. It is reset in the {@link #reset()} method
+ * i.e. when RecentsView closes.
+ */
+ private boolean mAnyTaskHasBeenDismissed;
+
public RecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
BaseContainerInterface sizeStrategy) {
super(context, attrs, defStyleAttr);
@@ -813,8 +828,12 @@
if (enableRefactorTaskThumbnail()) {
mTasksRepository = new TasksRepository(
mModel, mModel.getThumbnailCache(), mModel.getIconCache());
+ mOrientedStateRepository = new RecentsRotationStateRepository(mOrientationState);
+ mDeviceProfileRepository = new RecentsDeviceProfileRepository(mContainer);
} else {
mTasksRepository = null;
+ mOrientedStateRepository = null;
+ mDeviceProfileRepository = null;
}
mClearAllButton = (ClearAllButton) LayoutInflater.from(context)
@@ -2179,15 +2198,6 @@
* Updates TaskView scaling and translation required to support variable width.
*/
private void updateTaskSize() {
- updateTaskSize(false);
- }
-
- /**
- * Updates TaskView scaling and translation required to support variable width.
- *
- * @param isTaskDismissal indicates if update was called due to task dismissal
- */
- private void updateTaskSize(boolean isTaskDismissal) {
final int taskCount = getTaskViewCount();
if (taskCount == 0) {
return;
@@ -2214,7 +2224,7 @@
mClearAllButton.setFullscreenTranslationPrimary(accumulatedTranslationX);
- updateGridProperties(isTaskDismissal);
+ updateGridProperties();
}
public void getTaskSize(Rect outRect) {
@@ -2514,6 +2524,7 @@
mIgnoreResetTaskId = -1;
mTaskListChangeId = -1;
mFocusedTaskViewId = -1;
+ mAnyTaskHasBeenDismissed = false;
Log.d(TAG, "reset - mEnableDrawingLiveTile: " + mEnableDrawingLiveTile
+ ", mRecentsAnimationController: " + mRecentsAnimationController);
@@ -2989,22 +3000,11 @@
/**
* Updates TaskView and ClearAllButtion scaling and translation required to turn into grid
* layout.
- * This method is used when no task dismissal has occurred.
+ *
+ * Skips rebalance.
*/
private void updateGridProperties() {
- updateGridProperties(false, Integer.MAX_VALUE);
- }
-
- /**
- * Updates TaskView and ClearAllButtion scaling and translation required to turn into grid
- * layout.
- *
- * This method is used when task dismissal has occurred, but rebalance is not needed.
- *
- * @param isTaskDismissal indicates if update was called due to task dismissal
- */
- private void updateGridProperties(boolean isTaskDismissal) {
- updateGridProperties(isTaskDismissal, Integer.MAX_VALUE);
+ updateGridProperties(Integer.MAX_VALUE);
}
/**
@@ -3014,11 +3014,10 @@
* This method only calculates the potential position and depends on {@link #setGridProgress} to
* apply the actual scaling and translation.
*
- * @param isTaskDismissal indicates if update was called due to task dismissal
* @param startRebalanceAfter which view index to start rebalancing from. Use Integer.MAX_VALUE
- * to skip rebalance
+ * to skip rebalance
*/
- private void updateGridProperties(boolean isTaskDismissal, int startRebalanceAfter) {
+ private void updateGridProperties(int startRebalanceAfter) {
int taskCount = getTaskViewCount();
if (taskCount == 0) {
return;
@@ -3049,7 +3048,8 @@
TaskView homeTaskView = getHomeTaskView();
TaskView nextFocusedTaskView = null;
- if (!isTaskDismissal) {
+ // Don't clear the top row, if the user has dismissed a task, to maintain the task order.
+ if (!mAnyTaskHasBeenDismissed) {
mTopRowIdSet.clear();
}
for (int i = 0; i < taskCount; i++) {
@@ -3089,7 +3089,7 @@
// Rebalance the grid starting after a certain index
boolean isTopRow;
- if (isTaskDismissal) {
+ if (mAnyTaskHasBeenDismissed) {
if (i > startRebalanceAfter) {
mTopRowIdSet.remove(taskViewId);
isTopRow = topRowWidth <= bottomRowWidth;
@@ -3814,7 +3814,7 @@
anim.setFloat(taskView, taskView.getSecondaryDismissTranslationProperty(),
secondaryTranslation, clampToProgress(LINEAR, animationStartProgress,
dismissTranslationInterpolationEnd));
- anim.setFloat(taskView, TaskView.SCALE_AND_DIM_OUT, 0f,
+ anim.add(taskView.getFocusTransitionScaleAndDimOutAnimator(),
clampToProgress(LINEAR, 0f, ANIMATION_DISMISS_PROGRESS_MIDPOINT));
} else {
float primaryTranslation =
@@ -3870,6 +3870,7 @@
resetTaskVisuals();
if (success) {
+ mAnyTaskHasBeenDismissed = true;
if (shouldRemoveTask) {
if (dismissedTaskView.isRunningTask()) {
finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
@@ -3978,7 +3979,7 @@
mTopRowIdSet.remove(mFocusedTaskViewId);
finalNextFocusedTaskView.animateIconScaleAndDimIntoView();
}
- updateTaskSize(/*isTaskDismissal=*/ true);
+ updateTaskSize();
updateChildTaskOrientations();
// Update scroll and snap to page.
updateScrollSynchronously();
@@ -4014,8 +4015,7 @@
}
if (shouldRebalance) {
- updateGridProperties(/*isTaskDismissal=*/ true,
- highestVisibleTaskIndex);
+ updateGridProperties(highestVisibleTaskIndex);
updateScrollSynchronously();
}
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
index 0648986..74d120f 100644
--- a/quickstep/src/com/android/quickstep/views/TaskContainer.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
@@ -31,6 +31,7 @@
import com.android.quickstep.TaskUtils
import com.android.quickstep.task.thumbnail.TaskThumbnail
import com.android.quickstep.task.thumbnail.TaskThumbnailView
+import com.android.quickstep.task.util.GetThumbnailUseCase
import com.android.quickstep.task.viewmodel.TaskContainerData
import com.android.systemui.shared.recents.model.Task
@@ -56,6 +57,16 @@
val overlay: TaskOverlayFactory.TaskOverlay<*> = taskOverlayFactory.createOverlay(this)
val taskContainerData = TaskContainerData()
+ private val getThumbnailUseCase by lazy {
+ // TODO(b/335649589): Ideally create and obtain this from DI.
+ val recentsView =
+ RecentsViewContainer.containerFromContext<RecentsViewContainer>(
+ overlay.taskView.context
+ )
+ .getOverviewPanel<RecentsView<*, *>>()
+ GetThumbnailUseCase(recentsView.mTasksRepository!!)
+ }
+
init {
if (enableRefactorTaskThumbnail()) {
require(snapshotView is TaskThumbnailView)
@@ -64,6 +75,14 @@
}
}
+ val splitAnimationThumbnail: Bitmap?
+ get() =
+ if (enableRefactorTaskThumbnail()) {
+ getThumbnailUseCase.run(task.key.id)
+ } else {
+ thumbnailViewDeprecated.thumbnail
+ }
+
val thumbnailView: TaskThumbnailView
get() {
require(enableRefactorTaskThumbnail())
@@ -76,10 +95,6 @@
return snapshotView as TaskThumbnailViewDeprecated
}
- // TODO(b/349120849): Extract ThumbnailData from TaskContainerData/TaskThumbnailViewModel
- val thumbnail: Bitmap?
- get() = if (enableRefactorTaskThumbnail()) null else thumbnailViewDeprecated.thumbnail
-
// TODO(b/334826842): Support shouldShowSplashView for new TTV.
val shouldShowSplashView: Boolean
get() =
@@ -114,6 +129,15 @@
}
}
+ fun bind() {
+ if (enableRefactorTaskThumbnail()) {
+ bindThumbnailView()
+ } else {
+ thumbnailViewDeprecated.bind(task, overlay)
+ }
+ overlay.init()
+ }
+
fun destroy() {
digitalWellBeingToast?.destroy()
if (enableRefactorTaskThumbnail()) {
@@ -122,15 +146,6 @@
overlay.destroy()
}
- fun bind() {
- if (enableRefactorTaskThumbnail()) {
- bindThumbnailView()
- overlay.init()
- } else {
- thumbnailViewDeprecated.bind(task, overlay)
- }
- }
-
// TODO(b/335649589): TaskView's VM will already have access to TaskThumbnailView's VM
// so there will be no need to access TaskThumbnailView's VM through the TaskThumbnailView
fun bindThumbnailView() {
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 2e07e36..004003c 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -31,6 +31,7 @@
import android.util.FloatProperty
import android.util.Log
import android.view.Display
+import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.View.OnClickListener
@@ -51,6 +52,7 @@
import com.android.launcher3.Flags.enableRefactorTaskThumbnail
import com.android.launcher3.R
import com.android.launcher3.Utilities
+import com.android.launcher3.anim.AnimatedFloat
import com.android.launcher3.config.FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH
import com.android.launcher3.logging.StatsLogManager.LauncherEvent
import com.android.launcher3.model.data.ItemInfo
@@ -419,17 +421,17 @@
focusTransitionPropertyFactory.get(FOCUS_TRANSITION_INDEX_FULLSCREEN)
private val focusTransitionScaleAndDim =
focusTransitionPropertyFactory.get(FOCUS_TRANSITION_INDEX_SCALE_AND_DIM)
+
/**
- * Variant of [focusTransitionScaleAndDim] that has a built-in interpolator, to be used with
- * [com.android.launcher3.anim.PendingAnimation] via [SCALE_AND_DIM_OUT] only. PendingAnimation
- * doesn't support interpolator per animation, so we'll have to interpolate inside the property.
+ * Returns an animator of [focusTransitionScaleAndDim] that transition out with a built-in
+ * interpolator.
*/
- private var focusTransitionScaleAndDimOut = focusTransitionScaleAndDim.value
- set(value) {
- field = value
- focusTransitionScaleAndDim.value =
- FOCUS_TRANSITION_FAST_OUT_INTERPOLATOR.getInterpolation(field)
- }
+ fun getFocusTransitionScaleAndDimOutAnimator(): ObjectAnimator =
+ AnimatedFloat { v ->
+ focusTransitionScaleAndDim.value =
+ FOCUS_TRANSITION_FAST_OUT_INTERPOLATOR.getInterpolation(v)
+ }
+ .animateToValue(1f, 0f)
private var iconAndDimAnimator: ObjectAnimator? = null
// The current background requests to load the task thumbnail and icon
@@ -665,9 +667,8 @@
if (enableRefactorTaskThumbnail()) {
thumbnailViewDeprecated.visibility = GONE
val indexOfSnapshotView = indexOfChild(thumbnailViewDeprecated)
- TaskThumbnailView(context).apply {
- layoutParams = thumbnailViewDeprecated.layoutParams
- addView(this, indexOfSnapshotView)
+ LayoutInflater.from(context).inflate(R.layout.task_thumbnail, this, false).also {
+ addView(it, indexOfSnapshotView, thumbnailViewDeprecated.layoutParams)
}
} else {
thumbnailViewDeprecated
@@ -1179,7 +1180,7 @@
container.task,
container.iconView.drawable,
container.snapshotView,
- container.thumbnail,
+ container.splitAnimationThumbnail,
/* intent */ null,
/* user */ null,
container.itemInfo
@@ -1615,16 +1616,6 @@
override fun get(taskView: TaskView) = taskView.focusTransitionProgress
}
- @JvmField
- val SCALE_AND_DIM_OUT: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("scaleAndDimFastOut") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.focusTransitionScaleAndDimOut = v
- }
-
- override fun get(taskView: TaskView) = taskView.focusTransitionScaleAndDimOut
- }
-
private val SPLIT_SELECT_TRANSLATION_X: FloatProperty<TaskView> =
object : FloatProperty<TaskView>("splitSelectTranslationX") {
override fun setValue(taskView: TaskView, v: Float) {
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt
index 039dce4..4ea74df 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt
@@ -30,6 +30,7 @@
import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.LauncherAppState
import com.android.launcher3.icons.IconCache
+import com.android.launcher3.model.WidgetPredictionsRequester.LAUNCH_LOCATION
import com.android.launcher3.model.WidgetPredictionsRequester.buildBundleForPredictionSession
import com.android.launcher3.model.WidgetPredictionsRequester.filterPredictions
import com.android.launcher3.model.WidgetPredictionsRequester.notOnUiSurfaceFilter
@@ -103,7 +104,7 @@
fun buildBundleForPredictionSession_includesAddedAppWidgets() {
val existingWidgets = arrayListOf(widget1aInfo, widget1bInfo, widget2Info)
- val bundle = buildBundleForPredictionSession(existingWidgets, TEST_UI_SURFACE)
+ val bundle = buildBundleForPredictionSession(existingWidgets)
val addedWidgetsBundleExtra =
bundle.getParcelableArrayList(BUNDLE_KEY_ADDED_APP_WIDGETS, AppTarget::class.java)
@@ -213,7 +214,7 @@
.setClassName(providerClassName)
.build()
return AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_PIN)
- .setLaunchLocation(TEST_UI_SURFACE)
+ .setLaunchLocation(LAUNCH_LOCATION)
.build()
}
}
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 0f06d98..399aea6 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
@@ -4,6 +4,8 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_BACK_BUTTON_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_HOME_BUTTON_TAP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_LONGPRESS;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_LONGPRESS;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_TAP;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y;
@@ -26,6 +28,7 @@
import android.os.Handler;
import android.view.View;
+import android.view.inputmethod.Flags;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -109,8 +112,27 @@
@Test
public void testPressImeSwitcher() {
+ mNavButtonController.init(mockTaskbarControllers);
mNavButtonController.onButtonClick(BUTTON_IME_SWITCH, mockView);
+ verify(mockStatsLogger, times(1)).log(LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_TAP);
+ verify(mockStatsLogger, never()).log(LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_LONGPRESS);
verify(mockSystemUiProxy, times(1)).onImeSwitcherPressed();
+ verify(mockSystemUiProxy, never()).onImeSwitcherLongPress();
+ }
+
+ @Test
+ public void testLongPressImeSwitcher() {
+ mNavButtonController.init(mockTaskbarControllers);
+ mNavButtonController.onButtonLongClick(BUTTON_IME_SWITCH, mockView);
+ verify(mockStatsLogger, never()).log(LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_TAP);
+ verify(mockSystemUiProxy, never()).onImeSwitcherPressed();
+ if (Flags.imeSwitcherRevamp()) {
+ verify(mockStatsLogger, times(1)).log(LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_LONGPRESS);
+ verify(mockSystemUiProxy, times(1)).onImeSwitcherLongPress();
+ } else {
+ verify(mockStatsLogger, never()).log(LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_LONGPRESS);
+ verify(mockSystemUiProxy, never()).onImeSwitcherLongPress();
+ }
}
@Test
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
new file mode 100644
index 0000000..d417790
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPinningPreferenceRule.kt
@@ -0,0 +1,65 @@
+/*
+ * 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
new file mode 100644
index 0000000..a709133
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPinningPreferenceRuleTest.kt
@@ -0,0 +1,112 @@
+/*
+ * 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.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)
+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
new file mode 100644
index 0000000..a76a77d
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPreferenceRule.kt
@@ -0,0 +1,54 @@
+/*
+ * 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
new file mode 100644
index 0000000..22d2079
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPreferenceRuleTest.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.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)
+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 8a64949..bbcf566 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
@@ -29,10 +29,10 @@
import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.launcher3.taskbar.TaskbarManager
import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks
+import com.android.launcher3.taskbar.TaskbarViewController
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
-import com.android.launcher3.util.ModelTestExtensions.loadModelSync
import com.android.launcher3.util.TestUtil
import com.android.quickstep.AllAppsActionManager
import com.android.quickstep.TouchInteractionService
@@ -136,17 +136,23 @@
taskbarManager =
TestUtil.getOnUiThread {
- TaskbarManager(
- context,
- AllAppsActionManager(context, UI_HELPER_EXECUTOR) {
- PendingIntent(IIntentSender.Default())
- },
- object : TaskbarNavButtonCallbacks {},
- )
+ object :
+ TaskbarManager(
+ context,
+ AllAppsActionManager(context, UI_HELPER_EXECUTOR) {
+ PendingIntent(IIntentSender.Default())
+ },
+ object : TaskbarNavButtonCallbacks {},
+ ) {
+ override fun recreateTaskbar() {
+ super.recreateTaskbar()
+ if (currentActivityContext != null) injectControllers()
+ }
+ }
}
try {
- LauncherAppState.getInstance(context).model.loadModelSync()
+ TaskbarViewController.enableModelLoadingForTests(false)
// Replace Launcher Taskbar window with test instance.
instrumentation.runOnMainSync {
@@ -154,7 +160,6 @@
taskbarManager.onUserUnlocked() // Required to complete initialization.
}
- injectControllers()
base.evaluate()
} finally {
// Revert Taskbar window.
@@ -162,16 +167,15 @@
taskbarManager.destroy()
launcherTaskbarManager?.setSuspended(false)
}
+
+ TaskbarViewController.enableModelLoadingForTests(true)
}
}
}
}
/** Simulates Taskbar recreation lifecycle. */
- fun recreateTaskbar() {
- instrumentation.runOnMainSync { taskbarManager.recreateTaskbar() }
- injectControllers()
- }
+ fun recreateTaskbar() = instrumentation.runOnMainSync { taskbarManager.recreateTaskbar() }
private fun injectControllers() {
val controllers = activityContext.controllers
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 321e7a9..ee21df8 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
@@ -50,7 +50,7 @@
)
}
- override fun getApplicationContext() = application
+ override fun getApplicationContext(): SandboxContext = application
companion object {
/** Creates a [TaskbarWindowSandboxContext] to sandbox [base] for Taskbar tests. */
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskIconDataSource.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskIconDataSource.kt
new file mode 100644
index 0000000..242bc73
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskIconDataSource.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.data
+
+import android.graphics.drawable.Drawable
+import com.android.launcher3.util.CancellableTask
+import com.android.quickstep.TaskIconCache
+import com.android.quickstep.task.thumbnail.data.TaskIconDataSource
+import com.android.systemui.shared.recents.model.Task
+import com.google.common.truth.Truth.assertThat
+import org.mockito.kotlin.mock
+
+class FakeTaskIconDataSource : TaskIconDataSource {
+
+ val taskIdToDrawable: Map<Int, Drawable> = (0..10).associateWith { mock() }
+ val taskIdToUpdatingTask: MutableMap<Int, () -> Unit> = mutableMapOf()
+ var shouldLoadSynchronously: Boolean = true
+
+ /** Retrieves and sets an icon on [task] from [taskIdToDrawable]. */
+ override fun getIconInBackground(
+ task: Task,
+ callback: TaskIconCache.GetTaskIconCallback
+ ): CancellableTask<*>? {
+ val wrappedCallback = {
+ callback.onTaskIconReceived(
+ taskIdToDrawable.getValue(task.key.id),
+ "content desc ${task.key.id}",
+ "title ${task.key.id}"
+ )
+ }
+ if (shouldLoadSynchronously) {
+ wrappedCallback()
+ } else {
+ taskIdToUpdatingTask[task.key.id] = wrappedCallback
+ }
+ return null
+ }
+}
+
+fun Task.assertHasIconDataFromSource(fakeTaskIconDataSource: FakeTaskIconDataSource) {
+ assertThat(icon).isEqualTo(fakeTaskIconDataSource.taskIdToDrawable[key.id])
+ assertThat(titleDescription).isEqualTo("content desc ${key.id}")
+ assertThat(title).isEqualTo("title ${key.id}")
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
index e160627..19990a8 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
@@ -32,6 +32,9 @@
override fun getTaskDataById(taskId: Int): Flow<Task?> =
getAllTaskData().map { taskList -> taskList.firstOrNull { it.key.id == taskId } }
+ override fun getThumbnailById(taskId: Int): Flow<ThumbnailData?> =
+ getTaskDataById(taskId).map { it?.thumbnail }
+
override fun setVisibleTasks(visibleTaskIdList: List<Int>) {
visibleTasks.value = visibleTaskIdList
tasks.value = tasks.value.map { it.apply { thumbnail = thumbnailDataMap[it.key.id] } }
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryTest.kt
new file mode 100644
index 0000000..eff926d
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryTest.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.data
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.FakeInvariantDeviceProfileTest
+import com.android.quickstep.views.RecentsViewContainer
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+/** Test for [RecentsDeviceProfileRepository] */
+@RunWith(AndroidJUnit4::class)
+class RecentsDeviceProfileRepositoryTest : FakeInvariantDeviceProfileTest() {
+ private val recentsViewContainer = mock<RecentsViewContainer>()
+
+ private val systemUnderTest = RecentsDeviceProfileRepository(recentsViewContainer)
+
+ @Test
+ fun deviceProfileMappedCorrectly() {
+ initializeVarsForTablet()
+ val tabletDeviceProfile = newDP()
+ whenever(recentsViewContainer.deviceProfile).thenReturn(tabletDeviceProfile)
+
+ assertThat(systemUnderTest.getRecentsDeviceProfile())
+ .isEqualTo(RecentsDeviceProfileRepository.RecentsDeviceProfile(isLargeScreen = true))
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsRotationStateRepositoryTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsRotationStateRepositoryTest.kt
new file mode 100644
index 0000000..1f4da26
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsRotationStateRepositoryTest.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.data
+
+import android.view.Surface.ROTATION_90
+import com.android.quickstep.util.RecentsOrientedState
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+/** Test for [RecentsRotationStateRepository] */
+class RecentsRotationStateRepositoryTest {
+ private val recentsOrientedState = mock<RecentsOrientedState>()
+
+ private val systemUnderTest = RecentsRotationStateRepository(recentsOrientedState)
+
+ @Test
+ fun orientedStateMappedCorrectly() {
+ whenever(recentsOrientedState.recentsActivityRotation).thenReturn(ROTATION_90)
+
+ assertThat(systemUnderTest.getRecentsRotationState())
+ .isEqualTo(
+ RecentsRotationStateRepository.RecentsRotationState(activityRotation = ROTATION_90)
+ )
+ }
+}
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 c28a85a..88fa190 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
@@ -18,7 +18,6 @@
import android.content.ComponentName
import android.content.Intent
-import com.android.quickstep.TaskIconCache
import com.android.quickstep.util.DesktopTask
import com.android.quickstep.util.GroupTask
import com.android.systemui.shared.recents.model.Task
@@ -31,7 +30,6 @@
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Test
-import org.mockito.kotlin.mock
@OptIn(ExperimentalCoroutinesApi::class)
class TasksRepositoryTest {
@@ -44,10 +42,10 @@
)
private val recentsModel = FakeRecentTasksDataSource()
private val taskThumbnailDataSource = FakeTaskThumbnailDataSource()
- private val taskIconCache = mock<TaskIconCache>()
+ private val taskIconDataSource = FakeTaskIconDataSource()
private val systemUnderTest =
- TasksRepository(recentsModel, taskThumbnailDataSource, taskIconCache)
+ TasksRepository(recentsModel, taskThumbnailDataSource, taskIconDataSource)
@Test
fun getAllTaskDataReturnsFlattenedListOfTasks() = runTest {
@@ -81,6 +79,22 @@
}
@Test
+ fun setVisibleTasksPopulatesIcons() = runTest {
+ recentsModel.seedTasks(defaultTaskList)
+ systemUnderTest.getAllTaskData(forceRefresh = true)
+
+ systemUnderTest.setVisibleTasks(listOf(1, 2))
+
+ // .drop(1) to ignore initial null content before from thumbnail was loaded.
+ systemUnderTest
+ .getTaskDataById(1)
+ .drop(1)
+ .first()!!
+ .assertHasIconDataFromSource(taskIconDataSource)
+ systemUnderTest.getTaskDataById(2).first()!!.assertHasIconDataFromSource(taskIconDataSource)
+ }
+
+ @Test
fun changingVisibleTasksContainsAlreadyPopulatedThumbnails() = runTest {
recentsModel.seedTasks(defaultTaskList)
val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2]
@@ -101,7 +115,28 @@
}
@Test
- fun retrievedThumbnailsAreDiscardedWhenTaskBecomesInvisible() = runTest {
+ fun changingVisibleTasksContainsAlreadyPopulatedIcons() = runTest {
+ recentsModel.seedTasks(defaultTaskList)
+ systemUnderTest.getAllTaskData(forceRefresh = true)
+
+ systemUnderTest.setVisibleTasks(listOf(1, 2))
+
+ // .drop(1) to ignore initial null content before from icon was loaded.
+ systemUnderTest
+ .getTaskDataById(2)
+ .drop(1)
+ .first()!!
+ .assertHasIconDataFromSource(taskIconDataSource)
+
+ // Prevent new loading of Drawables
+ taskThumbnailDataSource.shouldLoadSynchronously = false
+ systemUnderTest.setVisibleTasks(listOf(2, 3))
+
+ systemUnderTest.getTaskDataById(2).first()!!.assertHasIconDataFromSource(taskIconDataSource)
+ }
+
+ @Test
+ fun retrievedImagesAreDiscardedWhenTaskBecomesInvisible() = runTest {
recentsModel.seedTasks(defaultTaskList)
val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2]
systemUnderTest.getAllTaskData(forceRefresh = true)
@@ -109,14 +144,20 @@
systemUnderTest.setVisibleTasks(listOf(1, 2))
// .drop(1) to ignore initial null content before from thumbnail was loaded.
- assertThat(systemUnderTest.getTaskDataById(2).drop(1).first()!!.thumbnail!!.thumbnail)
- .isEqualTo(bitmap2)
+ val task2 = systemUnderTest.getTaskDataById(2).drop(1).first()!!
+ assertThat(task2.thumbnail!!.thumbnail).isEqualTo(bitmap2)
+ task2.assertHasIconDataFromSource(taskIconDataSource)
// Prevent new loading of Bitmaps
taskThumbnailDataSource.shouldLoadSynchronously = false
+ taskIconDataSource.shouldLoadSynchronously = false
systemUnderTest.setVisibleTasks(listOf(0, 1))
- assertThat(systemUnderTest.getTaskDataById(2).first()!!.thumbnail).isNull()
+ val task2AfterVisibleTasksChanged = systemUnderTest.getTaskDataById(2).first()!!
+ assertThat(task2AfterVisibleTasksChanged.thumbnail).isNull()
+ assertThat(task2AfterVisibleTasksChanged.icon).isNull()
+ assertThat(task2AfterVisibleTasksChanged.titleDescription).isNull()
+ assertThat(task2AfterVisibleTasksChanged.title).isNull()
}
@Test
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/util/GetThumbnailUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/util/GetThumbnailUseCaseTest.kt
new file mode 100644
index 0000000..414f8ca
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/util/GetThumbnailUseCaseTest.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.util
+
+import android.content.ComponentName
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.Color
+import com.android.quickstep.recents.data.FakeTasksRepository
+import com.android.quickstep.task.viewmodel.TaskOverlayViewModelTest
+import com.android.systemui.shared.recents.model.Task
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+/** Test for [GetThumbnailUseCase] */
+class GetThumbnailUseCaseTest {
+ private val task =
+ Task(Task.TaskKey(TASK_ID, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
+ colorBackground = Color.BLACK
+ }
+ private val thumbnailData =
+ ThumbnailData(
+ thumbnail =
+ mock<Bitmap>().apply {
+ whenever(width).thenReturn(THUMBNAIL_WIDTH)
+ whenever(height).thenReturn(THUMBNAIL_HEIGHT)
+ }
+ )
+
+ private val tasksRepository = FakeTasksRepository()
+ private val systemUnderTest = GetThumbnailUseCase(tasksRepository)
+
+ @Test
+ fun taskNotSeeded_returnsNull() {
+ assertThat(systemUnderTest.run(TASK_ID)).isNull()
+ }
+
+ @Test
+ fun taskNotLoaded_returnsNull() {
+ tasksRepository.seedTasks(listOf(task))
+
+ assertThat(systemUnderTest.run(TASK_ID)).isNull()
+ }
+
+ @Test
+ fun taskNotVisible_returnsNull() {
+ tasksRepository.seedTasks(listOf(task))
+ tasksRepository.seedThumbnailData(mapOf(TaskOverlayViewModelTest.TASK_ID to thumbnailData))
+
+ assertThat(systemUnderTest.run(TASK_ID)).isNull()
+ }
+
+ @Test
+ fun taskVisible_returnsThumbnail() {
+ tasksRepository.seedTasks(listOf(task))
+ tasksRepository.seedThumbnailData(mapOf(TaskOverlayViewModelTest.TASK_ID to thumbnailData))
+ tasksRepository.setVisibleTasks(listOf(TaskOverlayViewModelTest.TASK_ID))
+
+ assertThat(systemUnderTest.run(TASK_ID)).isEqualTo(thumbnailData.thumbnail)
+ }
+
+ companion object {
+ const val TASK_ID = 0
+ const val THUMBNAIL_WIDTH = 100
+ const val THUMBNAIL_HEIGHT = 200
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/taskbar/customization/TaskbarSpecsEvaluatorTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/taskbar/customization/TaskbarSpecsEvaluatorTest.kt
index 0bf68eb..d66197a 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/taskbar/customization/TaskbarSpecsEvaluatorTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/taskbar/customization/TaskbarSpecsEvaluatorTest.kt
@@ -16,6 +16,7 @@
package com.android.quickstep.taskbar.customization
+import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.launcher3.taskbar.customization.TaskbarFeatureEvaluator
import com.android.launcher3.taskbar.customization.TaskbarIconSpecs
import com.android.launcher3.taskbar.customization.TaskbarSpecsEvaluator
@@ -32,7 +33,9 @@
class TaskbarSpecsEvaluatorTest {
private val taskbarFeatureEvaluator = mock<TaskbarFeatureEvaluator>()
- private val taskbarSpecsEvaluator = spy(TaskbarSpecsEvaluator(taskbarFeatureEvaluator))
+ private val taskbarActivityContext = mock<TaskbarActivityContext>()
+ private var taskbarSpecsEvaluator =
+ spy(TaskbarSpecsEvaluator(taskbarActivityContext, taskbarFeatureEvaluator, 0, 0))
@Test
fun testGetIconSizeByGrid_whenTaskbarIsTransient_withValidRowAndColumnInLandscape() {
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
index fd7ecb0..f3cde52 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
@@ -87,7 +87,7 @@
@Before
fun setup() {
whenever(mockTaskContainer.snapshotView).thenReturn(mockSnapshotView)
- whenever(mockTaskContainer.thumbnail).thenReturn(mockBitmap)
+ whenever(mockTaskContainer.splitAnimationThumbnail).thenReturn(mockBitmap)
whenever(mockTaskContainer.iconView).thenReturn(mockIconView)
whenever(mockIconView.drawable).thenReturn(mockTaskViewDrawable)
whenever(mockTaskView.taskContainers).thenReturn(List(1) { mockTaskContainer })
@@ -276,7 +276,7 @@
whenever(mockAppPairIcon.context).thenReturn(mockContextThemeWrapper)
doNothing()
.whenever(spySplitAnimationController)
- .composeIconSplitLaunchAnimator(any(), any(), any(), any())
+ .composeIconSplitLaunchAnimator(any(), any(), any(), any(), any())
doReturn(-1).whenever(spySplitAnimationController).hasChangesForBothAppPairs(any(), any())
spySplitAnimationController.playSplitLaunchAnimation(
@@ -296,7 +296,7 @@
)
verify(spySplitAnimationController)
- .composeIconSplitLaunchAnimator(any(), any(), any(), any())
+ .composeIconSplitLaunchAnimator(any(), any(), any(), any(), any())
}
@Test
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
index 13c4f72..27e761a 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
@@ -26,6 +26,7 @@
import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION
import com.android.launcher3.model.data.AppInfo
import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.TaskItemInfo
import com.android.launcher3.statehandlers.DesktopVisibilityController
import com.android.quickstep.RecentsModel
import com.android.quickstep.RecentsModel.RecentTasksChangedListener
@@ -78,6 +79,13 @@
val listenerCaptor = ArgumentCaptor.forClass(RecentTasksChangedListener::class.java)
verify(mockRecentsModel).registerRecentTasksChangedListener(listenerCaptor.capture())
recentTasksChangedListener = listenerCaptor.value
+
+ // Make sure updateHotseatItemInfos() is called after commitRunningAppsToUI()
+ whenever(taskbarViewController.commitRunningAppsToUI()).then {
+ recentAppsController.updateHotseatItemInfos(
+ recentAppsController.shownHotseatItems.toTypedArray()
+ )
+ }
}
@Test
@@ -88,7 +96,7 @@
val newHotseatItems =
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = hotseatPackages,
- runningTaskPackages = emptyList(),
+ runningTasks = emptyList(),
recentTaskPackages = emptyList()
)
assertThat(newHotseatItems.map { it?.targetPackage })
@@ -103,7 +111,7 @@
val newHotseatItems =
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = hotseatPackages,
- runningTaskPackages = emptyList(),
+ runningTasks = emptyList(),
recentTaskPackages = emptyList()
)
assertThat(newHotseatItems.map { it?.targetPackage })
@@ -117,7 +125,7 @@
val newHotseatItems =
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1),
- runningTaskPackages = emptyList(),
+ runningTasks = emptyList(),
recentTaskPackages = emptyList()
)
val expectedPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2)
@@ -126,13 +134,58 @@
}
@Test
+ fun updateHotseatItemInfos_inDesktopMode_hotseatPackageHasRunningTask_hotseatItemLinksToTask() {
+ setInDesktopMode(true)
+
+ val newHotseatItems =
+ prepareHotseatAndRunningAndRecentApps(
+ hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2),
+ runningTasks = listOf(createTask(id = 1, HOTSEAT_PACKAGE_1)),
+ recentTaskPackages = emptyList()
+ )
+
+ assertThat(newHotseatItems).hasLength(2)
+ assertThat(newHotseatItems[0]).isInstanceOf(TaskItemInfo::class.java)
+ assertThat(newHotseatItems[1]).isNotInstanceOf(TaskItemInfo::class.java)
+ val hotseatItem1 = newHotseatItems[0] as TaskItemInfo
+ assertThat(hotseatItem1.taskId).isEqualTo(1)
+ }
+
+ @Test
+ fun updateHotseatItemInfos_inDesktopMode_twoRunningTasksSamePackage_hotseatCoversFirstTask() {
+ setInDesktopMode(true)
+
+ val newHotseatItems =
+ prepareHotseatAndRunningAndRecentApps(
+ hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2),
+ runningTasks =
+ listOf(
+ createTask(id = 1, HOTSEAT_PACKAGE_1),
+ createTask(id = 2, HOTSEAT_PACKAGE_1)
+ ),
+ recentTaskPackages = emptyList()
+ )
+
+ // First task is in Hotseat Items
+ assertThat(newHotseatItems).hasLength(2)
+ assertThat(newHotseatItems[0]).isInstanceOf(TaskItemInfo::class.java)
+ assertThat(newHotseatItems[1]).isNotInstanceOf(TaskItemInfo::class.java)
+ val hotseatItem1 = newHotseatItems[0] as TaskItemInfo
+ assertThat(hotseatItem1.taskId).isEqualTo(1)
+ // Second task is in shownTasks
+ val shownTasks = recentAppsController.shownTasks.map { it.task1 }
+ assertThat(shownTasks)
+ .containsExactlyElementsIn(listOf(createTask(id = 2, HOTSEAT_PACKAGE_1)))
+ }
+
+ @Test
fun updateHotseatItemInfos_canShowRecent_notInDesktopMode_returnsNonPredictedHotseatItems() {
recentAppsController.canShowRecentApps = true
setInDesktopMode(false)
val newHotseatItems =
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1),
- runningTaskPackages = emptyList(),
+ runningTasks = emptyList(),
recentTaskPackages = emptyList()
)
val expectedPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2)
@@ -146,7 +199,11 @@
setInDesktopMode(true)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1),
- runningTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2),
+ runningTasks =
+ listOf(
+ createTask(id = 1, RUNNING_APP_PACKAGE_1),
+ createTask(id = 2, RUNNING_APP_PACKAGE_2)
+ ),
recentTaskPackages = emptyList()
)
assertThat(recentAppsController.shownTasks).isEmpty()
@@ -158,7 +215,7 @@
setInDesktopMode(false)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1),
- runningTaskPackages = emptyList(),
+ runningTasks = emptyList(),
recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
)
assertThat(recentAppsController.shownTasks).isEmpty()
@@ -169,11 +226,15 @@
setInDesktopMode(false)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2),
+ runningTasks =
+ listOf(
+ createTask(id = 1, RUNNING_APP_PACKAGE_1),
+ createTask(id = 2, RUNNING_APP_PACKAGE_2)
+ ),
recentTaskPackages = emptyList()
)
assertThat(recentAppsController.shownTasks).isEmpty()
- assertThat(recentAppsController.minimizedAppPackages).isEmpty()
+ assertThat(recentAppsController.minimizedTaskIds).isEmpty()
}
@Test
@@ -181,7 +242,7 @@
setInDesktopMode(true)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = emptyList(),
+ runningTasks = emptyList(),
recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
)
assertThat(recentAppsController.shownTasks).isEmpty()
@@ -190,120 +251,161 @@
@Test
fun onRecentTasksChanged_inDesktopMode_shownTasks_returnsRunningTasks() {
setInDesktopMode(true)
- val runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+ val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+ val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = runningTaskPackages,
+ runningTasks = listOf(task1, task2),
recentTaskPackages = emptyList()
)
- val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
- assertThat(shownPackages).containsExactlyElementsIn(runningTaskPackages)
- }
-
- @Test
- fun onRecentTasksChanged_inDesktopMode_runningAppIsHotseatItem_shownTasks_returnsDistinctItems() {
- setInDesktopMode(true)
- prepareHotseatAndRunningAndRecentApps(
- hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2),
- runningTaskPackages =
- listOf(HOTSEAT_PACKAGE_1, RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2),
- recentTaskPackages = emptyList()
- )
- val expectedPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
- val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
- assertThat(shownPackages).containsExactlyElementsIn(expectedPackages)
+ val shownTasks = recentAppsController.shownTasks.map { it.task1 }
+ assertThat(shownTasks).containsExactlyElementsIn(listOf(task1, task2))
}
@Test
fun onRecentTasksChanged_notInDesktopMode_getRunningApps_returnsEmptySet() {
setInDesktopMode(false)
+ val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+ val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2),
+ runningTasks = listOf(task1, task2),
recentTaskPackages = emptyList()
)
- assertThat(recentAppsController.runningAppPackages).isEmpty()
- assertThat(recentAppsController.minimizedAppPackages).isEmpty()
+ assertThat(recentAppsController.runningTaskIds).isEmpty()
+ assertThat(recentAppsController.minimizedTaskIds).isEmpty()
}
@Test
fun onRecentTasksChanged_inDesktopMode_getRunningApps_returnsAllDesktopTasks() {
setInDesktopMode(true)
- val runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+ val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+ val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = runningTaskPackages,
+ runningTasks = listOf(task1, task2),
recentTaskPackages = emptyList()
)
- assertThat(recentAppsController.runningAppPackages)
- .containsExactlyElementsIn(runningTaskPackages)
- assertThat(recentAppsController.minimizedAppPackages).isEmpty()
+ assertThat(recentAppsController.runningTaskIds).containsExactlyElementsIn(listOf(1, 2))
+ assertThat(recentAppsController.minimizedTaskIds).isEmpty()
}
@Test
fun onRecentTasksChanged_inDesktopMode_getRunningApps_includesHotseat() {
setInDesktopMode(true)
- val runningTaskPackages =
- listOf(HOTSEAT_PACKAGE_1, RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+ val runningTasks =
+ listOf(
+ createTask(id = 1, HOTSEAT_PACKAGE_1),
+ createTask(id = 2, RUNNING_APP_PACKAGE_1),
+ createTask(id = 3, RUNNING_APP_PACKAGE_2)
+ )
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2),
- runningTaskPackages = runningTaskPackages,
+ runningTasks = runningTasks,
recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
)
- assertThat(recentAppsController.runningAppPackages)
- .containsExactlyElementsIn(runningTaskPackages)
- assertThat(recentAppsController.minimizedAppPackages).isEmpty()
+ assertThat(recentAppsController.runningTaskIds).containsExactlyElementsIn(listOf(1, 2, 3))
+ assertThat(recentAppsController.minimizedTaskIds).isEmpty()
}
@Test
- fun getMinimizedApps_inDesktopMode_returnsAllAppsRunningAndInvisibleAppsMinimized() {
+ fun onRecentTasksChanged_inDesktopMode_allAppsRunningAndInvisibleAppsMinimized() {
setInDesktopMode(true)
- val runningTaskPackages =
- listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_3)
- val minimizedTaskIndices = setOf(2) // RUNNING_APP_PACKAGE_3
+ val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+ val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
+ val task3Minimized = createTask(id = 3, RUNNING_APP_PACKAGE_3, isVisible = false)
+ val runningTasks = listOf(task1, task2, task3Minimized)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = runningTaskPackages,
- minimizedTaskIndices = minimizedTaskIndices,
+ runningTasks = runningTasks,
recentTaskPackages = emptyList()
)
- assertThat(recentAppsController.runningAppPackages)
- .containsExactlyElementsIn(runningTaskPackages)
- assertThat(recentAppsController.minimizedAppPackages).containsExactly(RUNNING_APP_PACKAGE_3)
+ assertThat(recentAppsController.runningTaskIds).containsExactly(1, 2, 3)
+ assertThat(recentAppsController.minimizedTaskIds).containsExactly(3)
}
@Test
- fun getMinimizedApps_inDesktopMode_twoTasksSamePackageOneMinimizedReturnsNotMinimized() {
+ fun onRecentTasksChanged_inDesktopMode_samePackage_differentTasks_severalRunningTasks() {
setInDesktopMode(true)
- val runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_1)
- val minimizedTaskIndices = setOf(1) // The second RUNNING_APP_PACKAGE_1 task.
+ val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+ val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = runningTaskPackages,
- minimizedTaskIndices = minimizedTaskIndices,
+ runningTasks = listOf(task1, task2),
recentTaskPackages = emptyList()
)
- assertThat(recentAppsController.runningAppPackages)
- .containsExactlyElementsIn(runningTaskPackages.toSet())
- assertThat(recentAppsController.minimizedAppPackages).isEmpty()
+ assertThat(recentAppsController.runningTaskIds).containsExactlyElementsIn(listOf(1, 2))
}
@Test
fun onRecentTasksChanged_inDesktopMode_shownTasks_maintainsOrder() {
setInDesktopMode(true)
- val originalOrder = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+ val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+ val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = originalOrder,
+ runningTasks = listOf(task1, task2),
recentTaskPackages = emptyList()
)
+
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = listOf(RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_1),
+ runningTasks = listOf(task2, task1),
recentTaskPackages = emptyList()
)
- val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
- assertThat(shownPackages).isEqualTo(originalOrder)
+
+ val shownTasks = recentAppsController.shownTasks.map { it.task1 }
+ assertThat(shownTasks).isEqualTo(listOf(task1, task2))
+ }
+
+ @Test
+ fun onRecentTasksChanged_inDesktopMode_multiInstance_shownTasks_maintainsOrder() {
+ setInDesktopMode(true)
+ val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+ val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_1)
+ prepareHotseatAndRunningAndRecentApps(
+ hotseatPackages = emptyList(),
+ runningTasks = listOf(task1, task2),
+ recentTaskPackages = emptyList()
+ )
+
+ prepareHotseatAndRunningAndRecentApps(
+ hotseatPackages = emptyList(),
+ runningTasks = listOf(task2, task1),
+ recentTaskPackages = emptyList()
+ )
+
+ val shownTasks = recentAppsController.shownTasks.map { it.task1 }
+ assertThat(shownTasks).isEqualTo(listOf(task1, task2))
+ }
+
+ @Test
+ fun updateHotseatItems_inDesktopMode_multiInstanceHotseatPackage_shownItems_maintainsOrder() {
+ setInDesktopMode(true)
+ val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+ val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_1)
+ prepareHotseatAndRunningAndRecentApps(
+ hotseatPackages = listOf(RUNNING_APP_PACKAGE_1),
+ runningTasks = listOf(task1, task2),
+ recentTaskPackages = emptyList()
+ )
+ updateRecentTasks( // Trigger a recent-tasks change before calling updateHotseatItems()
+ runningTasks = listOf(task2, task1),
+ recentTaskPackages = emptyList()
+ )
+
+ prepareHotseatAndRunningAndRecentApps(
+ hotseatPackages = listOf(RUNNING_APP_PACKAGE_1),
+ runningTasks = listOf(task2, task1),
+ recentTaskPackages = emptyList()
+ )
+
+ val newHotseatItems = recentAppsController.shownHotseatItems
+ assertThat(newHotseatItems).hasSize(1)
+ assertThat(newHotseatItems[0]).isInstanceOf(TaskItemInfo::class.java)
+ assertThat((newHotseatItems[0] as TaskItemInfo).taskId).isEqualTo(1)
+ val shownTasks = recentAppsController.shownTasks.map { it.task1 }
+ assertThat(shownTasks).isEqualTo(listOf(task2))
}
@Test
@@ -311,12 +413,12 @@
setInDesktopMode(false)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = emptyList(),
+ runningTasks = emptyList(),
recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2, RECENT_PACKAGE_3)
)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = emptyList(),
+ runningTasks = emptyList(),
recentTaskPackages = listOf(RECENT_PACKAGE_2, RECENT_PACKAGE_3, RECENT_PACKAGE_1)
)
val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
@@ -327,15 +429,17 @@
@Test
fun onRecentTasksChanged_inDesktopMode_addTask_shownTasks_maintainsOrder() {
setInDesktopMode(true)
+ val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+ val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
+ val task3 = createTask(id = 3, RUNNING_APP_PACKAGE_3)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2),
+ runningTasks = listOf(task1, task2),
recentTaskPackages = emptyList()
)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages =
- listOf(RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_3),
+ runningTasks = listOf(task2, task1, task3),
recentTaskPackages = emptyList()
)
val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
@@ -349,12 +453,12 @@
setInDesktopMode(false)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = emptyList(),
+ runningTasks = emptyList(),
recentTaskPackages = listOf(RECENT_PACKAGE_3, RECENT_PACKAGE_2)
)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = emptyList(),
+ runningTasks = emptyList(),
recentTaskPackages = listOf(RECENT_PACKAGE_2, RECENT_PACKAGE_3, RECENT_PACKAGE_1)
)
val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
@@ -365,15 +469,17 @@
@Test
fun onRecentTasksChanged_inDesktopMode_removeTask_shownTasks_maintainsOrder() {
setInDesktopMode(true)
+ val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+ val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
+ val task3 = createTask(id = 3, RUNNING_APP_PACKAGE_3)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages =
- listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_3),
+ runningTasks = listOf(task1, task2, task3),
recentTaskPackages = emptyList()
)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = listOf(RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_1),
+ runningTasks = listOf(task2, task1),
recentTaskPackages = emptyList()
)
val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
@@ -385,12 +491,12 @@
setInDesktopMode(false)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = emptyList(),
+ runningTasks = emptyList(),
recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2, RECENT_PACKAGE_3)
)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = emptyList(),
+ runningTasks = emptyList(),
recentTaskPackages = listOf(RECENT_PACKAGE_2, RECENT_PACKAGE_3)
)
val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
@@ -401,27 +507,31 @@
@Test
fun onRecentTasksChanged_enterDesktopMode_shownTasks_onlyIncludesRunningTasks() {
setInDesktopMode(false)
- val runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+ val runningTask1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+ val runningTask2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
val recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
+
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = runningTaskPackages,
+ runningTasks = listOf(runningTask1, runningTask2),
recentTaskPackages = recentTaskPackages
)
+
setInDesktopMode(true)
recentTasksChangedListener.onRecentTasksChanged()
val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
- assertThat(shownPackages).containsExactlyElementsIn(runningTaskPackages)
+ assertThat(shownPackages).containsExactly(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
}
@Test
fun onRecentTasksChanged_exitDesktopMode_shownTasks_onlyIncludesRecentTasks() {
setInDesktopMode(true)
- val runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+ val runningTask1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+ val runningTask2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
val recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2, RECENT_PACKAGE_3)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = runningTaskPackages,
+ runningTasks = listOf(runningTask1, runningTask2),
recentTaskPackages = recentTaskPackages
)
setInDesktopMode(false)
@@ -437,7 +547,7 @@
setInDesktopMode(false)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = emptyList(),
+ runningTasks = emptyList(),
recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2, RECENT_PACKAGE_3)
)
val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
@@ -449,9 +559,11 @@
@Test
fun onRecentTasksChanged_notInDesktopMode_hasRecentAndRunningTasks_shownTasks_returnsRecentTaskAndDesktopTile() {
setInDesktopMode(false)
+ val runningTask1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+ val runningTask2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2),
+ runningTasks = listOf(runningTask1, runningTask2),
recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
)
val shownPackages = recentAppsController.shownTasks.map { it.packageNames }
@@ -467,7 +579,7 @@
setInDesktopMode(false)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = emptyList(),
+ runningTasks = emptyList(),
recentTaskPackages = listOf(RECENT_SPLIT_PACKAGES_1, RECENT_PACKAGE_1, RECENT_PACKAGE_2)
)
val shownPackages = recentAppsController.shownTasks.map { it.packageNames }
@@ -483,14 +595,14 @@
setInDesktopMode(false)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = emptyList(),
+ runningTasks = emptyList(),
recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
)
verify(taskbarViewController, times(1)).commitRunningAppsToUI()
// Call onRecentTasksChanged() again with the same tasks, verify it's a no-op.
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = emptyList(),
+ runningTasks = emptyList(),
recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
)
verify(taskbarViewController, times(1)).commitRunningAppsToUI()
@@ -499,16 +611,18 @@
@Test
fun onRecentTasksChanged_inDesktopMode_noActualChangeToRunning_commitRunningAppsToUI_notCalled() {
setInDesktopMode(true)
+ val runningTask1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+ val runningTask2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2),
+ runningTasks = listOf(runningTask1, runningTask2),
recentTaskPackages = emptyList()
)
verify(taskbarViewController, times(1)).commitRunningAppsToUI()
// Call onRecentTasksChanged() again with the same tasks, verify it's a no-op.
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2),
+ runningTasks = listOf(runningTask1, runningTask2),
recentTaskPackages = emptyList()
)
verify(taskbarViewController, times(1)).commitRunningAppsToUI()
@@ -517,21 +631,23 @@
@Test
fun onRecentTasksChanged_onlyMinimizedChanges_commitRunningAppsToUI_isCalled() {
setInDesktopMode(true)
- val runningTasks = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+ val task1Minimized = createTask(id = 1, RUNNING_APP_PACKAGE_1, isVisible = false)
+ val task2Visible = createTask(id = 2, RUNNING_APP_PACKAGE_2)
+ val task2Minimized = createTask(id = 2, RUNNING_APP_PACKAGE_2, isVisible = false)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = runningTasks,
- minimizedTaskIndices = setOf(0),
+ runningTasks = listOf(task1Minimized, task2Visible),
recentTaskPackages = emptyList()
)
verify(taskbarViewController, times(1)).commitRunningAppsToUI()
+
// Call onRecentTasksChanged() again with a new minimized app, verify we update UI.
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = runningTasks,
- minimizedTaskIndices = setOf(0, 1),
+ runningTasks = listOf(task1Minimized, task2Minimized),
recentTaskPackages = emptyList()
)
+
verify(taskbarViewController, times(2)).commitRunningAppsToUI()
}
@@ -539,36 +655,46 @@
fun onRecentTasksChanged_hotseatAppStartsRunning_commitRunningAppsToUI_isCalled() {
setInDesktopMode(true)
val hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2)
+ val originalTasks = listOf(createTask(id = 1, RUNNING_APP_PACKAGE_1))
+ val newTasks =
+ listOf(createTask(id = 1, RUNNING_APP_PACKAGE_1), createTask(id = 2, HOTSEAT_PACKAGE_1))
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = hotseatPackages,
- runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1),
+ runningTasks = originalTasks,
recentTaskPackages = emptyList()
)
verify(taskbarViewController, times(1)).commitRunningAppsToUI()
+
// Call onRecentTasksChanged() again with a new running app, verify we update UI.
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = hotseatPackages,
- runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, HOTSEAT_PACKAGE_1),
+ runningTasks = newTasks,
recentTaskPackages = emptyList()
)
+
verify(taskbarViewController, times(2)).commitRunningAppsToUI()
}
private fun prepareHotseatAndRunningAndRecentApps(
hotseatPackages: List<String>,
- runningTaskPackages: List<String>,
- minimizedTaskIndices: Set<Int> = emptySet(),
+ runningTasks: List<Task>,
recentTaskPackages: List<String>,
): Array<ItemInfo?> {
val hotseatItems = createHotseatItemsFromPackageNames(hotseatPackages)
- val newHotseatItems =
- recentAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray())
- val runningTasks = createDesktopTask(runningTaskPackages, minimizedTaskIndices)
+ recentAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray())
+ updateRecentTasks(runningTasks, recentTaskPackages)
+ return recentAppsController.shownHotseatItems.toTypedArray()
+ }
+
+ private fun updateRecentTasks(
+ runningTasks: List<Task>,
+ recentTaskPackages: List<String>,
+ ) {
val recentTasks = createRecentTasksFromPackageNames(recentTaskPackages)
val allTasks =
ArrayList<GroupTask>().apply {
- if (runningTasks != null) {
- add(runningTasks)
+ if (!runningTasks.isEmpty()) {
+ add(DesktopTask(ArrayList(runningTasks)))
}
addAll(recentTasks)
}
@@ -580,20 +706,21 @@
.whenever(mockRecentsModel)
.getTasks(any<Consumer<List<GroupTask>>>())
recentTasksChangedListener.onRecentTasksChanged()
- return newHotseatItems
}
private fun createHotseatItemsFromPackageNames(packageNames: List<String>): List<ItemInfo> {
- return packageNames.map {
- createTestAppInfo(packageName = it).apply {
- container =
- if (it.startsWith("predicted")) {
- CONTAINER_HOTSEAT_PREDICTION
- } else {
- CONTAINER_HOTSEAT
- }
+ return packageNames
+ .map {
+ createTestAppInfo(packageName = it).apply {
+ container =
+ if (it.startsWith("predicted")) {
+ CONTAINER_HOTSEAT_PREDICTION
+ } else {
+ CONTAINER_HOTSEAT
+ }
+ }
}
- }
+ .map { it.makeWorkspaceItem(taskbarActivityContext) }
}
private fun createTestAppInfo(
@@ -601,39 +728,24 @@
className: String = "testClassName"
) = AppInfo(ComponentName(packageName, className), className /* title */, userHandle, Intent())
- private fun createDesktopTask(
- packageNames: List<String>,
- minimizedTaskIndices: Set<Int>
- ): DesktopTask? {
- if (packageNames.isEmpty()) return null
-
- return DesktopTask(
- ArrayList(
- packageNames.mapIndexed { index, packageName ->
- createTask(packageName, index !in minimizedTaskIndices)
- }
- )
- )
- }
-
private fun createRecentTasksFromPackageNames(packageNames: List<String>): List<GroupTask> {
- return packageNames.map {
- if (it.startsWith("split")) {
- val splitPackages = it.split("_")
+ return packageNames.map { packageName ->
+ if (packageName.startsWith("split")) {
+ val splitPackages = packageName.split("_")
GroupTask(
- createTask(splitPackages[0]),
- createTask(splitPackages[1]),
+ createTask(100, splitPackages[0]),
+ createTask(101, splitPackages[1]),
/* splitBounds = */ null
)
} else {
- GroupTask(createTask(it))
+ // Use the number at the end of the test packageName as the id.
+ val id = 1000 + packageName[packageName.length - 1].code
+ GroupTask(createTask(id, packageName))
}
}
}
- private fun createTask(packageName: String, isVisible: Boolean = true): Task {
- // Use the number at the end of the test packageName as the id.
- val id = packageName[packageName.length - 1].code
+ private fun createTask(id: Int, packageName: String, isVisible: Boolean = true): Task {
return Task(
Task.TaskKey(
id,
diff --git a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
index 298dd6c..f5d082d 100644
--- a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
+++ b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
@@ -21,6 +21,7 @@
import static com.android.launcher3.util.NavigationMode.NO_BUTTON;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -288,6 +289,34 @@
assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion2.getX(), inRegion2.getY()));
}
+ @Test
+ public void testSimpleOrientationTouchTransformer() {
+ final DisplayController displayController = mock(DisplayController.class);
+ doReturn(mInfo).when(displayController).getInfo();
+ final SimpleOrientationTouchTransformer transformer =
+ new SimpleOrientationTouchTransformer(getApplicationContext(), displayController);
+ final MotionEvent move1 = generateMotionEvent(MotionEvent.ACTION_MOVE, 100, 10);
+ transformer.transform(move1, Surface.ROTATION_90);
+ // The position is transformed to 90 degree.
+ assertEquals(10, move1.getX(), 0f /* delta */);
+ assertEquals(NORMAL_SCREEN_SIZE.getWidth() - 100, move1.getY(), 0f /* delta */);
+
+ // If the touching state is specified, the position is still transformed to 90 degree even
+ // if the given rotation is changed.
+ final MotionEvent move2 = generateMotionEvent(MotionEvent.ACTION_MOVE, 100, 10);
+ transformer.updateTouchingOrientation(Surface.ROTATION_90);
+ transformer.transform(move2, Surface.ROTATION_0);
+ assertEquals(move1.getX(), move2.getX(), 0f /* delta */);
+ assertEquals(move1.getY(), move2.getY(), 0f /* delta */);
+
+ // If the touching state is cleared, it restores to use the given rotation.
+ final MotionEvent move3 = generateMotionEvent(MotionEvent.ACTION_MOVE, 100, 10);
+ transformer.clearTouchingOrientation();
+ transformer.transform(move3, Surface.ROTATION_0);
+ assertEquals(100, move3.getX(), 0f /* delta */);
+ assertEquals(10, move3.getY(), 0f /* delta */);
+ }
+
private DisplayController.Info createDisplayInfo(Size screenSize, int rotation) {
Point displaySize = new Point(screenSize.getWidth(), screenSize.getHeight());
RotationUtils.rotateSize(displaySize, rotation);
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java b/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java
index ec245ee..c24e974 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java
@@ -24,6 +24,7 @@
import androidx.test.filters.LargeTest;
import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
+import com.android.launcher3.util.rule.ScreenRecordRule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -133,6 +134,7 @@
@Test
@PortraitLandscape
+ @ScreenRecordRule.ScreenRecord // b/349439239
public void testLaunchAppInSplitscreen_fromTaskbarAllApps() {
getTaskbar().openAllApps()
.getAppIcon(TEST_APP_NAME)
diff --git a/res/anim-v33/shared_x_axis_activity_close_enter.xml b/res/anim-v33/shared_x_axis_activity_close_enter.xml
deleted file mode 100644
index 3d7ad2b..0000000
--- a/res/anim-v33/shared_x_axis_activity_close_enter.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 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.
- -->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:showBackdrop="true">
-
- <alpha
- android:fromAlpha="0.0"
- android:toAlpha="1.0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/standard_decelerate_interpolator"
- android:startOffset="100"
- android:duration="350" />
-
- <translate
- android:fromXDelta="-25%"
- android:toXDelta="0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/emphasized_interpolator"
- android:startOffset="0"
- android:duration="450" />
-
-</set>
\ No newline at end of file
diff --git a/res/anim-v33/shared_x_axis_activity_close_exit.xml b/res/anim-v33/shared_x_axis_activity_close_exit.xml
deleted file mode 100644
index fb63602..0000000
--- a/res/anim-v33/shared_x_axis_activity_close_exit.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 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.
- -->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false">
-
- <alpha
- android:fromAlpha="1.0"
- android:toAlpha="0.0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/standard_accelerate_interpolator"
- android:startOffset="0"
- android:duration="100" />
-
- <translate
- android:fromXDelta="0"
- android:toXDelta="25%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/emphasized_interpolator"
- android:startOffset="0"
- android:duration="450" />
-
-</set>
\ No newline at end of file
diff --git a/res/anim-v33/shared_x_axis_activity_open_enter.xml b/res/anim-v33/shared_x_axis_activity_open_enter.xml
deleted file mode 100644
index cba74ba..0000000
--- a/res/anim-v33/shared_x_axis_activity_open_enter.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 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.
- -->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:showBackdrop="true">
-
- <alpha
- android:fromAlpha="0.0"
- android:toAlpha="1.0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/standard_decelerate_interpolator"
- android:startOffset="100"
- android:duration="350" />
-
- <translate
- android:fromXDelta="25%"
- android:toXDelta="0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/emphasized_interpolator"
- android:startOffset="0"
- android:duration="450" />
-
-</set>
\ No newline at end of file
diff --git a/res/anim-v33/shared_x_axis_activity_open_exit.xml b/res/anim-v33/shared_x_axis_activity_open_exit.xml
deleted file mode 100644
index 22e878d..0000000
--- a/res/anim-v33/shared_x_axis_activity_open_exit.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 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.
- -->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false">
-
- <alpha
- android:fromAlpha="1.0"
- android:toAlpha="0.0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/standard_accelerate_interpolator"
- android:startOffset="0"
- android:duration="100" />
-
- <translate
- android:fromXDelta="0"
- android:toXDelta="-25%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/emphasized_interpolator"
- android:startOffset="0"
- android:duration="450" />
-
-</set>
\ No newline at end of file
diff --git a/res/drawable/cloud_download_24px.xml b/res/drawable/cloud_download_24px.xml
new file mode 100644
index 0000000..6f7c95a
--- /dev/null
+++ b/res/drawable/cloud_download_24px.xml
@@ -0,0 +1,11 @@
+<!-- GM3 icon cloud_download:vd_theme_24 -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M260,800Q169,800 104.5,737Q40,674 40,583Q40,505 87,444Q134,383 210,366Q227,294 295,229Q363,164 440,164Q473,164 496.5,187.5Q520,211 520,244L520,486L584,424L640,480L480,640L320,480L376,424L440,486L440,244Q364,258 322,317.5Q280,377 280,440L260,440Q202,440 161,481Q120,522 120,580Q120,638 161,679Q202,720 260,720L740,720Q782,720 811,691Q840,662 840,620Q840,578 811,549Q782,520 740,520L680,520L680,440Q680,392 658,350.5Q636,309 600,280L600,187Q674,222 717,290.5Q760,359 760,440L760,440L760,440Q829,448 874.5,499.5Q920,551 920,620Q920,695 867.5,747.5Q815,800 740,800L260,800ZM480,442Q480,442 480,442Q480,442 480,442L480,442Q480,442 480,442Q480,442 480,442L480,442Q480,442 480,442Q480,442 480,442L480,442Q480,442 480,442Q480,442 480,442Q480,442 480,442Q480,442 480,442L480,442Q480,442 480,442Q480,442 480,442Q480,442 480,442Q480,442 480,442L480,442L480,442Q480,442 480,442Q480,442 480,442Z"/>
+</vector>
diff --git a/res/drawable/ic_close_work_edu.xml b/res/drawable/ic_close_work_edu.xml
new file mode 100644
index 0000000..f336eea
--- /dev/null
+++ b/res/drawable/ic_close_work_edu.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="@color/material_color_on_surface"
+ android:pathData="M256,760L200,704L424,480L200,256L256,200L480,424L704,200L760,256L536,480L760,704L704,760L480,536L256,760Z"/>
+</vector>
diff --git a/res/drawable/rounded_action_button.xml b/res/drawable/rounded_action_button.xml
index 81e94f7..e283d3f 100644
--- a/res/drawable/rounded_action_button.xml
+++ b/res/drawable/rounded_action_button.xml
@@ -18,11 +18,11 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="rectangle">
- <solid android:color="?androidprv:attr/colorSurfaceVariant" />
+ <solid android:color="@color/material_color_surface_container_low" />
<corners android:radius="@dimen/rounded_button_radius" />
<stroke
android:width="1dp"
- android:color="?androidprv:attr/colorSurfaceVariant" />
+ android:color="@color/material_color_surface_container_low" />
<padding
android:left="@dimen/rounded_button_padding"
android:right="@dimen/rounded_button_padding" />
diff --git a/res/drawable/work_card.xml b/res/drawable/work_card.xml
index 4a66cac..9bf2b8d 100644
--- a/res/drawable/work_card.xml
+++ b/res/drawable/work_card.xml
@@ -16,9 +16,8 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="rectangle">
- <solid android:color="?androidprv:attr/colorSurface" />
+ <solid android:color="@color/material_color_surface_container_highest" />
<corners android:radius="@dimen/work_edu_card_radius" />
</shape>
diff --git a/res/layout/widgets_two_pane_sheet.xml b/res/layout/widgets_two_pane_sheet.xml
index bb2b7bd..ce5eed9 100644
--- a/res/layout/widgets_two_pane_sheet.xml
+++ b/res/layout/widgets_two_pane_sheet.xml
@@ -48,20 +48,23 @@
android:textSize="24sp" />
<TextView
- android:id="@+id/no_widgets_text"
- style="@style/PrimaryHeadline"
+ android:id="@+id/widget_picker_description"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="center"
- android:textSize="18sp"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:layout_below="@id/title"
+ android:maxLines="1"
+ android:paddingHorizontal="@dimen/widget_list_horizontal_margin_two_pane"
+ android:textColor="?attr/widgetPickerDescriptionColor"
android:visibility="gone"
- tools:text="@string/no_widgets_available" />
+ android:lineHeight="20sp"
+ android:textSize="14sp" />
<LinearLayout
android:id="@+id/linear_layout_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_below="@id/title">
+ android:layout_below="@id/widget_picker_description">
<FrameLayout
android:id="@+id/recycler_view_container"
@@ -124,6 +127,16 @@
android:background="@drawable/widgets_surface_background"
android:importantForAccessibility="yes"
android:id="@+id/right_pane">
+ <TextView
+ android:id="@+id/no_widgets_text"
+ style="@style/PrimaryHeadline"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:textSize="18sp"
+ android:visibility="gone"
+ tools:text="@string/no_widgets_available" />
+
<!-- Shown when there are recommendations to display -->
<LinearLayout
android:id="@+id/widget_recommendations_container"
diff --git a/res/layout/work_apps_edu.xml b/res/layout/work_apps_edu.xml
index 99db8c6..c581ae3 100644
--- a/res/layout/work_apps_edu.xml
+++ b/res/layout/work_apps_edu.xml
@@ -54,7 +54,7 @@
android:layout_gravity="center"
android:contentDescription="@string/accessibility_close"
android:background="@android:color/transparent"
- android:src="@drawable/ic_remove_no_shadow" />
+ android:src="@drawable/ic_close_work_edu" />
</FrameLayout>
</LinearLayout>
</com.android.launcher3.allapps.WorkEduCard>
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 90e784d..1a8f8e2 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Legstukke gedeaktiveer in Veiligmodus"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Kortpad is nie beskikbaar nie"</string>
<string name="home_screen" msgid="5629429142036709174">"Tuis"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Stel <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> as verstektuisskermapp in Instellings"</string>
<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>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 1289b8c..80447c5 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"ምግብሮች በደህንነቱ የተጠበቀ ሁኔታ ተሰናክለዋል"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"አቋራጭ አይገኝም"</string>
<string name="home_screen" msgid="5629429142036709174">"መነሻ"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"በቅንብሮች ውስጥ <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>ን እንደ ነባሪ የHome መተግበሪያ ያቀናብሩ"</string>
<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>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 37d6fba..4eee121 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"الأدوات غير مفعّلة في الوضع الآمن"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"الاختصار غير متاح"</string>
<string name="home_screen" msgid="5629429142036709174">"الشاشة الرئيسية"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"يمكن ضبط \"<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>\" كتطبيق الشاشة الرئيسية التلقائي من خلال \"الإعدادات\""</string>
<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>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index e983ce8..52ec7ea 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"ৱিজেটবোৰক সুৰক্ষিত ম\'ডত অক্ষম কৰা হ’ল"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"শ্বৰ্টকাট নাই"</string>
<string name="home_screen" msgid="5629429142036709174">"গৃহ স্ক্ৰীন"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"ছেটিঙত <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>ক ডিফ’ল্ট গৃহপৃষ্ঠা এপ্ হিচাপে ছেট কৰক"</string>
<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>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index f390c7c..6c1cc46 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Vidcetlər Güvənli rejimdə deaktiv edilib"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Qısayol əlçatan deyil"</string>
<string name="home_screen" msgid="5629429142036709174">"Əsas səhifə"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Ayarlarda <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> tətbiqini defolt əsas ekran tətbiqi kimi ayarlayın"</string>
<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>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index fc71eeb..24328cf 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Vidžeti su onemogućeni u Bezbednom režimu"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Prečica nije dostupna"</string>
<string name="home_screen" msgid="5629429142036709174">"Početni ekran"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Podesite <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> kao podrazumevanu početnu aplikaciju u Podešavanjima"</string>
<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>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 4589125..ebbb378 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Віджэты адключаны ў Бяспечным рэжыме"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Ярлык недаступны"</string>
<string name="home_screen" msgid="5629429142036709174">"Галоўны экран"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Зрабіць <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> стандартнай праграмай для галоўнага экрана, перайшоўшы ў Налады"</string>
<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>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 643c24e..d5d948e 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Приспособленията са деактивирани в безопасния режим"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Няма достъп до прекия път"</string>
<string name="home_screen" msgid="5629429142036709174">"Начален екран"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"От настройките задайте <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> като основното приложение за начален екран"</string>
<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>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index 7e10a0e..cf75fb58 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"সুরক্ষিত মোডে উইজেট নিষ্ক্রিয় থাকে"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"শর্টকাটগুলি অনুপলব্ধ"</string>
<string name="home_screen" msgid="5629429142036709174">"হোম"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"সেটিংসে গিয়ে <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> অ্যাপকে ডিফল্ট হোম অ্যাপ হিসেবে সেট করুন"</string>
<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>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 10aa86a..1758c39 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Vidžeti su onemogućeni u sigurnom načinu rada."</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Prečica nije dostupna"</string>
<string name="home_screen" msgid="5629429142036709174">"Početni ekran"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Postavite <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> kao zadanu aplikaciju za početni ekran u Postavkama"</string>
<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>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 3d2ff4a..bf578f5 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"En Mode segur, els widgets estan desactivats."</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"La drecera no està disponible"</string>
<string name="home_screen" msgid="5629429142036709174">"Inici"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Defineix <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> com a aplicació d\'inici predeterminada a Configuració"</string>
<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>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 14a3583..9a8fc6f 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"V nouzovém režimu jsou widgety zakázány."</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Zkratka není k dispozici"</string>
<string name="home_screen" msgid="5629429142036709174">"Domů"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Nastavit <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> jako výchozí vstupní aplikaci v Nastavení"</string>
<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>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index f18e1e8..208c2ef 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets er deaktiveret i Beskyttet tilstand"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Genvejen er ikke tilgængelig"</string>
<string name="home_screen" msgid="5629429142036709174">"Startskærm"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Angiv <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> som standardstartapp i Indstillinger"</string>
<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>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 9a4e5e2..380030b 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets im abgesicherten Modus deaktiviert"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Verknüpfung nicht verfügbar"</string>
<string name="home_screen" msgid="5629429142036709174">"Startbildschirm"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> in den Einstellungen als Stand-Start-App festlegen"</string>
<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>
@@ -37,7 +36,7 @@
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Dieses App-Paar wird auf diesem Gerät nicht unterstützt"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Gerät aufklappen, um dieses App-Paar zu verwenden"</string>
<string name="app_pair_not_available" msgid="3556767440808032031">"App-Paar nicht verfügbar"</string>
- <string name="long_press_widget_to_add" msgid="3587712543577675817">"Zum Verschieben des Widgets berühren und halten"</string>
+ <string name="long_press_widget_to_add" msgid="3587712543577675817">"Zum Verschieben des Widgets gedrückt halten"</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Doppeltippen und halten, um ein Widget zu bewegen oder benutzerdefinierte Aktionen zu nutzen."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d breit und %2$d hoch"</string>
@@ -75,7 +74,7 @@
<string name="label_application" msgid="8531721983832654978">"App"</string>
<string name="all_apps_label" msgid="5015784846527570951">"Alle Apps"</string>
<string name="notifications_header" msgid="1404149926117359025">"Benachrichtigungen"</string>
- <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Zum Verschieben einer Verknüpfung berühren und halten"</string>
+ <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Zum Verschieben einer Verknüpfung gedrückt halten"</string>
<string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Doppeltippen und halten, um eine Verknüpfung zu bewegen oder benutzerdefinierte Aktionen zu nutzen."</string>
<string name="out_of_space" msgid="6455557115204099579">"Auf diesem Startbildschirm ist kein Platz mehr vorhanden"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Ablage \"Favoriten\" ist voll."</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 840ff5b..e86ebae 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Τα γραφικά στοιχεία απενεργοποιήθηκαν στην ασφαλή λειτουργία"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Η συντόμευση δεν είναι διαθέσιμη"</string>
<string name="home_screen" msgid="5629429142036709174">"Αρχική οθόνη"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Ορίστε το <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> ως την προεπιλεγμένη εφαρμογή αρχικής οθόνης στις Ρυθμίσεις"</string>
<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>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 4deacb6..50c5976 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
<string name="home_screen" msgid="5629429142036709174">"Home"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Set <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> as the default home app in Settings"</string>
<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>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 4deacb6..50c5976 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
<string name="home_screen" msgid="5629429142036709174">"Home"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Set <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> as the default home app in Settings"</string>
<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>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 4deacb6..50c5976 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
<string name="home_screen" msgid="5629429142036709174">"Home"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Set <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> as the default home app in Settings"</string>
<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>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index e734744..5879129 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets inhabilitados en modo seguro"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"El acceso directo no está disponible"</string>
<string name="home_screen" msgid="5629429142036709174">"Pantalla principal"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Establece <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> como la app de inicio predeterminada en Configuración"</string>
<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>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 3e0e516..8d384ac 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets inhabilitados en modo Seguro"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Acceso directo no disponible"</string>
<string name="home_screen" msgid="5629429142036709174">"Inicio"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Define <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> como aplicación de inicio predeterminada en Ajustes"</string>
<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>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index bab7da3..44448a6 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Turvarežiimis on vidinad keelatud"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Otsetee pole saadaval"</string>
<string name="home_screen" msgid="5629429142036709174">"Avakuva"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Määrake rakendus <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> seadetes avakuva vaikerakenduseks"</string>
<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>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index b8017d6..6fc4cf4 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgetak desgaitu egin dira modu seguruan"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Lasterbideak ez daude erabilgarri"</string>
<string name="home_screen" msgid="5629429142036709174">"Orri nagusia"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Ezarri <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> hasierako aplikazio lehenetsi gisa ezarpenetan"</string>
<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>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 27ce075..fffce13 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"ابزارکها در حالت ایمن غیرفعال هستند"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"میانبر دردسترس نیست"</string>
<string name="home_screen" msgid="5629429142036709174">"صفحه اصلی"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"تنظیم <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> بهعنوان برنامه صفحه اصلی پیشفرض در «تنظیمات»"</string>
<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>
@@ -38,7 +37,7 @@
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"برای استفاده از این جفت برنامه، دستگاه را باز کنید"</string>
<string name="app_pair_not_available" msgid="3556767440808032031">"جفت برنامه دردسترس نیست"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"برای جابهجا کردن ابزارک، لمس کنید و نگه دارید."</string>
- <string name="long_accessible_way_to_add" msgid="2733588281439571974">"برای جابهجا کردن ابزارک یا استفاده از کنشهای سفارشی، دوضربه بزنید و نگه دارید."</string>
+ <string name="long_accessible_way_to_add" msgid="2733588281439571974">"برای جابهجا کردن ابزارک یا استفاده از کنشهای سفارشی، دو تکضرب بزنید و نگه دارید."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d عرض در %2$d طول"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"ابزارک <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
@@ -76,7 +75,7 @@
<string name="all_apps_label" msgid="5015784846527570951">"همه برنامهها"</string>
<string name="notifications_header" msgid="1404149926117359025">"اعلانها"</string>
<string name="long_press_shortcut_to_add" msgid="5405328730817637737">"برای جابهجا کردن میانبر، لمس کنید و نگه دارید."</string>
- <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"برای جابهجا کردن میانبر یا استفاده از کنشهای سفارشی، دوضربه بزنید و نگه دارید."</string>
+ <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"برای جابهجا کردن میانبر یا استفاده از کنشهای سفارشی، دو تکضرب بزنید و نگه دارید."</string>
<string name="out_of_space" msgid="6455557115204099579">"فضای خالی در این صفحه اصلی وجود ندارد"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"فضای بیشتری در سینی موارد دلخواه وجود ندارد"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"فهرست برنامهها"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 181c89e..310053e 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgetit poistettu käytöstä vikasietotilassa"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Pikakuvake ei ole käytettävissä."</string>
<string name="home_screen" msgid="5629429142036709174">"Etusivu"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Valitse <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> oletusaloitusnäyttösovellukseksi asetuksissa"</string>
<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>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index f3b08cb..11b2c01 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -21,22 +21,21 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Lanceur3"</string>
<string name="work_folder_name" msgid="3753320833950115786">"Travail"</string>
- <string name="activity_not_found" msgid="8071924732094499514">"L\'application n\'est pas installée."</string>
- <string name="activity_not_available" msgid="7456344436509528827">"Application indisponible"</string>
- <string name="safemode_shortcut_error" msgid="9160126848219158407">"L\'application téléchargée est désactivée en mode sans échec."</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"L\'appli n\'est pas installée."</string>
+ <string name="activity_not_available" msgid="7456344436509528827">"Appli indisponible"</string>
+ <string name="safemode_shortcut_error" msgid="9160126848219158407">"L\'appli téléchargée est désactivée en mode sans échec."</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets désactivés en mode sans échec"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Le raccourci n\'est pas disponible"</string>
<string name="home_screen" msgid="5629429142036709174">"Accueil"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Définissez <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> comme appli d\'accueil par défaut dans les paramètres"</string>
<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>
<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\'applications n\'est pas prise en charge sur cet appareil"</string>
- <string name="app_pair_needs_unfold" msgid="4588897528143807002">"Déplier l\'appareil pour utiliser cette paire d\'applications"</string>
- <string name="app_pair_not_available" msgid="3556767440808032031">"La Paire d\'applications n\'est pas offerte"</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>
+ <string name="app_pair_needs_unfold" msgid="4588897528143807002">"Déplier l\'appareil pour utiliser cette paire d\'applis"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"La Paire d\'applis n\'est pas offerte"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Maintenez le doigt sur un widget pour le déplacer."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Touchez 2x un widget et maintenez le doigt dessus pour le déplacer ou utiliser des actions personnalisées."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
@@ -69,20 +68,20 @@
<string name="widget_add_button_content_description" msgid="1810530016360039643">"Ajoutez le widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Touchez pour modifier les paramètres du widget"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Modifier les paramètres du widget"</string>
- <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Rechercher dans les applications"</string>
- <string name="all_apps_loading_message" msgid="5813968043155271636">"Chargement des applications en cours…"</string>
- <string name="all_apps_no_search_results" msgid="3200346862396363786">"Aucune application trouvée correspondant à « <xliff:g id="QUERY">%1$s</xliff:g> »"</string>
- <string name="label_application" msgid="8531721983832654978">"Application"</string>
- <string name="all_apps_label" msgid="5015784846527570951">"Toutes les applications"</string>
+ <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Rechercher dans les applis"</string>
+ <string name="all_apps_loading_message" msgid="5813968043155271636">"Chargement des applis en cours…"</string>
+ <string name="all_apps_no_search_results" msgid="3200346862396363786">"Aucune appli trouvée correspondant à « <xliff:g id="QUERY">%1$s</xliff:g> »"</string>
+ <string name="label_application" msgid="8531721983832654978">"Appli"</string>
+ <string name="all_apps_label" msgid="5015784846527570951">"Toutes les applis"</string>
<string name="notifications_header" msgid="1404149926117359025">"Notifications"</string>
<string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Maintenez le doigt sur un raccourci pour le déplacer."</string>
<string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Touchez deux fois un raccourci et maintenez le doigt dessus pour le déplacer ou utiliser des actions personnalisées."</string>
<string name="out_of_space" msgid="6455557115204099579">"Pas d\'espace libre sur cet écran d\'accueil"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Il n\'y a plus d\'espace dans la zone des favoris"</string>
- <string name="all_apps_button_label" msgid="8130441508702294465">"Liste des applications"</string>
+ <string name="all_apps_button_label" msgid="8130441508702294465">"Liste des applis"</string>
<string name="all_apps_search_results" msgid="5889367432531296759">"Résultats de recherche"</string>
- <string name="all_apps_button_personal_label" msgid="1315764287305224468">"Liste des applications personnelles"</string>
- <string name="all_apps_button_work_label" msgid="7270707118948892488">"Liste des applications professionnelles"</string>
+ <string name="all_apps_button_personal_label" msgid="1315764287305224468">"Liste des applis personnelles"</string>
+ <string name="all_apps_button_work_label" msgid="7270707118948892488">"Liste des applis professionnelles"</string>
<string name="remove_drop_target_label" msgid="7812859488053230776">"Supprimer"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Désinstaller"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Détails de l\'appli"</string>
@@ -92,17 +91,17 @@
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Ne pas suggérer d\'appli"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Épingler la prédiction"</string>
<string name="permlab_install_shortcut" msgid="5632423390354674437">"installer des raccourcis"</string>
- <string name="permdesc_install_shortcut" msgid="923466509822011139">"Permet à une application d\'ajouter des raccourcis sans l\'intervention de l\'utilisateur."</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Permet à une appli d\'ajouter des raccourcis sans l\'intervention de l\'utilisateur."</string>
<string name="permlab_read_settings" msgid="5136500343007704955">"lire les paramètres et les raccourcis de la page d\'accueil"</string>
- <string name="permdesc_read_settings" msgid="4208061150510996676">"Permet à l\'application de lire les paramètres et les raccourcis de l\'écran d\'accueil."</string>
+ <string name="permdesc_read_settings" msgid="4208061150510996676">"Permet à l\'appli de lire les paramètres et les raccourcis de l\'écran d\'accueil."</string>
<string name="permlab_write_settings" msgid="4820028712156303762">"modifier les paramètres et les raccourcis de la page d\'accueil"</string>
- <string name="permdesc_write_settings" msgid="726859348127868466">"Permet à l\'application de modifier les paramètres et les raccourcis de l\'écran d\'accueil."</string>
+ <string name="permdesc_write_settings" msgid="726859348127868466">"Permet à l\'appli de modifier les paramètres et les raccourcis de l\'écran d\'accueil."</string>
<string name="gadget_error_text" msgid="740356548025791839">"Impossible de charger le widget"</string>
<string name="gadget_setup_text" msgid="8348374825537681407">"Paramètres du widget"</string>
<string name="gadget_complete_setup_text" msgid="309040266978007925">"Touchez pour terminer la configuration"</string>
- <string name="uninstall_system_app_text" msgid="4172046090762920660">"Impossible de désinstaller cette application, car il s\'agit d\'une application système."</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Impossible de désinstaller cette appli, car il s\'agit d\'une appli système."</string>
<string name="folder_hint_text" msgid="5174843001373488816">"Modifier le nom"</string>
- <string name="disabled_app_label" msgid="6673129024321402780">"L\'application <xliff:g id="APP_NAME">%1$s</xliff:g> est désactivée"</string>
+ <string name="disabled_app_label" msgid="6673129024321402780">"L\'appli <xliff:g id="APP_NAME">%1$s</xliff:g> est désactivée"</string>
<string name="dotted_app_label" msgid="1865617679843363410">"{count,plural, =1{{app_name} a # notification}one{{app_name} a # notification}other{{app_name} a # notifications}}"</string>
<string name="default_scroll_format" msgid="7475544710230993317">"Page %1$d sur %2$d"</string>
<string name="workspace_scroll_format" msgid="8458889198184077399">"Écran d\'accueil %1$d sur %2$d"</string>
@@ -114,7 +113,7 @@
<string name="folder_renamed" msgid="1794088362165669656">"Nouveau nom du dossier : <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Dossier : <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> élément(s)"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Dossier : <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> éléments ou plus"</string>
- <string name="app_pair_name_format" msgid="8134106404716224054">"Paire d\'applications : <xliff:g id="APP1">%1$s</xliff:g> et <xliff:g id="APP2">%2$s</xliff:g>"</string>
+ <string name="app_pair_name_format" msgid="8134106404716224054">"Paire d\'applis : <xliff:g id="APP1">%1$s</xliff:g> et <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Fond d\'écran et style"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Modifier l\'écran d\'accueil"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Paramètres d\'accueil"</string>
@@ -125,23 +124,23 @@
<string name="notification_dots_desc_on" msgid="1679848116452218908">"Activé"</string>
<string name="notification_dots_desc_off" msgid="1760796511504341095">"Désactivé"</string>
<string name="title_missing_notification_access" msgid="7503287056163941064">"L\'accès aux notifications est requis"</string>
- <string name="msg_missing_notification_access" msgid="281113995110910548">"Pour afficher les points de notification, activez les notifications d\'application pour <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="msg_missing_notification_access" msgid="281113995110910548">"Pour afficher les points de notification, activez les notifications d\'appli pour <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="title_change_settings" msgid="1376365968844349552">"Modifier les paramètres"</string>
<string name="notification_dots_service_title" msgid="4284221181793592871">"Afficher les points de notification"</string>
<string name="developer_options_title" msgid="700788437593726194">"Options pour les développeurs"</string>
- <string name="auto_add_shortcuts_label" msgid="4926805029653694105">"Ajouter les icônes des applications à l\'écran d\'accueil"</string>
- <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Pour les nouvelles applications"</string>
+ <string name="auto_add_shortcuts_label" msgid="4926805029653694105">"Ajouter les icônes des applis à l\'écran d\'accueil"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Pour les nouvelles applis"</string>
<string name="package_state_unknown" msgid="7592128424511031410">"Inconnu"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Supprimer"</string>
<string name="abandoned_search" msgid="891119232568284442">"Rechercher"</string>
- <string name="abandoned_promises_title" msgid="7096178467971716750">"Cette application n\'est pas installée"</string>
- <string name="abandoned_promise_explanation" msgid="3990027586878167529">"L\'application liée à cette icône n\'est pas installée. Vous pouvez la supprimer ou rechercher l\'application et l\'installer manuellement."</string>
- <string name="app_installing_title" msgid="5864044122733792085">"Installation de l\'application <xliff:g id="NAME">%1$s</xliff:g> en cours, <xliff:g id="PROGRESS">%2$s</xliff:g> terminée"</string>
+ <string name="abandoned_promises_title" msgid="7096178467971716750">"Cette appli n\'est pas installée"</string>
+ <string name="abandoned_promise_explanation" msgid="3990027586878167529">"L\'appli liée à cette icône n\'est pas installée. Vous pouvez la supprimer ou rechercher l\'appli et l\'installer manuellement."</string>
+ <string name="app_installing_title" msgid="5864044122733792085">"Installation de l\'appli <xliff:g id="NAME">%1$s</xliff:g> en cours, <xliff:g id="PROGRESS">%2$s</xliff:g> terminée"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Téléchargement de <xliff:g id="NAME">%1$s</xliff:g> : <xliff:g id="PROGRESS">%2$s</xliff:g>"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> en attente d\'installation"</string>
<string name="app_archived_title" msgid="7717956158562544081">"L\'appli <xliff:g id="NAME">%1$s</xliff:g> est archivée. Touchez le bouton pour télécharger et restaurer l\'appli."</string>
- <string name="dialog_update_title" msgid="114234265740994042">"Mise à jour de l\'application requise"</string>
- <string name="dialog_update_message" msgid="4176784553982226114">"L\'application pour cette icône n\'est pas à jour. Vous pouvez soit la mettre à jour manuellement pour réactiver ce raccourci, soit retirer l\'icône."</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Mise à jour de l\'appli requise"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"L\'appli pour cette icône n\'est pas à jour. Vous pouvez soit la mettre à jour manuellement pour réactiver ce raccourci, soit retirer l\'icône."</string>
<string name="dialog_update" msgid="2178028071796141234">"Mettre à jour"</string>
<string name="dialog_remove" msgid="6510806469849709407">"Retirer"</string>
<string name="widgets_list" msgid="796804551140113767">"Liste des widgets"</string>
@@ -174,15 +173,15 @@
<string name="all_apps_personal_tab" msgid="4190252696685155002">"Personnel"</string>
<string name="all_apps_work_tab" msgid="4884822796154055118">"Travail"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Profil professionnel"</string>
- <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Les applications professionnelles sont indiquées par un badge et elles sont visibles pour votre administrateur informatique"</string>
+ <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Les applis professionnelles sont indiquées par un badge et elles sont visibles pour votre administrateur informatique"</string>
<string name="work_profile_edu_accept" msgid="6069788082535149071">"OK"</string>
- <string name="work_apps_paused_title" msgid="3040901117349444598">"Les applications professionnelles sont interrompues"</string>
- <string name="work_apps_paused_info_body" msgid="1687828929959237477">"Vous ne recevrez pas de notifications de vos applications professionnelles"</string>
- <string name="work_apps_paused_body" msgid="261634750995824906">"Les applications professionnelles ne peuvent ni vous envoyer de notifications, ni utiliser la pile, ni accéder à votre position"</string>
- <string name="work_apps_paused_telephony_unavailable_body" msgid="8358872357502756790">"Vous ne recevrez pas d\'appels téléphoniques, de messages texte ni de notifications de vos applications professionnelles"</string>
- <string name="work_apps_paused_edu_banner" msgid="8872412121608402058">"Les applications professionnelles sont indiquées par un badge et sont visibles pour votre administrateur informatique"</string>
+ <string name="work_apps_paused_title" msgid="3040901117349444598">"Les applis professionnelles sont interrompues"</string>
+ <string name="work_apps_paused_info_body" msgid="1687828929959237477">"Vous ne recevrez pas de notifications de vos applis professionnelles"</string>
+ <string name="work_apps_paused_body" msgid="261634750995824906">"Les applis professionnelles ne peuvent ni vous envoyer de notifications, ni utiliser la pile, ni accéder à votre position"</string>
+ <string name="work_apps_paused_telephony_unavailable_body" msgid="8358872357502756790">"Vous ne recevrez pas d\'appels téléphoniques, de messages texte ni de notifications de vos applis professionnelles"</string>
+ <string name="work_apps_paused_edu_banner" msgid="8872412121608402058">"Les applis professionnelles sont indiquées par un badge et sont visibles pour votre administrateur informatique"</string>
<string name="work_apps_paused_edu_accept" msgid="6377476824357318532">"OK"</string>
- <string name="work_apps_pause_btn_text" msgid="4669288269140620646">"Mettre en pause les applications professionnelles"</string>
+ <string name="work_apps_pause_btn_text" msgid="4669288269140620646">"Mettre en pause les applis professionnelles"</string>
<string name="work_apps_enable_btn_text" msgid="1736198302467317371">"Réactiver"</string>
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filtrer"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Échec : <xliff:g id="WHAT">%1$s</xliff:g>"</string>
@@ -195,5 +194,5 @@
<string name="ps_container_lock_title" msgid="2640257399982364682">"Verrouiller"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Transition vers l\'Espace privé"</string>
<string name="ps_add_button_label" msgid="8127988716897128773">"Installer"</string>
- <string name="ps_add_button_content_description" msgid="3254274107740952556">"Installer des applications dans l\'Espace privé"</string>
+ <string name="ps_add_button_content_description" msgid="3254274107740952556">"Installer des applis dans l\'Espace privé"</string>
</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 4a7c7fb..b4c5ec6 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Les widgets sont désactivés en mode sécurisé."</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Raccourci non disponible"</string>
<string name="home_screen" msgid="5629429142036709174">"Accueil"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Définissez <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> comme application d\'accueil par défaut dans Paramètres"</string>
<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>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index 0430a9a..ac72802 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Os widgets están desactivados no modo seguro"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"O atallo non está dispoñible"</string>
<string name="home_screen" msgid="5629429142036709174">"Inicio"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Define <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> como aplicación de inicio predeterminada en Configuración"</string>
<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>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 3c248db..74747d0 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"સુરક્ષિત મોડમાં વિજેટ્સ અક્ષમ કર્યા"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"શૉર્ટકટ ઉપલબ્ધ નથી"</string>
<string name="home_screen" msgid="5629429142036709174">"હોમ સ્ક્રીન"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"સેટિંગમાં <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>ને ડિફૉલ્ટ હોમ ઍપ તરીકે સેટ કરો"</string>
<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>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 50d4762..6071935 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"विजेट सुरक्षित मोड में अक्षम हैं"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"शॉर्टकट उपलब्ध नहीं है"</string>
<string name="home_screen" msgid="5629429142036709174">"होम स्क्रीन"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"सेटिंग में जाकर, <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> को डिफ़ॉल्ट होम ऐप्लिकेशन के तौर पर सेट करें"</string>
<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>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 7a1fd94..cf7a91a 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgeti su onemogućeni u Sigurnom načinu rada"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Prečac nije dostupan"</string>
<string name="home_screen" msgid="5629429142036709174">"Početni zaslon"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Postavite aplikaciju <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> kao zadanu aplikaciju početnog zaslona u postavkama"</string>
<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>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 3b556da..f306110 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"A modulok ki vannak kapcsolva Csökkentett módban"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"A gyorsparancs nem áll rendelkezésre"</string>
<string name="home_screen" msgid="5629429142036709174">"Kezdőképernyő"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"A(z) <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> appot a Beállításokban adhatja meg alapértelmezett kezdőalkalmazásként."</string>
<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>
@@ -188,7 +187,7 @@
<string name="remote_action_failed" msgid="1383965239183576790">"Sikertelen: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"Privát terület"</string>
<string name="private_space_secondary_label" msgid="9203933341714508907">"Koppintson a beállításhoz vagy a megnyitáshoz"</string>
- <string name="ps_container_title" msgid="4391796149519594205">"Privát terület"</string>
+ <string name="ps_container_title" msgid="4391796149519594205">"Privát"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"Privát terület beállításai"</string>
<string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Privát, feloldott."</string>
<string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Privát, zárolt."</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 491ba05..2d345f2 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Վիջեթներն անջատված են անվտանգ ռեժիմում"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Դյուրանցումն անհասանելի է"</string>
<string name="home_screen" msgid="5629429142036709174">"Հիմնական էկրան"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Սահմանել <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> գործարկիչը որպես մեկնարկի կանխադրված հավելված Կարգավորումներում"</string>
<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>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 1335385..9ced9f4 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widget dinonaktifkan dalam mode Aman"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Pintasan tidak tersedia"</string>
<string name="home_screen" msgid="5629429142036709174">"Layar utama"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Jadikan <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> sebagai aplikasi layar utama default di Setelan"</string>
<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>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 4865264..2ab7817 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Græjur eru óvirkar í öruggri stillingu"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Flýtileið er ekki tiltæk"</string>
<string name="home_screen" msgid="5629429142036709174">"Heim"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Stilltu <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> sem sjálfgefið heimaforrit í stillingunum"</string>
<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>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 41b4b60..a59de6c 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widget disabilitati in modalità provvisoria"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"La scorciatoia non è disponibile"</string>
<string name="home_screen" msgid="5629429142036709174">"Home"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Imposta <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> come app iniziale predefinita nelle Impostazioni"</string>
<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>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index ae71edd..89a0c8a 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"ווידג\'טים מושבתים במצב בטוח"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"קיצור הדרך אינו זמין"</string>
<string name="home_screen" msgid="5629429142036709174">"בית"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"הגדרה של <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> כאפליקציית הבית ב\'הגדרות\'"</string>
<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>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index c64d335..b48c7be 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"セーフモードではウィジェットは無効です"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ショートカットは使用できません"</string>
<string name="home_screen" msgid="5629429142036709174">"ホーム"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"[設定] で <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> をデフォルトのホームアプリに設定します"</string>
<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>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index bcced9a..76b8b5d 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"უსაფრთხო რეჟიმში ვიჯეტი გამორთულია"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"მალსახმობი მიუწვდომელია"</string>
<string name="home_screen" msgid="5629429142036709174">"მთავარი გვერდი"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>-ის დაყენება ნაგულისხმევ Home აპად პარამეტრებში"</string>
<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>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 62bb983..95d4420 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Қауіпсіз режимде виджеттер өшіріледі"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Таңбаша қолжетімді емес"</string>
<string name="home_screen" msgid="5629429142036709174">"Негізгі экран"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Параметрлерден <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> қолданбасын әдепкі негізгі экран қолданбасы ретінде орнатыңыз."</string>
<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>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index b733699..5c71276 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"បានបិទធាតុក្រាហ្វិកក្នុងរបៀបសុវត្ថិភាព"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ផ្លូវកាត់មិនអាចប្រើបានទេ"</string>
<string name="home_screen" msgid="5629429142036709174">"អេក្រង់ដើម"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"កំណត់ <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> ជាកម្មវិធីអេក្រង់ដើមលំនាំដើមនៅក្នុង \"ការកំណត់\""</string>
<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>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 16b39f8..931ca30 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"ಸುರಕ್ಷಿತ ಮೋಡ್ನಲ್ಲಿ ವಿಜೆಟ್ಗಳನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ಶಾರ್ಟ್ಕಟ್ ಲಭ್ಯವಿಲ್ಲ"</string>
<string name="home_screen" msgid="5629429142036709174">"ಹೋಮ್"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"ಸೆಟ್ಟಿಂಗ್ಗಳಲ್ಲಿ <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> ಅನ್ನು ಡೀಫಾಲ್ಟ್ ಹೋಮ್ ಆ್ಯಪ್ ಆಗಿ ಸೆಟ್ ಮಾಡಿ"</string>
<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>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 1d5c58f..9f64cfb 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"안전 모드에서 위젯 사용 중지됨"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"바로가기를 사용할 수 없음"</string>
<string name="home_screen" msgid="5629429142036709174">"홈"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"설정에서 <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> 런처를 기본 홈 앱으로 설정하세요."</string>
<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>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 6f01db3..3e67c36 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Виджеттер Коопсуз режимде өчүрүлгөн"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Кыска жол жок"</string>
<string name="home_screen" msgid="5629429142036709174">"Башкы экран"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Параметрлерден <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> жүргүзгүчүн демейки башкы бет колдонмосу катары коюу"</string>
<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>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index 7ec103b..e0cd3ee 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"ວິດເຈັດຖືກປິດໃນ Safe mode"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ບໍ່ສາມາດໃຊ້ທາງລັດໄດ້"</string>
<string name="home_screen" msgid="5629429142036709174">"ໂຮມສະກຣີນ"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"ຕັ້ງ <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> ເປັນແອັບໂຮມເລີ່ມຕົ້ນໃນການຕັ້ງຄ່າ"</string>
<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>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 1112474..9846525 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Valdikliai išjungti Saugiame režime"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Sparčiojo klavišo negalima naudoti"</string>
<string name="home_screen" msgid="5629429142036709174">"Pagrindinis"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Nustatykite „<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>“ kaip numatytąją pagrindinę programą skiltyje „Nustatymai“"</string>
<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>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 4f1a92c..49f7ffe 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Logrīki atspējoti drošajā režīmā"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Saīsne nav pieejama."</string>
<string name="home_screen" msgid="5629429142036709174">"Sākums"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Iestatiet <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> kā noklusējuma sākuma lietotni iestatījumos."</string>
<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>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index d984ca0..2bfa089 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Додатоците се оневозможени во безбеден режим"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Кратенката не е достапна"</string>
<string name="home_screen" msgid="5629429142036709174">"Почетен екран"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Поставете <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> да биде стандардна апликација за почетен екран во „Поставки“"</string>
<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>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index ea8849e..a2babd5 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"സുരക്ഷിത മോഡിൽ വിജറ്റുകൾ പ്രവർത്തനരഹിതമാക്കി"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"കുറുക്കുവഴി ലഭ്യമല്ല"</string>
<string name="home_screen" msgid="5629429142036709174">"ഹോം"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"ക്രമീകരണത്തിൽ <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> എന്നത് ഡിഫോൾട്ട് ഹോം ആപ്പായി സജ്ജീകരിക്കുക"</string>
<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>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 8554717..93a1c9c 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Safe горимд виджетүүдийг идэвхгүйжүүлсэн"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Товчлол алга"</string>
<string name="home_screen" msgid="5629429142036709174">"Нүүр"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>-г Тохиргоонд өгөгдмөл үндсэн аппаар тохируулна уу"</string>
<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>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index dcdf25c..c084fab 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"विजेट सुरक्षित मोडमध्ये अक्षम झाले"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"शॉर्टकट उपलब्ध नाही"</string>
<string name="home_screen" msgid="5629429142036709174">"होम"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"सेटिंग्ज मध्ये <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> हे डीफॉल्ट होम अॅप म्हणून सेट करा"</string>
<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>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index 22ee2cb..72d92e2 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widget dilumpuhkan dalam mod Selamat"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Pintasan tidak tersedia"</string>
<string name="home_screen" msgid="5629429142036709174">"Rumah"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Tetapkan <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> sebagai apl skrin utama lalai dalam Tetapan"</string>
<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>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index bca1c6e..194ece7 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"လုံခြုံရေး မုဒ်ထဲမှာ ဝီဂျက်များကို ပိတ်ထား"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ဖြတ်လမ်း မရနိုင်ပါ"</string>
<string name="home_screen" msgid="5629429142036709174">"ပင်မစာမျက်နှာ"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"ဆက်တင်များတွင် <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> ကို မူရင်းပင်မစာမျက်နှာအက်ပ်အဖြစ် သတ်မှတ်ရန်"</string>
<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>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 1440443..a9e6c5d 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Moduler er deaktivert i sikker modus"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Snarveien er ikke tilgjengelig"</string>
<string name="home_screen" msgid="5629429142036709174">"Startskjerm"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Angi <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> som standard startsideapp i innstillingene"</string>
<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>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 52613cb..7880c36 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"सुरक्षित मोडमा विगेटहरू अक्षम गरियो"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"सर्टकट उपलब्ध छैन"</string>
<string name="home_screen" msgid="5629429142036709174">"होम"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"सेटिङमा गई <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> लाई डिफल्ट होम एपका रूपमा सेट गर्नुहोस्"</string>
<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>
diff --git a/res/values-night-v31/colors.xml b/res/values-night-v31/colors.xml
index d23f4d1..2688b83 100644
--- a/res/values-night-v31/colors.xml
+++ b/res/values-night-v31/colors.xml
@@ -26,6 +26,8 @@
<color name="home_settings_track_off_color">@android:color/system_neutral1_700</color>
<color name="widget_picker_title_color_dark">@android:color/system_neutral1_100</color>
+ <color name="widget_picker_description_color_dark">
+ @android:color/system_neutral2_200</color>
<color name="widget_picker_header_app_title_color_dark">
@android:color/system_neutral1_100</color>
<color name="widget_picker_header_app_subtitle_color_dark">
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 90dcd46..7b3f563 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets uitgezet in veilige modus"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Snelkoppeling is niet beschikbaar"</string>
<string name="home_screen" msgid="5629429142036709174">"Startscherm"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Stel <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> in als de standaard startscherm-app in Instellingen"</string>
<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>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 6d2f87e..33b7664 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"ନିରାପଦ ମୋଡରେ ୱିଜେଟ୍ ଅକ୍ଷମ କରାଗଲା"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ଶର୍ଟକଟ୍ ଉପଲବ୍ଧ ନାହିଁ"</string>
<string name="home_screen" msgid="5629429142036709174">"ହୋମ"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"ସେଟିଂସରେ <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>କୁ ଡିଫଲ୍ଟ Home ଆପ ଭାବରେ ସେଟ କରନ୍ତୁ"</string>
<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>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index b287b2f..71c25eb 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"ਵਿਜੇਟ ਸੁਰੱਖਿਅਤ ਮੋਡ ਵਿੱਚ ਅਸਮਰਥਿਤ"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ਸ਼ਾਰਟਕੱਟ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
<string name="home_screen" msgid="5629429142036709174">"ਮੁੱਖ ਪੰਨਾ"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਜਾ ਕੇ <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> ਨੂੰ ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਹੋਮ ਐਪ ਵਜੋਂ ਸੈੱਟ ਕਰੋ"</string>
<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>
@@ -186,14 +185,14 @@
<string name="work_apps_enable_btn_text" msgid="1736198302467317371">"ਰੋਕ ਹਟਾਓ"</string>
<string name="developer_options_filter_hint" msgid="5896817443635989056">"ਫਿਲਟਰ"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"ਇਹ ਕਾਰਵਾਈ ਅਸਫਲ ਹੋਈ: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
- <string name="private_space_label" msgid="2359721649407947001">"ਨਿੱਜੀ ਸਪੇਸ"</string>
+ <string name="private_space_label" msgid="2359721649407947001">"ਪ੍ਰਾਈਵੇਟ ਸਪੇਸ"</string>
<string name="private_space_secondary_label" msgid="9203933341714508907">"ਸੈੱਟਅੱਪ ਕਰਨ ਜਾਂ ਖੋਲ੍ਹਣ ਲਈ ਟੈਪ ਕਰੋ"</string>
<string name="ps_container_title" msgid="4391796149519594205">"ਨਿੱਜੀ"</string>
- <string name="ps_container_settings" msgid="6059734123353320479">"ਨਿੱਜੀ ਸਪੇਸ ਸੰਬੰਧੀ ਸੈਟਿੰਗਾਂ"</string>
+ <string name="ps_container_settings" msgid="6059734123353320479">"ਪ੍ਰਾਈਵੇਟ ਸਪੇਸ ਸੰਬੰਧੀ ਸੈਟਿੰਗਾਂ"</string>
<string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"ਨਿੱਜੀ, ਅਣਲਾਕ ਕੀਤਾ ਗਿਆ।"</string>
<string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"ਨਿੱਜੀ, ਲਾਕ ਕੀਤਾ ਗਿਆ।"</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"ਲਾਕ ਕਰੋ"</string>
- <string name="ps_container_transition" msgid="8667331812048014412">"ਨਿੱਜੀ ਸਪੇਸ ਨੂੰ ਤਬਦੀਲ ਕਰਨਾ"</string>
+ <string name="ps_container_transition" msgid="8667331812048014412">"ਪ੍ਰਾਈਵੇਟ ਸਪੇਸ ਨੂੰ ਤਬਦੀਲ ਕਰਨਾ"</string>
<string name="ps_add_button_label" msgid="8127988716897128773">"ਸਥਾਪਤ ਕਰੋ"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"ਪ੍ਰਾਈਵੇਟ ਸਪੇਸ ਵਿੱਚ ਐਪਾਂ ਸਥਾਪਤ ਕਰੋ"</string>
</resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 685b7d9..587c40f 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widżety są wyłączone w trybie bezpiecznym"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Skrót nie jest dostępny"</string>
<string name="home_screen" msgid="5629429142036709174">"Ekran główny"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Otwórz Ustawienia i ustaw aplikację <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> jako domyślną aplikację ekranu głównego"</string>
<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>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 14a3cf5..67d93de 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets desativados no Modo de segurança"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"O atalho não está disponível"</string>
<string name="home_screen" msgid="5629429142036709174">"Página inicial"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Defina a app <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> como a app inicial predefinida nas Definições"</string>
<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>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 4c4b13b..4302588 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets desativados no modo de segurança"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"O atalho não está disponível"</string>
<string name="home_screen" msgid="5629429142036709174">"Início"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Definir <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> como app de início padrão nas Configurações"</string>
<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>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 256fa60..d9e0413 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgeturile sunt dezactivate în modul de siguranță"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Comanda rapidă nu este disponibilă"</string>
<string name="home_screen" msgid="5629429142036709174">"Pagina de pornire"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Setează <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> drept aplicație ecran de pornire prestabilită, în Setări"</string>
<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>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 402143d..64a21f7 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Виджеты отключены в безопасном режиме"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Ярлык недоступен"</string>
<string name="home_screen" msgid="5629429142036709174">"Главный экран"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Установить \"<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>\" в качестве приложения главного экрана по умолчанию в настройках"</string>
<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>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 455f18b..71efc03 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"සුරක්ෂිත ආකාරය තුළ විජටය අබල කරන ලදි"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"කෙටි මග ලබා ගත නොහැකිය"</string>
<string name="home_screen" msgid="5629429142036709174">"මුල් පිටුව"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> සැකසීම් තුළ පෙරනිමි මුල් පිටුව යෙදුම ලෙස සකසන්න"</string>
<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>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 7771cf7..43bc29c 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Miniaplikácie sú v núdzovom režime zakázané"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Skratky nie sú k dispozícii"</string>
<string name="home_screen" msgid="5629429142036709174">"Domov"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Nastaviť aplikáciu <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> ako predvolenú vstupnú aplikáciu v Nastaveniach"</string>
<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>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 2e75b17..5d423ca 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Pripomočki so onemogočeni v varnem načinu"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Bližnjica ni na voljo"</string>
<string name="home_screen" msgid="5629429142036709174">"Začetni zaslon"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Nastavitev zaganjalnika <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> kot privzete aplikacije za začetni zaslon v nastavitvah"</string>
<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>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index a00f11a..8f4133d 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Miniaplikacionet janë të çaktivizuara në modalitetin e sigurt"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Shkurtorja nuk është e disponueshme"</string>
<string name="home_screen" msgid="5629429142036709174">"Ekrani bazë"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Cakto <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> si aplikacionin e parazgjedhur të ekranit bazë te \"Cilësimet\""</string>
<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>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 97d3ff4..cd6523c 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Виџети су онемогућени у Безбедном режиму"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Пречица није доступна"</string>
<string name="home_screen" msgid="5629429142036709174">"Почетни екран"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Подесите <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> као подразумевану почетну апликацију у Подешавањима"</string>
<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>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 8a8524e..4f95c67 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets är inaktiverade i felsäkert läge"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Genvägen är inte tillgänglig"</string>
<string name="home_screen" msgid="5629429142036709174">"Startskärm"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Ställ in <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> som standardstartskärmsapp i Inställningar"</string>
<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>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 64f6296..53a9abe 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Wijeti zimezimwa katika hali ya Usalama"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Hakuna njia ya mkato"</string>
<string name="home_screen" msgid="5629429142036709174">"Skrini ya kwanza"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Weka <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> iwe programu chaguomsingi ya mwanzo kwenye Mipangilio"</string>
<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>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 189f4ce..738f85c 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"பாதுகாப்புப் பயன்முறையில் விட்ஜெட்கள் முடக்கப்பட்டுள்ளன"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ஷார்ட்கட் இல்லை"</string>
<string name="home_screen" msgid="5629429142036709174">"முகப்பு"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"அமைப்புகளில் <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> என்பதை இயல்பு முகப்பு ஆப்ஸாக அமையுங்கள்"</string>
<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>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index d789baa..0520ebf 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"సురక్షిత మోడ్లో విడ్జెట్లు నిలిపివేయబడ్డాయి"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"షార్ట్కట్ అందుబాటులో లేదు"</string>
<string name="home_screen" msgid="5629429142036709174">"మొదటి ట్యాబ్"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"సెట్టింగ్లలో <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>ను ఆటోమేటిక్ సెట్టింగ్ మొదటి స్క్రీన్ యాప్గా సెట్ చేయండి"</string>
<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>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index f89638d..554fd94 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"มีการปิดใช้งานวิดเจ็ตในเซฟโหมด"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"ทางลัดไม่พร้อมใช้งาน"</string>
<string name="home_screen" msgid="5629429142036709174">"หน้าแรก"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"ตั้ง <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> เป็นแอปหน้าแรกเริ่มต้นในการตั้งค่า"</string>
<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>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 8754a12..cb6fe66 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Naka-disable ang mga widget sa Safe mode"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Hindi available ang shortcut"</string>
<string name="home_screen" msgid="5629429142036709174">"Home"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Itakda ang <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> bilang default na home app sa Mga Setting"</string>
<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>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 761ce56..b12ec27 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Güvenli modda widget\'lar devre dışı"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Kısayol kullanılamıyor"</string>
<string name="home_screen" msgid="5629429142036709174">"Ana ekran"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> başlatıcısını Ayarlar\'da varsayılan ana ekran uygulaması olarak ayarlayın"</string>
<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>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 2c85d5a..2a01dae 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"У безпечному режимі віджети вимкнено"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Ярлик недоступний"</string>
<string name="home_screen" msgid="5629429142036709174">"Головний екран"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Зробити <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> додатком головного екрана за умовчанням у налаштуваннях"</string>
<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>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 09f4304..544357e 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"ویجیٹس کو محفوظ وضع میں غیر فعال کر دیا گیا"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"شارٹ کٹ دستیاب نہیں ہے"</string>
<string name="home_screen" msgid="5629429142036709174">"ہوم"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"ترتیبات میں <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> کو بطور ڈیفالٹ ہوم ایپ سیٹ کریں"</string>
<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>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index d8dea69..5038d4f 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Xavfsiz rejimda vidjetlar o‘chirib qo‘yilgan"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Tezkor tugmadan foydalanib bo‘lmaydi"</string>
<string name="home_screen" msgid="5629429142036709174">"Bosh ekran"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Sozlamalar orqali <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> ilovasini birlamchi bosh ekran ilovasi sifatida belgilash"</string>
<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>
diff --git a/res/values-v31/colors.xml b/res/values-v31/colors.xml
index fa87221..7270366 100644
--- a/res/values-v31/colors.xml
+++ b/res/values-v31/colors.xml
@@ -75,6 +75,8 @@
<color name="widget_picker_title_color_light">
@android:color/system_neutral1_900</color>
+ <color name="widget_picker_description_color_light">
+ @android:color/system_neutral2_700</color>
<color name="widget_picker_header_app_title_color_light">
@android:color/system_neutral1_900</color>
<color name="widget_picker_header_app_subtitle_color_light">
diff --git a/res/values-v33/style.xml b/res/values-v33/style.xml
deleted file mode 100644
index 1261b23..0000000
--- a/res/values-v33/style.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-* Copyright (C) 2022 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.
-*/
--->
-
-<resources>
- <style name="HomeSettings.Theme" parent="@android:style/Theme.DeviceDefault.Settings">
- <item name="android:listPreferredItemPaddingEnd">16dp</item>
- <item name="android:listPreferredItemPaddingStart">24dp</item>
- <item name="android:navigationBarColor">@android:color/transparent</item>
- <item name="android:statusBarColor">@android:color/transparent</item>
- <item name="android:switchStyle">@style/SwitchStyle</item>
- <item name="android:textAppearanceListItem">@style/HomeSettings.PreferenceTitle</item>
- <item name="android:windowActionBar">false</item>
- <item name="android:windowNoTitle">true</item>
- <item name="preferenceTheme">@style/HomeSettings.PreferenceTheme</item>
- <item name="android:windowAnimationStyle">@style/Animation.SharedBackground</item>
- </style>
-
- <style name="Animation.SharedBackground" parent="@android:style/Animation.Activity">
- <item name="android:activityOpenEnterAnimation">@anim/shared_x_axis_activity_open_enter</item>
- <item name="android:activityOpenExitAnimation">@anim/shared_x_axis_activity_open_exit</item>
- <item name="android:activityCloseEnterAnimation">@anim/shared_x_axis_activity_close_enter</item>
- <item name="android:activityCloseExitAnimation">@anim/shared_x_axis_activity_close_exit</item>
- </style>
-</resources>
\ No newline at end of file
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 172c995..e5252b1 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Tiện ích bị vô hiệu hóa ở chế độ an toàn"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Lối tắt không khả dụng"</string>
<string name="home_screen" msgid="5629429142036709174">"Màn hình chính"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Đặt <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> làm trình chạy mặc định trong phần Cài đặt"</string>
<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>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 9c658dc..7a76158 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"安全模式下不允许使用微件"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"无法使用快捷方式"</string>
<string name="home_screen" msgid="5629429142036709174">"主屏幕"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"在“设置”中将“<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>”设为默认主屏幕应用"</string>
<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>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index af32638..ed6e52f 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"在安全模式中無法使用小工具"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"沒有可用的捷徑"</string>
<string name="home_screen" msgid="5629429142036709174">"主畫面"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"前往「設定」將「<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>」設為預設主畫面應用程式"</string>
<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>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 54a1c5d..264d607 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"在安全模式下無法使用小工具"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"目前無法使用捷徑"</string>
<string name="home_screen" msgid="5629429142036709174">"主畫面"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"前往「設定」將「<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>」設為預設主畫面應用程式"</string>
<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>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 9752e18..be6cd49 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -27,8 +27,7 @@
<string name="safemode_widget_error" msgid="4863470563535682004">"Amawijethi akhutshaziwe kwimodi yokuphepha"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Isinqamuleli asitholakali"</string>
<string name="home_screen" msgid="5629429142036709174">"Ikhaya"</string>
- <!-- no translation found for set_default_home_app (5808906607627586381) -->
- <skip />
+ <string name="set_default_home_app" msgid="5808906607627586381">"Setha i-<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> njenge-app yasekhaya ezenzakalelayo Kumasethingi"</string>
<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>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index be8b2e1..e4e047e 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -61,6 +61,7 @@
<attr name="preloadIconAccentColor" format="color" />
<attr name="preloadIconBackgroundColor" format="color" />
<attr name="widgetPickerTitleColor" format="color"/>
+ <attr name="widgetPickerDescriptionColor" format="color"/>
<attr name="widgetPickerPrimarySurfaceColor" format="color"/>
<attr name="widgetPickerSecondarySurfaceColor" format="color"/>
<attr name="widgetPickerHeaderAppTitleColor" format="color"/>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index ce80964..8fa1992 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -104,6 +104,7 @@
<color name="widget_picker_primary_surface_color_light">#EFEDED</color>
<color name="widget_picker_secondary_surface_color_light">#FAF9F8</color>
<color name="widget_picker_title_color_light">#1F1F1F</color>
+ <color name="widget_picker_description_color_light">#4C4D50</color>
<color name="widget_picker_header_app_title_color_light">#1F1F1F</color>
<color name="widget_picker_header_app_subtitle_color_light">#444746</color>
<color name="widget_picker_header_background_color_light">#C2E7FF</color>
@@ -123,6 +124,7 @@
<color name="widget_picker_primary_surface_color_dark">#1F2020</color>
<color name="widget_picker_secondary_surface_color_dark">#393939</color>
<color name="widget_picker_title_color_dark">#E3E3E3</color>
+ <color name="widget_picker_description_color_dark">#CCCDCF</color>
<color name="widget_picker_header_app_title_color_dark">#E3E3E3</color>
<color name="widget_picker_header_app_subtitle_color_dark">#C4C7C5</color>
<color name="widget_picker_header_background_color_dark">#004A77</color>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index ae3d3b3..f7273a0 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -243,6 +243,7 @@
<item name="widgetPickerSecondarySurfaceColor">
@color/widget_picker_secondary_surface_color_light</item>
<item name="widgetPickerTitleColor">@color/widget_picker_title_color_light</item>
+ <item name="widgetPickerDescriptionColor">@color/widget_picker_description_color_light</item>
<item name="widgetPickerHeaderAppTitleColor">
@color/widget_picker_header_app_title_color_light</item>
<item name="widgetPickerHeaderAppSubtitleColor">
@@ -282,6 +283,7 @@
@color/widget_picker_secondary_surface_color_dark</item>
<item name="widgetPickerTitleColor">
@color/widget_picker_title_color_dark</item>
+ <item name="widgetPickerDescriptionColor">@color/widget_picker_description_color_dark</item>
<item name="widgetPickerHeaderAppTitleColor">
@color/widget_picker_header_app_title_color_dark</item>
<item name="widgetPickerHeaderAppSubtitleColor">
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index b51e850..ef56246 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -37,6 +37,8 @@
import com.android.launcher3.celllayout.CellLayoutLayoutParams;
import com.android.launcher3.celllayout.CellPosMapper.CellPos;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.debug.TestEvent;
+import com.android.launcher3.debug.TestEventEmitter;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
import com.android.launcher3.logging.InstanceId;
@@ -221,6 +223,9 @@
dl.addView(frame);
frame.mIsOpen = true;
frame.post(() -> frame.snapToWidget(false));
+ TestEventEmitter.INSTANCE.get(widget.getContext()).sendEvent(
+ TestEvent.RESIZE_FRAME_SHOWING
+ );
}
private void setCornerRadiusFromWidget() {
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 83427a0..0cb2137 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -40,11 +40,15 @@
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.icu.text.MessageFormat;
+import android.text.Spannable;
+import android.text.SpannableString;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.TextUtils.TruncateAt;
+import android.text.style.ImageSpan;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.Property;
import android.util.Size;
import android.util.TypedValue;
@@ -54,6 +58,7 @@
import android.view.ViewDebug;
import android.widget.TextView;
+import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting;
@@ -96,6 +101,8 @@
public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
IconLabelDotView, DraggableView, Reorderable {
+ public static final String TAG = "BubbleTextView";
+
public static final int DISPLAY_WORKSPACE = 0;
public static final int DISPLAY_ALL_APPS = 1;
public static final int DISPLAY_FOLDER = 2;
@@ -494,7 +501,13 @@
mLastOriginalText = label;
mLastModifiedText = mLastOriginalText;
mBreakPointsIntArray = StringMatcherUtility.getListOfBreakpoints(label, MATCHER);
- setText(label);
+ if (Flags.enableNewArchivingIcon()
+ && info instanceof ItemInfoWithIcon infoWithIcon
+ && infoWithIcon.isInactiveArchive()) {
+ setTextWithStartIcon(label, R.drawable.cloud_download_24px);
+ } else {
+ setText(label);
+ }
}
if (info.contentDescription != null) {
setContentDescription(info.isDisabled()
@@ -804,7 +817,13 @@
getLineSpacingExtra());
if (!TextUtils.equals(modifiedString, mLastModifiedText)) {
mLastModifiedText = modifiedString;
- setText(modifiedString);
+ if (Flags.enableNewArchivingIcon()
+ && getTag() instanceof ItemInfoWithIcon infoWithIcon
+ && infoWithIcon.isInactiveArchive()) {
+ setTextWithStartIcon(modifiedString, R.drawable.cloud_download_24px);
+ } else {
+ setText(modifiedString);
+ }
// if text contains NEW_LINE, set max lines to 2
if (TextUtils.indexOf(modifiedString, NEW_LINE) != -1) {
setSingleLine(false);
@@ -825,6 +844,28 @@
super.setTextColor(getModifiedColor());
}
+ /**
+ * Uses a SpannableString to set text with a Drawable at the start of the TextView
+ * @param text text to use for TextView
+ * @param drawableRes Drawable Resource to use for drawing image at start of text
+ */
+ private void setTextWithStartIcon(CharSequence text, @DrawableRes int drawableRes) {
+ Drawable drawable = getContext().getDrawable(drawableRes);
+ if (drawable == null) {
+ setText(text);
+ Log.w(TAG, "setTextWithStartIcon: start icon Drawable not found from resources"
+ + ", will just set text instead. text=" + text);
+ return;
+ }
+ drawable.setTint(getCurrentTextColor());
+ drawable.setBounds(0, 0, Math.round(getTextSize()), Math.round(getTextSize()));
+ ImageSpan imageSpan = new ImageSpan(drawable, ImageSpan.ALIGN_CENTER);
+ // First space will be replaced with Drawable, second space is for space before text.
+ SpannableString spannable = new SpannableString(" " + text);
+ spannable.setSpan(imageSpan, 0, 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
+ setText(spannable);
+ }
+
@Override
public void setTextColor(ColorStateList colors) {
mTextColor = colors.getDefaultColor();
diff --git a/src/com/android/launcher3/FastScrollRecyclerView.java b/src/com/android/launcher3/FastScrollRecyclerView.java
index eff748a..960d77a 100644
--- a/src/com/android/launcher3/FastScrollRecyclerView.java
+++ b/src/com/android/launcher3/FastScrollRecyclerView.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -27,8 +28,8 @@
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
-import com.android.app.animation.Interpolators;
import com.android.launcher3.compat.AccessibilityManagerCompat;
+import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.views.RecyclerViewFastScroller;
@@ -188,6 +189,10 @@
* Scrolls this recycler view to the top.
*/
public void scrollToTop() {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PRIVATE_SPACE_SCROLL_FAILURE, "FastScrollRecyclerView#scrollToTop",
+ new Exception());
+ }
if (mScrollbar != null) {
mScrollbar.reattachThumbToScroll();
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 5c052b2..cb897dc 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -166,7 +166,6 @@
import androidx.core.os.BuildCompat;
import androidx.window.embedding.RuleController;
-import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.allapps.ActivityAllAppsContainerView;
@@ -181,6 +180,8 @@
import com.android.launcher3.celllayout.CellPosMapper.TwoPanelCellPosMapper;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.debug.TestEvent;
+import com.android.launcher3.debug.TestEventEmitter;
import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
@@ -595,6 +596,7 @@
RuleController.getInstance(this).setRules(
RuleController.parseRules(this, R.xml.split_configuration));
}
+ TestEventEmitter.INSTANCE.get(this).sendEvent(TestEvent.LAUNCHER_ON_CREATE);
}
protected ModelCallbacks createModelCallbacks() {
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 239967d..b41da0f 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -24,6 +24,7 @@
import static com.android.launcher3.LauncherPrefs.THEMED_ICONS;
import static com.android.launcher3.model.LoaderTask.SMARTSPACE_ON_HOME_SCREEN;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI;
import static com.android.launcher3.util.SettingsCache.PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI;
@@ -63,6 +64,9 @@
import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.widget.custom.CustomWidgetManager;
+import java.util.Locale;
+import java.util.Objects;
+
public class LauncherAppState implements SafeCloseable {
public static final String ACTION_FORCE_ROLOAD = "force-reload-launcher";
@@ -111,18 +115,32 @@
if (BuildCompat.isAtLeastV() && Flags.enableSupportForArchiving()) {
ArchiveCompatibilityParams params = new ArchiveCompatibilityParams();
params.setEnableUnarchivalConfirmation(false);
+ if (Flags.enableNewArchivingIcon()) {
+ params.setEnableIconOverlay(false);
+ }
launcherApps.setArchiveCompatibility(params);
}
SimpleBroadcastReceiver modelChangeReceiver =
- new SimpleBroadcastReceiver(mModel::onBroadcastIntent);
- modelChangeReceiver.registerAsync(mContext, Intent.ACTION_LOCALE_CHANGED,
+ new SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, mModel::onBroadcastIntent);
+ final Locale oldLocale = mContext.getResources().getConfiguration().locale;
+ modelChangeReceiver.register(
+ mContext,
+ () -> {
+ // if local has changed before receiver is registered on bg thread,
+ // mModel needs to reload.
+ Locale newLocale = mContext.getResources().getConfiguration().locale;
+ if (!Objects.equals(oldLocale, newLocale)) {
+ mModel.forceReload();
+ }
+ },
+ Intent.ACTION_LOCALE_CHANGED,
ACTION_DEVICE_POLICY_RESOURCE_UPDATED);
if (BuildConfig.IS_STUDIO_BUILD) {
mContext.registerReceiver(modelChangeReceiver, new IntentFilter(ACTION_FORCE_ROLOAD),
RECEIVER_EXPORTED);
}
- mOnTerminateCallback.add(() -> modelChangeReceiver.unregisterReceiverSafelyAsync(mContext));
+ mOnTerminateCallback.add(() -> modelChangeReceiver.unregisterReceiverSafely(mContext));
SafeCloseable userChangeListener = UserCache.INSTANCE.get(mContext)
.addUserEventListener(mModel::onUserEvent);
diff --git a/src/com/android/launcher3/ModelCallbacks.kt b/src/com/android/launcher3/ModelCallbacks.kt
index 13062b6..83c34ce 100644
--- a/src/com/android/launcher3/ModelCallbacks.kt
+++ b/src/com/android/launcher3/ModelCallbacks.kt
@@ -11,6 +11,8 @@
import com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID
import com.android.launcher3.allapps.AllAppsStore
import com.android.launcher3.config.FeatureFlags
+import com.android.launcher3.debug.TestEvent
+import com.android.launcher3.debug.TestEventEmitter
import com.android.launcher3.model.BgDataModel
import com.android.launcher3.model.StringCache
import com.android.launcher3.model.data.AppInfo
@@ -156,6 +158,7 @@
/*pause=*/ false,
deviceProfile.isTwoPanels
)
+ TestEventEmitter.INSTANCE.get(launcher).sendEvent(TestEvent.WORKSPACE_FINISH_LOADING)
}
/**
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index e601a3e..2995e8a 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -80,6 +80,8 @@
import com.android.launcher3.celllayout.CellPosMapper;
import com.android.launcher3.celllayout.CellPosMapper.CellPos;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.debug.TestEvent;
+import com.android.launcher3.debug.TestEventEmitter;
import com.android.launcher3.dot.FolderDotInfo;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
@@ -314,7 +316,6 @@
*/
public Workspace(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
-
mLauncher = Launcher.getLauncher(context);
mStateTransitionAnimation = new WorkspaceStateTransitionAnimation(mLauncher, this);
mWallpaperManager = WallpaperManager.getInstance(context);
@@ -2218,6 +2219,7 @@
if (d.stateAnnouncer != null && !droppedOnOriginalCell) {
d.stateAnnouncer.completeAction(R.string.item_moved);
}
+ TestEventEmitter.INSTANCE.get(getContext()).sendEvent(TestEvent.WORKSPACE_ON_DROP);
}
@Nullable
diff --git a/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java
index d0fc175..6f73e07 100644
--- a/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java
@@ -29,9 +29,9 @@
import androidx.customview.widget.ExploreByTouchHelper;
import com.android.launcher3.CellLayout;
-import com.android.launcher3.Launcher;
import com.android.launcher3.R;
-import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.BaseDragLayer;
import java.util.List;
@@ -47,16 +47,17 @@
protected final CellLayout mView;
protected final Context mContext;
+ protected final ActivityContext mActivityContext;
protected final LauncherAccessibilityDelegate mDelegate;
- protected final DragLayer mDragLayer;
+ protected final BaseDragLayer<?> mDragLayer;
public DragAndDropAccessibilityDelegate(CellLayout forView) {
super(forView);
mView = forView;
mContext = mView.getContext();
- Launcher launcher = Launcher.getLauncher(mContext);
- mDelegate = launcher.getAccessibilityDelegate();
- mDragLayer = launcher.getDragLayer();
+ mActivityContext = ActivityContext.lookupContext(mContext);
+ mDelegate = (LauncherAccessibilityDelegate) mActivityContext.getAccessibilityDelegate();
+ mDragLayer = mActivityContext.getDragLayer();
}
@Override
diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java
index 9623709..89e6adc 100644
--- a/src/com/android/launcher3/allapps/AllAppsStore.java
+++ b/src/com/android/launcher3/allapps/AllAppsStore.java
@@ -42,6 +42,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Consumer;
@@ -260,8 +261,12 @@
public void dump(String prefix, PrintWriter writer) {
writer.println(prefix + "\tAllAppsStore Apps[] size: " + mApps.length);
for (int i = 0; i < mApps.length; i++) {
- writer.println(String.format("%s\tPackage index and name: %d/%s", prefix, i,
- mApps[i].componentName.getPackageName()));
+ writer.println(String.format(Locale.getDefault(),
+ "%s\tPackage index, name, and class: " + "%d/%s:%s",
+ prefix,
+ i,
+ mApps[i].componentName.getPackageName(),
+ mApps[i].componentName.getClassName()));
}
}
}
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
index 98ca420..4b38df8 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
@@ -26,6 +26,7 @@
import static com.android.launcher3.allapps.UserProfileManager.STATE_ENABLED;
import android.content.Context;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
@@ -278,6 +279,13 @@
privateProfileManager.getReadyToAnimate())
&& privateProfileManager.getCurrentState() == STATE_ENABLED
? 0 : 1);
+ Log.d(TAG, "onBindViewHolder: "
+ + "isPrivateSpaceItem: " + isPrivateSpaceItem
+ + " isStateTransitioning: " + privateProfileManager.isStateTransitioning()
+ + " isScrolling: " + privateProfileManager.isScrolling()
+ + " readyToAnimate: " + privateProfileManager.getReadyToAnimate()
+ + " currentState: " + privateProfileManager.getCurrentState()
+ + " currentAlpha: " + icon.getAlpha());
}
// Views can still be bounded before the app list is updated hence showing icons
// after collapsing.
diff --git a/src/com/android/launcher3/anim/AnimatedFloat.java b/src/com/android/launcher3/anim/AnimatedFloat.java
index b414ab6..4441164 100644
--- a/src/com/android/launcher3/anim/AnimatedFloat.java
+++ b/src/com/android/launcher3/anim/AnimatedFloat.java
@@ -20,6 +20,8 @@
import android.animation.ObjectAnimator;
import android.util.FloatProperty;
+import java.util.function.Consumer;
+
/**
* A mutable float which allows animating the value
*/
@@ -38,9 +40,9 @@
}
};
- private static final Runnable NO_OP = () -> { };
+ private static final Consumer<Float> NO_OP = t -> { };
- private final Runnable mUpdateCallback;
+ private final Consumer<Float> mUpdateCallback;
private ObjectAnimator mValueAnimator;
// Only non-null when an animation is playing to this value.
private Float mEndValue;
@@ -52,6 +54,10 @@
}
public AnimatedFloat(Runnable updateCallback) {
+ this(v -> updateCallback.run());
+ }
+
+ public AnimatedFloat(Consumer<Float> updateCallback) {
mUpdateCallback = updateCallback;
}
@@ -60,6 +66,11 @@
value = initialValue;
}
+ public AnimatedFloat(Consumer<Float> updateCallback, float initialValue) {
+ this(updateCallback);
+ value = initialValue;
+ }
+
/**
* Returns an animation from the current value to the given value.
*/
@@ -99,7 +110,7 @@
public void updateValue(float v) {
if (Float.compare(v, value) != 0) {
value = v;
- mUpdateCallback.run();
+ mUpdateCallback.accept(value);
}
}
diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java
index e58890f..47a2bdd 100644
--- a/src/com/android/launcher3/anim/PendingAnimation.java
+++ b/src/com/android/launcher3/anim/PendingAnimation.java
@@ -59,6 +59,13 @@
add(anim, springProperty);
}
+ /**
+ * Utility method to sent an interpolator on an animation and add it to the list
+ */
+ public void add(Animator anim, TimeInterpolator interpolator) {
+ add(anim, interpolator, SpringProperty.DEFAULT);
+ }
+
@Override
public void add(Animator anim) {
add(anim, SpringProperty.DEFAULT);
diff --git a/src/com/android/launcher3/debug/TestEventsEmitterProduction.kt b/src/com/android/launcher3/debug/TestEventsEmitterProduction.kt
new file mode 100644
index 0000000..650df5a
--- /dev/null
+++ b/src/com/android/launcher3/debug/TestEventsEmitterProduction.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.debug
+
+import android.content.Context
+import com.android.launcher3.util.MainThreadInitializedObject
+import com.android.launcher3.util.SafeCloseable
+
+/** Events fired by the launcher. */
+enum class TestEvent(val event: String) {
+ LAUNCHER_ON_CREATE("LAUNCHER_ON_CREATE"),
+ WORKSPACE_ON_DROP("WORKSPACE_ON_DROP"),
+ RESIZE_FRAME_SHOWING("RESIZE_FRAME_SHOWING"),
+ WORKSPACE_FINISH_LOADING("WORKSPACE_FINISH_LOADING"),
+}
+
+/** Interface to create TestEventEmitters. */
+interface TestEventEmitter : SafeCloseable {
+
+ companion object {
+ @JvmField
+ val INSTANCE =
+ MainThreadInitializedObject<TestEventEmitter> { _: Context? ->
+ TestEventsEmitterProduction()
+ }
+ }
+
+ fun sendEvent(event: TestEvent)
+}
+
+/**
+ * TestEventsEmitterProduction shouldn't do anything since it runs on the launcher code and not on
+ * tests. This is just a placeholder and test should override this class.
+ */
+class TestEventsEmitterProduction : TestEventEmitter {
+
+ override fun close() {}
+
+ override fun sendEvent(event: TestEvent) {}
+}
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index bc5a164..c50c008 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -27,6 +27,7 @@
import android.view.View;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.app.animation.Interpolators;
import com.android.launcher3.DragSource;
@@ -69,8 +70,9 @@
*/
protected DragDriver mDragDriver = null;
+ @VisibleForTesting
/** Options controlling the drag behavior. */
- protected DragOptions mOptions;
+ public DragOptions mOptions;
/** Coordinate for motion down event */
protected final Point mMotionDown = new Point();
@@ -79,7 +81,8 @@
protected final Point mTmpPoint = new Point();
- protected DropTarget.DragObject mDragObject;
+ @VisibleForTesting
+ public DropTarget.DragObject mDragObject;
/** Who can receive drop events */
private final ArrayList<DropTarget> mDropTargets = new ArrayList<>();
diff --git a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
index fbe9e33..bebef70 100644
--- a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
+++ b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
@@ -26,7 +26,7 @@
public class SpringLoadedDragController implements OnAlarmListener {
// how long the user must hover over a mini-screen before it unshrinks
private static final long ENTER_SPRING_LOAD_HOVER_TIME = 500;
- private static final long ENTER_SPRING_LOAD_HOVER_TIME_IN_TEST = 2000;
+ private static final long ENTER_SPRING_LOAD_HOVER_TIME_IN_TEST = 3000;
private static final long ENTER_SPRING_LOAD_CANCEL_HOVER_TIME = 950;
Alarm mAlarm;
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index ae8f1d5..2408955 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -78,8 +78,6 @@
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
-import com.android.launcher3.model.WidgetItem;
-import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.model.data.AppPairInfo;
import com.android.launcher3.model.data.CollectionInfo;
import com.android.launcher3.model.data.FolderInfo;
@@ -106,6 +104,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
/**
* Utility class for generating the preview of Launcher for a given InvariantDeviceProfile.
@@ -376,15 +375,6 @@
getApplicationContext(), providerInfo));
}
- private void inflateAndAddWidgets(LauncherAppWidgetInfo info, WidgetsModel widgetsModel) {
- WidgetItem widgetItem = widgetsModel.getWidgetProviderInfoByProviderName(
- info.providerName, info.user, mContext);
- if (widgetItem == null) {
- return;
- }
- inflateAndAddWidgets(info, widgetItem.widgetInfo);
- }
-
private void inflateAndAddWidgets(
LauncherAppWidgetInfo info, LauncherAppWidgetProviderInfo providerInfo) {
AppWidgetHostView view = mAppWidgetHost.createView(
@@ -468,17 +458,22 @@
break;
}
}
+ Map<ComponentKey, AppWidgetProviderInfo> widgetsMap = widgetProviderInfoMap;
for (ItemInfo itemInfo : currentAppWidgets) {
switch (itemInfo.itemType) {
case Favorites.ITEM_TYPE_APPWIDGET:
case Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
- if (widgetProviderInfoMap != null) {
- inflateAndAddWidgets(
- (LauncherAppWidgetInfo) itemInfo, widgetProviderInfoMap);
- } else {
- inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo,
- dataModel.widgetsModel);
+ if (widgetsMap == null) {
+ widgetsMap = dataModel.widgetsModel.getWidgetsByComponentKey()
+ .entrySet()
+ .stream()
+ .filter(entry -> entry.getValue().widgetInfo != null)
+ .collect(Collectors.toMap(
+ Map.Entry::getKey,
+ entry -> entry.getValue().widgetInfo
+ ));
}
+ inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo, widgetsMap);
break;
default:
break;
@@ -508,6 +503,7 @@
&& !SHOULD_SHOW_FIRST_PAGE_WIDGET) {
CellLayout firstScreen = mWorkspaceScreens.get(FIRST_SCREEN_ID);
View qsb = mHomeElementInflater.inflate(R.layout.qsb_preview, firstScreen, false);
+ // TODO: set bgHandler on qsb when it is BaseTemplateCard, which requires API changes.
CellLayoutLayoutParams lp = new CellLayoutLayoutParams(
0, 0, firstScreen.getCountX(), 1);
lp.canReorder = false;
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 861631d..fbd24d8 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -795,6 +795,9 @@
@UiEvent(doc = "User launches Overview from meta+tab keyboard shortcut")
LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_SHORTCUT(1765),
+ @UiEvent(doc = "User long pressed on the taskbar IME switcher button")
+ LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_LONGPRESS(1798),
+
// ADD MORE
;
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 079987b..2febb22 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -109,7 +109,7 @@
final IconCache iconCache = app.getIconCache();
final String[] packages = mPackages;
- final int N = packages.length;
+ final int packageCount = packages.length;
final FlagOp flagOp;
final HashSet<String> packageSet = new HashSet<>(Arrays.asList(packages));
final Predicate<ItemInfo> matcher = mOp == OP_USER_AVAILABILITY_CHANGE
@@ -123,7 +123,7 @@
}
switch (mOp) {
case OP_ADD: {
- for (int i = 0; i < N; i++) {
+ for (int i = 0; i < packageCount; i++) {
iconCache.updateIconsForPkg(packages[i], mUser);
if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
if (DEBUG) {
@@ -146,7 +146,7 @@
+ " Look for earlier AllAppsList logs to find more information.");
removedComponents.add(a.componentName);
})) {
- for (int i = 0; i < N; i++) {
+ for (int i = 0; i < packageCount; i++) {
iconCache.updateIconsForPkg(packages[i], mUser);
activitiesLists.put(packages[i],
appsList.updatePackage(context, packages[i], mUser));
@@ -156,13 +156,13 @@
flagOp = FlagOp.NO_OP.removeFlag(WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE);
break;
case OP_REMOVE: {
- for (int i = 0; i < N; i++) {
+ for (int i = 0; i < packageCount; i++) {
iconCache.removeIconsForPkg(packages[i], mUser);
}
// Fall through
}
case OP_UNAVAILABLE:
- for (int i = 0; i < N; i++) {
+ for (int i = 0; i < packageCount; i++) {
if (DEBUG) {
Log.d(TAG, getOpString() + ": removing package=" + packages[i]);
}
@@ -217,44 +217,44 @@
// For system apps, package manager send OP_UPDATE when an app is enabled.
final boolean isNewApkAvailable = mOp == OP_ADD || mOp == OP_UPDATE;
synchronized (dataModel) {
- dataModel.forAllWorkspaceItemInfos(mUser, si -> {
+ dataModel.forAllWorkspaceItemInfos(mUser, itemInfo -> {
boolean infoUpdated = false;
boolean shortcutUpdated = false;
- ComponentName cn = si.getTargetComponent();
- if (cn != null && matcher.test(si)) {
+ ComponentName cn = itemInfo.getTargetComponent();
+ if (cn != null && matcher.test(itemInfo)) {
String packageName = cn.getPackageName();
- if (si.hasStatusFlag(WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI)) {
- forceKeepShortcuts.add(si.id);
+ if (itemInfo.hasStatusFlag(WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI)) {
+ forceKeepShortcuts.add(itemInfo.id);
if (mOp == OP_REMOVE) {
return;
}
}
- if (si.isPromise() && isNewApkAvailable) {
+ if (itemInfo.isPromise() && isNewApkAvailable) {
boolean isTargetValid = !cn.getClassName().equals(
IconCache.EMPTY_CLASS_NAME);
- if (si.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+ if (itemInfo.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
List<ShortcutInfo> shortcut =
new ShortcutRequest(context, mUser)
.forPackage(cn.getPackageName(),
- si.getDeepShortcutId())
+ itemInfo.getDeepShortcutId())
.query(ShortcutRequest.PINNED);
if (shortcut.isEmpty()) {
isTargetValid = false;
if (DEBUG) {
Log.d(TAG, "Pinned Shortcut not found for updated"
- + " package=" + si.getTargetPackage());
+ + " package=" + itemInfo.getTargetPackage());
}
} else {
if (DEBUG) {
Log.d(TAG, "Found pinned shortcut for updated"
- + " package=" + si.getTargetPackage()
+ + " package=" + itemInfo.getTargetPackage()
+ ", isTargetValid=" + isTargetValid);
}
- si.updateFromDeepShortcutInfo(shortcut.get(0), context);
+ itemInfo.updateFromDeepShortcutInfo(shortcut.get(0), context);
infoUpdated = true;
}
} else if (isTargetValid) {
@@ -262,39 +262,39 @@
.isActivityEnabled(cn, mUser);
}
- if (!isTargetValid && (si.hasStatusFlag(
+ if (!isTargetValid && (itemInfo.hasStatusFlag(
FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON)
- || si.isArchived())) {
- if (updateWorkspaceItemIntent(context, si, packageName)) {
+ || itemInfo.isArchived())) {
+ if (updateWorkspaceItemIntent(context, itemInfo, packageName)) {
infoUpdated = true;
- } else if (si.hasPromiseIconUi()) {
- removedShortcuts.add(si.id);
+ } else if (itemInfo.hasPromiseIconUi()) {
+ removedShortcuts.add(itemInfo.id);
if (DEBUG) {
FileLog.w(TAG, "Removing restored shortcut promise icon"
+ " that no longer points to valid component."
- + " id=" + si.id
- + ", package=" + si.getTargetPackage()
- + ", status=" + si.status
- + ", isArchived=" + si.isArchived());
+ + " id=" + itemInfo.id
+ + ", package=" + itemInfo.getTargetPackage()
+ + ", status=" + itemInfo.status
+ + ", isArchived=" + itemInfo.isArchived());
}
return;
}
} else if (!isTargetValid) {
- removedShortcuts.add(si.id);
+ removedShortcuts.add(itemInfo.id);
if (DEBUG) {
FileLog.w(TAG, "Removing shortcut that no longer points to"
+ " valid component."
- + " id=" + si.id
- + " package=" + si.getTargetPackage()
- + " status=" + si.status);
+ + " id=" + itemInfo.id
+ + " package=" + itemInfo.getTargetPackage()
+ + " status=" + itemInfo.status);
}
return;
} else {
- si.status = WorkspaceItemInfo.DEFAULT;
+ itemInfo.status = WorkspaceItemInfo.DEFAULT;
infoUpdated = true;
}
} else if (isNewApkAvailable && removedComponents.contains(cn)) {
- if (updateWorkspaceItemIntent(context, si, packageName)) {
+ if (updateWorkspaceItemIntent(context, itemInfo, packageName)) {
infoUpdated = true;
}
}
@@ -304,7 +304,7 @@
packageName);
// TODO: See if we can migrate this to
// AppInfo#updateRuntimeFlagsForActivityTarget
- si.setProgressLevel(
+ itemInfo.setProgressLevel(
activities == null || activities.isEmpty()
? 100
: PackageManagerHelper.getLoadingProgress(
@@ -313,42 +313,42 @@
// In case an app is archived, we need to make sure that archived state
// in WorkspaceItemInfo is refreshed.
if (Flags.enableSupportForArchiving() && !activities.isEmpty()) {
- boolean newArchivalState = activities.get(
- 0).getActivityInfo().isArchived;
- if (newArchivalState != si.isArchived()) {
- si.runtimeStatusFlags ^= FLAG_ARCHIVED;
+ boolean newArchivalState = activities.get(0)
+ .getActivityInfo().isArchived;
+ if (newArchivalState != itemInfo.isArchived()) {
+ itemInfo.runtimeStatusFlags ^= FLAG_ARCHIVED;
infoUpdated = true;
}
}
- if (si.itemType == Favorites.ITEM_TYPE_APPLICATION) {
+ if (itemInfo.itemType == Favorites.ITEM_TYPE_APPLICATION) {
if (activities != null && !activities.isEmpty()) {
- si.setNonResizeable(ApiWrapper.INSTANCE.get(context)
+ itemInfo.setNonResizeable(ApiWrapper.INSTANCE.get(context)
.isNonResizeableActivity(activities.get(0)));
}
- iconCache.getTitleAndIcon(si, si.usingLowResIcon());
+ iconCache.getTitleAndIcon(itemInfo, itemInfo.usingLowResIcon());
infoUpdated = true;
}
}
- int oldRuntimeFlags = si.runtimeStatusFlags;
- si.runtimeStatusFlags = flagOp.apply(si.runtimeStatusFlags);
- if (si.runtimeStatusFlags != oldRuntimeFlags) {
+ int oldRuntimeFlags = itemInfo.runtimeStatusFlags;
+ itemInfo.runtimeStatusFlags = flagOp.apply(itemInfo.runtimeStatusFlags);
+ if (itemInfo.runtimeStatusFlags != oldRuntimeFlags) {
shortcutUpdated = true;
}
}
if (infoUpdated || shortcutUpdated) {
- updatedWorkspaceItems.add(si);
+ updatedWorkspaceItems.add(itemInfo);
}
- if (infoUpdated && si.id != ItemInfo.NO_ID) {
- taskController.getModelWriter().updateItemInDatabase(si);
+ if (infoUpdated && itemInfo.id != ItemInfo.NO_ID) {
+ taskController.getModelWriter().updateItemInDatabase(itemInfo);
}
});
for (LauncherAppWidgetInfo widgetInfo : dataModel.appWidgets) {
if (mUser.equals(widgetInfo.user)
&& widgetInfo.hasRestoreFlag(
- LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
+ LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
&& packageSet.contains(widgetInfo.providerName.getPackageName())) {
widgetInfo.restoreStatus &=
~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY
@@ -391,7 +391,7 @@
} else if (mOp == OP_UPDATE) {
// Mark disabled packages in the broadcast to be removed
final LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
- for (int i=0; i<N; i++) {
+ for (int i = 0; i < packageCount; i++) {
if (!launcherApps.isPackageEnabled(packages[i], mUser)) {
if (DEBUG) {
Log.d(TAG, "OP_UPDATE:"
@@ -423,7 +423,7 @@
if (mOp == OP_ADD) {
// Load widgets for the new package. Changes due to app updates are handled through
// AppWidgetHost events, this is just to initialize the long-press options.
- for (int i = 0; i < N; i++) {
+ for (int i = 0; i < packageCount; i++) {
dataModel.widgetsModel.update(app, new PackageUserKey(packages[i], mUser));
}
taskController.bindUpdatedWidgets(dataModel);
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index 454ae96..58ebf0f 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -54,7 +54,9 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.function.Function;
import java.util.function.Predicate;
+import java.util.stream.Collectors;
/**
* Widgets data model that is used by the adapters of the widget views and controllers.
@@ -67,7 +69,26 @@
private static final boolean DEBUG = false;
/* Map of widgets and shortcuts that are tracked per package. */
- private final Map<PackageItemInfo, List<WidgetItem>> mWidgetsList = new HashMap<>();
+ private final Map<PackageItemInfo, List<WidgetItem>> mWidgetsByPackageItem = new HashMap<>();
+
+ /**
+ * Returns all widgets keyed by their component key.
+ */
+ public synchronized Map<ComponentKey, WidgetItem> getWidgetsByComponentKey() {
+ return mWidgetsByPackageItem.values().stream()
+ .flatMap(Collection::stream).distinct()
+ .collect(Collectors.toMap(
+ widget -> new ComponentKey(widget.componentName, widget.user),
+ Function.identity()
+ ));
+ }
+
+ /**
+ * Returns widgets grouped by the package item that they should belong to.
+ */
+ public synchronized Map<PackageItemInfo, List<WidgetItem>> getWidgetsByPackageItem() {
+ return mWidgetsByPackageItem;
+ }
/**
* Returns a list of {@link WidgetsListBaseEntry} filtered using given widget item filter. All
@@ -85,7 +106,8 @@
ArrayList<WidgetsListBaseEntry> result = new ArrayList<>();
AlphabeticIndexCompat indexer = new AlphabeticIndexCompat(context);
- for (Map.Entry<PackageItemInfo, List<WidgetItem>> entry : mWidgetsList.entrySet()) {
+ for (Map.Entry<PackageItemInfo, List<WidgetItem>> entry :
+ mWidgetsByPackageItem.entrySet()) {
PackageItemInfo pkgItem = entry.getKey();
List<WidgetItem> widgetItems = entry.getValue()
.stream()
@@ -112,41 +134,6 @@
return getFilteredWidgetsListForPicker(context, /*widgetItemFilter=*/ item -> true);
}
- /** Returns a mapping of packages to their widgets without static shortcuts. */
- public synchronized Map<PackageUserKey, List<WidgetItem>> getAllWidgetsWithoutShortcuts() {
- if (!WIDGETS_ENABLED) {
- return Collections.emptyMap();
- }
- Map<PackageUserKey, List<WidgetItem>> packagesToWidgets = new HashMap<>();
- mWidgetsList.forEach((packageItemInfo, widgetsAndShortcuts) -> {
- List<WidgetItem> widgets = widgetsAndShortcuts.stream()
- .filter(item -> item.widgetInfo != null)
- .collect(toList());
- if (widgets.size() > 0) {
- packagesToWidgets.put(
- new PackageUserKey(packageItemInfo.packageName, packageItemInfo.user),
- widgets);
- }
- });
- return packagesToWidgets;
- }
-
- /**
- * Returns a map of widget component keys to corresponding widget items. Excludes the
- * shortcuts.
- */
- public synchronized Map<ComponentKey, WidgetItem> getAllWidgetComponentsWithoutShortcuts() {
- if (!WIDGETS_ENABLED) {
- return Collections.emptyMap();
- }
- Map<ComponentKey, WidgetItem> widgetsMap = new HashMap<>();
- mWidgetsList.forEach((packageItemInfo, widgetsAndShortcuts) ->
- widgetsAndShortcuts.stream().filter(item -> item.widgetInfo != null).forEach(
- item -> widgetsMap.put(new ComponentKey(item.componentName, item.user),
- item)));
- return widgetsMap;
- }
-
/**
* @param packageUser If null, all widgets and shortcuts are updated and returned, otherwise
* only widgets and shortcuts associated with the package/user are.
@@ -210,14 +197,14 @@
if (packageUser == null) {
// Clear the list if this is an update on all widgets and shortcuts.
- mWidgetsList.clear();
+ mWidgetsByPackageItem.clear();
} else {
// Otherwise, only clear the widgets and shortcuts for the changed package.
- mWidgetsList.remove(packageItemInfoCache.getOrCreate(packageUser));
+ mWidgetsByPackageItem.remove(packageItemInfoCache.getOrCreate(packageUser));
}
// add and update.
- mWidgetsList.putAll(rawWidgetsShortcuts.stream()
+ mWidgetsByPackageItem.putAll(rawWidgetsShortcuts.stream()
.filter(new WidgetValidityCheck(app))
.filter(new WidgetFlagCheck())
.flatMap(widgetItem -> getPackageUserKeys(app.getContext(), widgetItem).stream()
@@ -237,7 +224,7 @@
return;
}
WidgetManagerHelper widgetManager = new WidgetManagerHelper(app.getContext());
- for (Entry<PackageItemInfo, List<WidgetItem>> entry : mWidgetsList.entrySet()) {
+ for (Entry<PackageItemInfo, List<WidgetItem>> entry : mWidgetsByPackageItem.entrySet()) {
if (packageNames.contains(entry.getKey().packageName)) {
List<WidgetItem> items = entry.getValue();
int count = items.size();
@@ -258,50 +245,6 @@
}
}
- private PackageItemInfo createPackageItemInfo(
- ComponentName providerName,
- UserHandle user,
- int category
- ) {
- if (category == NO_CATEGORY) {
- return new PackageItemInfo(providerName.getPackageName(), user);
- } else {
- return new PackageItemInfo("" , category, user);
- }
- }
-
- private IntSet getCategories(ComponentName providerName, Context context) {
- IntSet categories = WidgetSections.getWidgetsToCategory(context).get(providerName);
- if (categories != null) {
- return categories;
- }
- categories = new IntSet();
- categories.add(NO_CATEGORY);
- return categories;
- }
-
- public WidgetItem getWidgetProviderInfoByProviderName(
- ComponentName providerName, UserHandle user, Context context) {
- if (!WIDGETS_ENABLED) {
- return null;
- }
- IntSet categories = getCategories(providerName, context);
-
- // Checking if we have a provider in any of the categories.
- for (Integer category: categories) {
- PackageItemInfo key = createPackageItemInfo(providerName, user, category);
- List<WidgetItem> widgets = mWidgetsList.get(key);
- if (widgets != null) {
- return widgets.stream().filter(
- item -> item.componentName.equals(providerName)
- )
- .findFirst()
- .orElse(null);
- }
- }
- return null;
- }
-
/** Returns {@link PackageItemInfo} of a pending widget. */
public static PackageItemInfo newPendingItemInfo(Context context, ComponentName provider,
UserHandle user) {
diff --git a/src/com/android/launcher3/model/data/TaskItemInfo.kt b/src/com/android/launcher3/model/data/TaskItemInfo.kt
new file mode 100644
index 0000000..fc1cd4d
--- /dev/null
+++ b/src/com/android/launcher3/model/data/TaskItemInfo.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.data
+
+/**
+ * Temporary class holding a Task ID to allow us to reference a Task when clicking a hotseat item.
+ *
+ * TODO(b/315344726): Remove this class when we have proper Taskbar support for multi-instance apps
+ */
+class TaskItemInfo(val taskId: Int, itemInfo: WorkspaceItemInfo) : WorkspaceItemInfo(itemInfo)
diff --git a/src/com/android/launcher3/pm/UserCache.java b/src/com/android/launcher3/pm/UserCache.java
index cf03462..7339111 100644
--- a/src/com/android/launcher3/pm/UserCache.java
+++ b/src/com/android/launcher3/pm/UserCache.java
@@ -75,7 +75,7 @@
private final List<BiConsumer<UserHandle, String>> mUserEventListeners = new ArrayList<>();
private final SimpleBroadcastReceiver mUserChangeReceiver =
- new SimpleBroadcastReceiver(this::onUsersChanged);
+ new SimpleBroadcastReceiver(MODEL_EXECUTOR, this::onUsersChanged);
private final Context mContext;
@@ -93,12 +93,12 @@
@Override
public void close() {
- MODEL_EXECUTOR.execute(() -> mUserChangeReceiver.unregisterReceiverSafelySync(mContext));
+ MODEL_EXECUTOR.execute(() -> mUserChangeReceiver.unregisterReceiverSafely(mContext));
}
@WorkerThread
private void initAsync() {
- mUserChangeReceiver.registerSync(mContext,
+ mUserChangeReceiver.register(mContext,
Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
Intent.ACTION_MANAGED_PROFILE_REMOVED,
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 3dcc663..b1e82bb 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -49,6 +49,7 @@
import android.view.Display;
import androidx.annotation.AnyThread;
+import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting;
@@ -76,6 +77,7 @@
private static final String TAG = "DisplayController";
private static final boolean DEBUG = false;
+ private static boolean sTaskbarModePreferenceStatusForTests = false;
private static boolean sTransientTaskbarStatusForTests = true;
// TODO(b/254119092) remove all logs with this tag
@@ -109,7 +111,10 @@
private DisplayInfoChangeListener mPriorityListener;
private final ArrayList<DisplayInfoChangeListener> mListeners = new ArrayList<>();
- private final SimpleBroadcastReceiver mReceiver = new SimpleBroadcastReceiver(this::onIntent);
+ // We will register broadcast receiver on main thread to ensure not missing changes on
+ // TARGET_OVERLAY_PACKAGE and ACTION_OVERLAY_CHANGED.
+ private final SimpleBroadcastReceiver mReceiver =
+ new SimpleBroadcastReceiver(MAIN_EXECUTOR, this::onIntent);
private Info mInfo;
private boolean mDestroyed = false;
@@ -132,11 +137,11 @@
mWindowContext.registerComponentCallbacks(this);
} else {
mWindowContext = null;
- mReceiver.registerAsync(mContext, ACTION_CONFIGURATION_CHANGED);
+ mReceiver.register(mContext, ACTION_CONFIGURATION_CHANGED);
}
// Initialize navigation mode change listener
- mReceiver.registerPkgActionsAsync(mContext, TARGET_OVERLAY_PACKAGE, ACTION_OVERLAY_CHANGED);
+ mReceiver.registerPkgActions(mContext, TARGET_OVERLAY_PACKAGE, ACTION_OVERLAY_CHANGED);
WindowManagerProxy wmProxy = WindowManagerProxy.INSTANCE.get(context);
Context displayInfoContext = getDisplayInfoContext(display);
@@ -203,6 +208,14 @@
}
/**
+ * Enables respecting taskbar mode preference during test.
+ */
+ @VisibleForTesting
+ public static void enableTaskbarModePreferenceForTests(boolean enable) {
+ sTaskbarModePreferenceStatusForTests = enable;
+ }
+
+ /**
* Returns whether the taskbar is pinned in gesture navigation mode.
*/
public static boolean isPinnedTaskbar(Context context) {
@@ -223,7 +236,7 @@
} else {
// TODO: unregister broadcast receiver
}
- mReceiver.unregisterReceiverSafelyAsync(mContext);
+ mReceiver.unregisterReceiverSafely(mContext);
}
/**
@@ -461,7 +474,7 @@
if (navigationMode != NavigationMode.NO_BUTTON) {
return false;
}
- if (Utilities.isRunningInTestHarness()) {
+ if (Utilities.isRunningInTestHarness() && !sTaskbarModePreferenceStatusForTests) {
// TODO(b/258604917): Once ENABLE_TASKBAR_PINNING is enabled, remove usage of
// sTransientTaskbarStatusForTests and update test to directly
// toggle shared preference to switch transient taskbar on/off.
@@ -513,9 +526,8 @@
return Collections.unmodifiableSet(mPerDisplayBounds.keySet());
}
- /**
- * Returns all {@link WindowBounds}s for the current display.
- */
+ /** Returns all {@link WindowBounds}s for the current display. */
+ @Nullable
public List<WindowBounds> getCurrentBounds() {
return mPerDisplayBounds.get(normalizedDisplayInfo);
}
diff --git a/src/com/android/launcher3/util/LockedUserState.kt b/src/com/android/launcher3/util/LockedUserState.kt
index 2737249..10559f3 100644
--- a/src/com/android/launcher3/util/LockedUserState.kt
+++ b/src/com/android/launcher3/util/LockedUserState.kt
@@ -20,21 +20,28 @@
import android.os.Process
import android.os.UserManager
import androidx.annotation.VisibleForTesting
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
class LockedUserState(private val mContext: Context) : SafeCloseable {
val isUserUnlockedAtLauncherStartup: Boolean
- var isUserUnlocked: Boolean
- private set
+ var isUserUnlocked = false
+ private set(value) {
+ field = value
+ if (value) {
+ notifyUserUnlocked()
+ }
+ }
private val mUserUnlockedActions: RunnableList = RunnableList()
@VisibleForTesting
- val mUserUnlockedReceiver = SimpleBroadcastReceiver {
- if (Intent.ACTION_USER_UNLOCKED == it.action) {
- isUserUnlocked = true
- notifyUserUnlocked()
+ val mUserUnlockedReceiver =
+ SimpleBroadcastReceiver(UI_HELPER_EXECUTOR) {
+ if (Intent.ACTION_USER_UNLOCKED == it.action) {
+ isUserUnlocked = true
+ }
}
- }
init {
// 1) when user reboots devices, launcher process starts at lock screen and both
@@ -43,26 +50,34 @@
// yet isUserUnlockedAtLauncherStartup will remains as false.
// 2) when launcher process restarts after user has unlocked screen, both variable are
// init as true and will not change.
- isUserUnlocked =
- mContext
- .getSystemService(UserManager::class.java)!!
- .isUserUnlocked(Process.myUserHandle())
+ isUserUnlocked = checkIsUserUnlocked()
isUserUnlockedAtLauncherStartup = isUserUnlocked
- if (isUserUnlocked) {
- notifyUserUnlocked()
- } else {
- mUserUnlockedReceiver.registerAsync(mContext, Intent.ACTION_USER_UNLOCKED)
+ if (!isUserUnlocked) {
+ mUserUnlockedReceiver.register(
+ mContext,
+ {
+ // If user is unlocked while registering broadcast receiver, we should update
+ // [isUserUnlocked], which will call [notifyUserUnlocked] in setter
+ if (checkIsUserUnlocked()) {
+ MAIN_EXECUTOR.execute { isUserUnlocked = true }
+ }
+ },
+ Intent.ACTION_USER_UNLOCKED
+ )
}
}
+ private fun checkIsUserUnlocked() =
+ mContext.getSystemService(UserManager::class.java)!!.isUserUnlocked(Process.myUserHandle())
+
private fun notifyUserUnlocked() {
mUserUnlockedActions.executeAllAndDestroy()
- mUserUnlockedReceiver.unregisterReceiverSafelyAsync(mContext)
+ mUserUnlockedReceiver.unregisterReceiverSafely(mContext)
}
/** Stops the receiver from listening for ACTION_USER_UNLOCK broadcasts. */
override fun close() {
- mUserUnlockedReceiver.unregisterReceiverSafelyAsync(mContext)
+ mUserUnlockedReceiver.unregisterReceiverSafely(mContext)
}
/**
diff --git a/src/com/android/launcher3/util/ScreenOnTracker.java b/src/com/android/launcher3/util/ScreenOnTracker.java
index c1d192c..12eff61 100644
--- a/src/com/android/launcher3/util/ScreenOnTracker.java
+++ b/src/com/android/launcher3/util/ScreenOnTracker.java
@@ -19,6 +19,8 @@
import static android.content.Intent.ACTION_SCREEN_ON;
import static android.content.Intent.ACTION_USER_PRESENT;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
import android.content.Context;
import android.content.Intent;
@@ -32,7 +34,8 @@
public static final MainThreadInitializedObject<ScreenOnTracker> INSTANCE =
new MainThreadInitializedObject<>(ScreenOnTracker::new);
- private final SimpleBroadcastReceiver mReceiver = new SimpleBroadcastReceiver(this::onReceive);
+ private final SimpleBroadcastReceiver mReceiver =
+ new SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, this::onReceive);
private final CopyOnWriteArrayList<ScreenOnListener> mListeners = new CopyOnWriteArrayList<>();
private final Context mContext;
@@ -42,12 +45,12 @@
// Assume that the screen is on to begin with
mContext = context;
mIsScreenOn = true;
- mReceiver.registerAsync(context, ACTION_SCREEN_ON, ACTION_SCREEN_OFF, ACTION_USER_PRESENT);
+ mReceiver.register(context, ACTION_SCREEN_ON, ACTION_SCREEN_OFF, ACTION_USER_PRESENT);
}
@Override
public void close() {
- mReceiver.unregisterReceiverSafelyAsync(mContext);
+ mReceiver.unregisterReceiverSafely(mContext);
}
private void onReceive(Intent intent) {
diff --git a/src/com/android/launcher3/util/SimpleBroadcastReceiver.java b/src/com/android/launcher3/util/SimpleBroadcastReceiver.java
index 5f39cce..539a7cb 100644
--- a/src/com/android/launcher3/util/SimpleBroadcastReceiver.java
+++ b/src/com/android/launcher3/util/SimpleBroadcastReceiver.java
@@ -15,21 +15,17 @@
*/
package com.android.launcher3.util;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.Handler;
import android.os.Looper;
import android.os.PatternMatcher;
import android.text.TextUtils;
+import androidx.annotation.AnyThread;
import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
-import androidx.annotation.WorkerThread;
-
-import com.android.launcher3.BuildConfig;
import java.util.function.Consumer;
@@ -37,8 +33,16 @@
private final Consumer<Intent> mIntentConsumer;
- public SimpleBroadcastReceiver(Consumer<Intent> intentConsumer) {
+ // Handler to register/unregister broadcast receiver
+ private final Handler mHandler;
+
+ public SimpleBroadcastReceiver(LooperExecutor looperExecutor, Consumer<Intent> intentConsumer) {
+ this(looperExecutor.getHandler(), intentConsumer);
+ }
+
+ public SimpleBroadcastReceiver(Handler handler, Consumer<Intent> intentConsumer) {
mIntentConsumer = intentConsumer;
+ mHandler = handler;
}
@Override
@@ -46,55 +50,104 @@
mIntentConsumer.accept(intent);
}
- /** Helper method to register multiple actions. Caller should be on main thread. */
- @UiThread
- public void registerAsync(Context context, String... actions) {
- assertOnMainThread();
- UI_HELPER_EXECUTOR.execute(() -> registerSync(context, actions));
+ /** Calls {@link #register(Context, Runnable, String...)} with null completionCallback. */
+ @AnyThread
+ public void register(Context context, String... actions) {
+ register(context, null, actions);
}
- /** Helper method to register multiple actions. Caller should be on main thread. */
- @WorkerThread
- public void registerSync(Context context, String... actions) {
- assertOnBgThread();
+ /**
+ * Calls {@link #register(Context, Runnable, int, String...)} with null completionCallback.
+ */
+ @AnyThread
+ public void register(Context context, int flags, String... actions) {
+ register(context, null, flags, actions);
+ }
+
+ /**
+ * Register broadcast receiver. If this method is called on the same looper with mHandler's
+ * looper, then register will be called synchronously. Otherwise asynchronously. This ensures
+ * register happens on {@link #mHandler}'s looper.
+ *
+ * @param completionCallback callback that will be triggered after registration is completed,
+ * caller usually pass this callback to check if states has changed
+ * while registerReceiver() is executed on a binder call.
+ */
+ @AnyThread
+ public void register(
+ Context context, @Nullable Runnable completionCallback, String... actions) {
+ if (Looper.myLooper() == mHandler.getLooper()) {
+ registerInternal(context, completionCallback, actions);
+ } else {
+ mHandler.post(() -> registerInternal(context, completionCallback, actions));
+ }
+ }
+
+ /** Register broadcast receiver and run completion callback if passed. */
+ @AnyThread
+ private void registerInternal(
+ Context context, @Nullable Runnable completionCallback, String... actions) {
context.registerReceiver(this, getFilter(actions));
+ if (completionCallback != null) {
+ completionCallback.run();
+ }
}
/**
- * Helper method to register multiple actions associated with a action. Caller should be from
- * main thread.
+ * Same as {@link #register(Context, Runnable, String...)} above but with additional flags
+ * params.
*/
- @UiThread
- public void registerPkgActionsAsync(Context context, @Nullable String pkg, String... actions) {
- assertOnMainThread();
- UI_HELPER_EXECUTOR.execute(() -> registerPkgActionsSync(context, pkg, actions));
+ @AnyThread
+ public void register(
+ Context context, @Nullable Runnable completionCallback, int flags, String... actions) {
+ if (Looper.myLooper() == mHandler.getLooper()) {
+ registerInternal(context, completionCallback, flags, actions);
+ } else {
+ mHandler.post(() -> registerInternal(context, completionCallback, flags, actions));
+ }
+ }
+
+ /** Register broadcast receiver and run completion callback if passed. */
+ @AnyThread
+ private void registerInternal(
+ Context context, @Nullable Runnable completionCallback, int flags, String... actions) {
+ context.registerReceiver(this, getFilter(actions), flags);
+ if (completionCallback != null) {
+ completionCallback.run();
+ }
+ }
+
+ /** Same as {@link #register(Context, Runnable, String...)} above but with pkg name. */
+ @AnyThread
+ public void registerPkgActions(Context context, @Nullable String pkg, String... actions) {
+ if (Looper.myLooper() == mHandler.getLooper()) {
+ context.registerReceiver(this, getPackageFilter(pkg, actions));
+ } else {
+ mHandler.post(() -> {
+ context.registerReceiver(this, getPackageFilter(pkg, actions));
+ });
+ }
}
/**
- * Helper method to register multiple actions associated with a action. Caller should be from
- * bg thread.
+ * Unregister broadcast receiver. If this method is called on the same looper with mHandler's
+ * looper, then unregister will be called synchronously. Otherwise asynchronously. This ensures
+ * unregister happens on {@link #mHandler}'s looper.
*/
- @WorkerThread
- public void registerPkgActionsSync(Context context, @Nullable String pkg, String... actions) {
- assertOnBgThread();
- context.registerReceiver(this, getPackageFilter(pkg, actions));
+ @AnyThread
+ public void unregisterReceiverSafely(Context context) {
+ if (Looper.myLooper() == mHandler.getLooper()) {
+ unregisterReceiverSafelyInternal(context);
+ } else {
+ mHandler.post(() -> {
+ unregisterReceiverSafelyInternal(context);
+ });
+ }
}
- /**
- * Unregisters the receiver ignoring any errors on bg thread. Caller should be on main thread.
- */
- @UiThread
- public void unregisterReceiverSafelyAsync(Context context) {
- assertOnMainThread();
- UI_HELPER_EXECUTOR.execute(() -> unregisterReceiverSafelySync(context));
- }
-
- /**
- * Unregisters the receiver ignoring any errors on bg thread. Caller should be on bg thread.
- */
- @WorkerThread
- public void unregisterReceiverSafelySync(Context context) {
- assertOnBgThread();
+ /** Unregister broadcast receiver ignoring any errors. */
+ @AnyThread
+ private void unregisterReceiverSafelyInternal(Context context) {
try {
context.unregisterReceiver(this);
} catch (IllegalArgumentException e) {
@@ -121,20 +174,4 @@
}
return filter;
}
-
- private static void assertOnBgThread() {
- if (BuildConfig.IS_STUDIO_BUILD && isMainThread()) {
- throw new IllegalStateException("Should not be called from main thread!");
- }
- }
-
- private static void assertOnMainThread() {
- if (BuildConfig.IS_STUDIO_BUILD && !isMainThread()) {
- throw new IllegalStateException("Should not be called from bg thread!");
- }
- }
-
- private static boolean isMainThread() {
- return Thread.currentThread() == Looper.getMainLooper().getThread();
- }
}
diff --git a/src/com/android/launcher3/util/ViewCache.java b/src/com/android/launcher3/util/ViewCache.java
index 98e6822..b98e977 100644
--- a/src/com/android/launcher3/util/ViewCache.java
+++ b/src/com/android/launcher3/util/ViewCache.java
@@ -21,6 +21,8 @@
import android.view.View;
import android.view.ViewGroup;
+import androidx.annotation.VisibleForTesting;
+
import com.android.launcher3.R;
/**
@@ -67,7 +69,8 @@
}
}
- private static class CacheEntry {
+ @VisibleForTesting
+ static class CacheEntry {
final int mMaxSize;
final View[] mViews;
diff --git a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
index a2277a0..f8cbe0d 100644
--- a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
+++ b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
@@ -32,7 +32,7 @@
private static final int MIN_PARALLAX_PAGE_SPAN = 4;
private final SimpleBroadcastReceiver mWallpaperChangeReceiver =
- new SimpleBroadcastReceiver(i -> onWallpaperChanged());
+ new SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, i -> onWallpaperChanged());
private final Workspace<?> mWorkspace;
private final boolean mIsRtl;
private final Handler mHandler;
@@ -198,10 +198,10 @@
public void setWindowToken(IBinder token) {
mWindowToken = token;
if (mWindowToken == null && mRegistered) {
- mWallpaperChangeReceiver.unregisterReceiverSafelyAsync(mWorkspace.getContext());
+ mWallpaperChangeReceiver.unregisterReceiverSafely(mWorkspace.getContext());
mRegistered = false;
} else if (mWindowToken != null && !mRegistered) {
- mWallpaperChangeReceiver.registerAsync(
+ mWallpaperChangeReceiver.register(
mWorkspace.getContext(), ACTION_WALLPAPER_CHANGED);
onWallpaperChanged();
mRegistered = true;
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
index 40c3984..71d8503 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
@@ -21,22 +21,16 @@
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.widget.RemoteViews;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.util.Executors;
-import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.widget.LauncherWidgetHolder.ProviderChangedListener;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
-import java.util.Set;
-import java.util.WeakHashMap;
import java.util.function.IntConsumer;
/**
@@ -129,37 +123,4 @@
public void clearViews() {
super.clearViews();
}
-
- public static class ListenableHostView extends LauncherAppWidgetHostView {
-
- private Set<Runnable> mUpdateListeners = Collections.EMPTY_SET;
-
- ListenableHostView(Context context) {
- super(context);
- }
-
- @Override
- public void updateAppWidget(RemoteViews remoteViews) {
- super.updateAppWidget(remoteViews);
- mUpdateListeners.forEach(Runnable::run);
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(LauncherAppWidgetHostView.class.getName());
- }
-
- /**
- * Adds a callback to be run everytime the provided app widget updates.
- * @return a closable to remove this callback
- */
- public SafeCloseable addUpdateListener(Runnable callback) {
- if (mUpdateListeners == Collections.EMPTY_SET) {
- mUpdateListeners = Collections.newSetFromMap(new WeakHashMap<>());
- }
- mUpdateListeners.add(callback);
- return () -> mUpdateListeners.remove(callback);
- }
- }
}
diff --git a/src/com/android/launcher3/widget/LauncherWidgetHolder.java b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
index 1fb8c83..f499fca 100644
--- a/src/com/android/launcher3/widget/LauncherWidgetHolder.java
+++ b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
@@ -37,6 +37,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
import com.android.launcher3.BaseActivity;
@@ -49,7 +50,6 @@
import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.util.ResourceBasedOverride;
import com.android.launcher3.util.SafeCloseable;
-import com.android.launcher3.widget.LauncherAppWidgetHost.ListenableHostView;
import com.android.launcher3.widget.custom.CustomWidgetManager;
import java.util.ArrayList;
@@ -484,7 +484,8 @@
* Sets or unsets a flag the can change whether the widget host should be in the listening
* state.
*/
- private void setShouldListenFlag(int flag, boolean on) {
+ @VisibleForTesting
+ void setShouldListenFlag(int flag, boolean on) {
if (on) {
mFlags.updateAndGet(old -> old | flag);
} else {
diff --git a/src/com/android/launcher3/widget/ListenableHostView.java b/src/com/android/launcher3/widget/ListenableHostView.java
new file mode 100644
index 0000000..b809db0
--- /dev/null
+++ b/src/com/android/launcher3/widget/ListenableHostView.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.launcher3.widget;
+
+import android.content.Context;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.RemoteViews;
+
+import com.android.launcher3.util.SafeCloseable;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+public class ListenableHostView extends LauncherAppWidgetHostView {
+
+ private Set<Runnable> mUpdateListeners = Collections.EMPTY_SET;
+
+ ListenableHostView(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void updateAppWidget(RemoteViews remoteViews) {
+ super.updateAppWidget(remoteViews);
+ mUpdateListeners.forEach(Runnable::run);
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ info.setClassName(LauncherAppWidgetHostView.class.getName());
+ }
+
+ /**
+ * Adds a callback to be run everytime the provided app widget updates.
+ * @return a closable to remove this callback
+ */
+ public SafeCloseable addUpdateListener(Runnable callback) {
+ if (mUpdateListeners == Collections.EMPTY_SET) {
+ mUpdateListeners = Collections.newSetFromMap(new WeakHashMap<>());
+ }
+ mUpdateListeners.add(callback);
+ return () -> mUpdateListeners.remove(callback);
+ }
+}
diff --git a/src/com/android/launcher3/widget/WidgetManagerHelper.java b/src/com/android/launcher3/widget/WidgetManagerHelper.java
index 9132b4f..23d0585 100644
--- a/src/com/android/launcher3/widget/WidgetManagerHelper.java
+++ b/src/com/android/launcher3/widget/WidgetManagerHelper.java
@@ -32,6 +32,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
+import androidx.annotation.VisibleForTesting;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -57,8 +58,13 @@
final Context mContext;
public WidgetManagerHelper(Context context) {
+ this(context, AppWidgetManager.getInstance(context));
+ }
+
+ @VisibleForTesting
+ public WidgetManagerHelper(Context context, AppWidgetManager appWidgetManager) {
mContext = context;
- mAppWidgetManager = AppWidgetManager.getInstance(context);
+ mAppWidgetManager = appWidgetManager;
}
/**
diff --git a/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java b/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
index 9260af9..d84a219 100644
--- a/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
@@ -16,7 +16,7 @@
package com.android.launcher3.widget.picker;
-import static com.android.launcher3.widget.util.WidgetsTableUtils.groupWidgetItemsUsingRowPxWithoutReordering;
+import static com.android.launcher3.widget.util.WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering;
import android.content.ComponentName;
import android.content.Context;
@@ -38,6 +38,7 @@
import com.android.launcher3.pageindicators.PageIndicatorDots;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -57,6 +58,12 @@
private static final String INITIALLY_DISPLAYED_WIDGETS_STATE_KEY =
"widgetRecommendationsView:mDisplayedWidgets";
private static final int MAX_CATEGORIES = 3;
+
+ // Whether to show all widgets in a full page without any limitation on height
+ private boolean mShowFullPageViewIfLowDensity = false;
+ // Number of items below which a category is considered low density.
+ private static final int IDEAL_ITEMS_PER_CATEGORY = 2;
+
private TextView mRecommendationPageTitle;
private final List<String> mCategoryTitles = new ArrayList<>();
@@ -88,6 +95,14 @@
}
/**
+ * When there are less than 3 categories or when at least one category has less than 2 widgets,
+ * all widgets will be shown in a single page without being limited by the available height.
+ */
+ public void enableFullPageViewIfLowDensity() {
+ mShowFullPageViewIfLowDensity = true;
+ }
+
+ /**
* Saves the necessary state in the provided bundle. To be called in case of orientation /
* other config changes.
*/
@@ -170,6 +185,22 @@
return displayedWidgets.size();
}
+ private boolean shouldShowFullPageView(
+ Map<WidgetRecommendationCategory, List<WidgetItem>> recommendations) {
+ if (mShowFullPageViewIfLowDensity) {
+ boolean hasLessCategories = recommendations.size() < MAX_CATEGORIES;
+ long lowDensityCategoriesCount = recommendations.values()
+ .stream()
+ .limit(MAX_CATEGORIES)
+ .filter(items -> items.size() < IDEAL_ITEMS_PER_CATEGORY).count();
+
+ // If there less number of categories or if there are at least 2 categorizes with less
+ // widgets, prefer showing single page view.
+ return hasLessCategories || lowDensityCategoriesCount > 1;
+ }
+ return false;
+ }
+
/**
* Displays the recommendations grouped by categories as pages.
* <p>In case of a single category, no title is displayed for it.</p>
@@ -188,6 +219,14 @@
Map<WidgetRecommendationCategory, List<WidgetItem>> recommendations,
DeviceProfile deviceProfile, final @Px float availableHeight,
final @Px int availableWidth, final @Px int cellPadding, final int requestedPage) {
+ if (shouldShowFullPageView(recommendations)) {
+ // Show all widgets in single page with unlimited available height.
+ return setRecommendations(
+ recommendations.values().stream().flatMap(Collection::stream).toList(),
+ deviceProfile, /*availableHeight=*/ Float.MAX_VALUE, availableWidth,
+ cellPadding);
+
+ }
this.mAvailableHeight = availableHeight;
this.mAvailableWidth = availableWidth;
Context context = getContext();
@@ -325,7 +364,7 @@
// Since we are limited by space, we don't sort recommendations - to show most relevant
// (if possible).
- List<ArrayList<WidgetItem>> rows = groupWidgetItemsUsingRowPxWithoutReordering(
+ List<ArrayList<WidgetItem>> rows = groupWidgetItemsUsingRowPxWithReordering(
filteredRecommendedWidgets,
context,
deviceProfile,
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index fd15677..2e36583 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -679,6 +679,18 @@
return sheet;
}
+ /**
+ * Updates the widget picker's title and description in the header to the provided values (if
+ * present).
+ */
+ public void mayUpdateTitleAndDescription(@Nullable String title,
+ @Nullable String descriptionRes) {
+ if (title != null) {
+ mHeaderTitle.setText(title);
+ }
+ // Full sheet doesn't support a description.
+ }
+
@Override
public void saveHierarchyState(SparseArray<Parcelable> sparseArray) {
Bundle bundle = new Bundle();
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
index 1ed3d88..0bcab60 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
@@ -18,7 +18,7 @@
import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
import static com.android.launcher3.widget.util.WidgetSizes.getWidgetSizePx;
-import static com.android.launcher3.widget.util.WidgetsTableUtils.WIDGETS_TABLE_ROW_SIZE_COMPARATOR;
+import static com.android.launcher3.widget.util.WidgetsTableUtils.WIDGETS_TABLE_ROW_COUNT_COMPARATOR;
import static java.lang.Math.max;
@@ -163,6 +163,6 @@
}
// Perform re-ordering once we have filtered out recommendations that fit.
- return filteredRows.stream().sorted(WIDGETS_TABLE_ROW_SIZE_COMPARATOR).toList();
+ return filteredRows.stream().sorted(WIDGETS_TABLE_ROW_COUNT_COMPARATOR).toList();
}
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
index 840d98a..c84680d 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
@@ -36,6 +36,7 @@
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ScrollView;
+import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -60,9 +61,6 @@
* Popup for showing the full list of available widgets with a two-pane layout.
*/
public class WidgetsTwoPaneSheet extends WidgetsFullSheet {
-
- private static final int PERSONAL_TAB = 0;
- private static final int WORK_TAB = 1;
private static final int MINIMUM_WIDTH_LEFT_PANE_FOLDABLE_DP = 268;
private static final int MAXIMUM_WIDTH_LEFT_PANE_FOLDABLE_DP = 395;
private static final String SUGGESTIONS_PACKAGE_NAME = "widgets_list_suggestions_entry";
@@ -83,6 +81,7 @@
private int mActivePage = -1;
@Nullable
private PackageUserKey mSelectedHeader;
+ private TextView mHeaderDescription;
public WidgetsTwoPaneSheet(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
@@ -120,12 +119,17 @@
mWidgetRecommendationsView.initParentViews(mWidgetRecommendationsContainer);
mWidgetRecommendationsView.setWidgetCellLongClickListener(this);
mWidgetRecommendationsView.setWidgetCellOnClickListener(this);
+ if (!mDeviceProfile.isTwoPanels) {
+ mWidgetRecommendationsView.enableFullPageViewIfLowDensity();
+ }
// To save the currently displayed page, so that, it can be requested when rebinding
// recommendations with different size constraints.
mWidgetRecommendationsView.addPageSwitchListener(
newPage -> mRecommendationsCurrentPage = newPage);
mHeaderTitle = mContent.findViewById(R.id.title);
+ mHeaderDescription = mContent.findViewById(R.id.widget_picker_description);
+
mRightPane = mContent.findViewById(R.id.right_pane);
mRightPaneScrollView = mContent.findViewById(R.id.right_pane_scroll_view);
mRightPaneScrollView.setOverScrollMode(View.OVER_SCROLL_NEVER);
@@ -141,6 +145,17 @@
}
@Override
+ public void mayUpdateTitleAndDescription(@Nullable String title, @Nullable String description) {
+ if (title != null) {
+ mHeaderTitle.setText(title);
+ }
+ if (description != null) {
+ mHeaderDescription.setText(description);
+ mHeaderDescription.setVisibility(VISIBLE);
+ }
+ }
+
+ @Override
protected int getTabletHorizontalMargin(DeviceProfile deviceProfile) {
if (enableCategorizedWidgetSuggestions()) {
// two pane picker is full width for fold as well as tablet.
@@ -371,9 +386,10 @@
protected void updateRecyclerViewVisibility(AdapterHolder adapterHolder) {
// The first item is always an empty space entry. Look for any more items.
boolean isWidgetAvailable = adapterHolder.mWidgetsListAdapter.hasVisibleEntries();
-
- mRightPane.setVisibility(isWidgetAvailable ? VISIBLE : GONE);
-
+ if (!isWidgetAvailable) {
+ mRightPane.removeAllViews();
+ mRightPane.addView(mNoWidgetsView);
+ }
super.updateRecyclerViewVisibility(adapterHolder);
}
diff --git a/src/com/android/launcher3/widget/util/WidgetsTableUtils.java b/src/com/android/launcher3/widget/util/WidgetsTableUtils.java
index edaf474..df72f07 100644
--- a/src/com/android/launcher3/widget/util/WidgetsTableUtils.java
+++ b/src/com/android/launcher3/widget/util/WidgetsTableUtils.java
@@ -69,6 +69,21 @@
});
/**
+ * Comparator that enables displaying rows with more number of items at the top, and then
+ * rest of widgets shown in increasing order of their size (totalW * H).
+ */
+ public static final Comparator<ArrayList<WidgetItem>> WIDGETS_TABLE_ROW_COUNT_COMPARATOR =
+ Comparator.comparingInt(row -> {
+ if (row.size() > 1) {
+ return -row.size();
+ } else {
+ int rowWidth = row.stream().mapToInt(w -> w.spanX).sum();
+ int rowHeight = row.get(0).spanY;
+ return (rowWidth * rowHeight);
+ }
+ });
+
+ /**
* Groups {@code widgetItems} items into a 2D array which matches their appearance in a UI
* table. This takes liberty to rearrange widgets to make the table visually appealing.
*/
diff --git a/tests/assets/ReorderWidgets/full_reorder_case b/tests/assets/ReorderWidgets/full_reorder_case
index 850e4fd..2890b79 100644
--- a/tests/assets/ReorderWidgets/full_reorder_case
+++ b/tests/assets/ReorderWidgets/full_reorder_case
@@ -17,12 +17,12 @@
# Test 4x4
board: 4x4
xxxx
-bbmm
+bbaa
iimm
-iiaa
+iimm
arguments: 0 2
board: 4x4
xxxx
-bbii
+bbaa
mmii
-mmaa
\ No newline at end of file
+mmii
\ No newline at end of file
diff --git a/tests/assets/ReorderWidgets/push_reorder_case b/tests/assets/ReorderWidgets/push_reorder_case
index 8e845a2..1eacfae 100644
--- a/tests/assets/ReorderWidgets/push_reorder_case
+++ b/tests/assets/ReorderWidgets/push_reorder_case
@@ -17,28 +17,28 @@
#Test 5x5
board: 5x5
xxxxx
-bbbm-
+bbb--
--ccc
--ddd
------
-arguments: 2 1
+----m
+arguments: 2 2
board: 5x5
xxxxx
---m--
bbb--
+--m--
--ccc
--ddd
#6x5 Test
board: 6x5
xxxxxx
-bbbbm-
+bbbb--
--aaa-
--ddd-
-------
-arguments: 2 1
+-----m
+arguments: 2 2
board: 6x5
xxxxxx
---m---
bbbb--
+--m---
--aaa-
--ddd-
\ No newline at end of file
diff --git a/tests/assets/ReorderWidgets/simple_reorder_case b/tests/assets/ReorderWidgets/simple_reorder_case
index 2c50ce4..991ccb5 100644
--- a/tests/assets/ReorderWidgets/simple_reorder_case
+++ b/tests/assets/ReorderWidgets/simple_reorder_case
@@ -21,7 +21,7 @@
--mm-
-----
-----
-arguments: 0 4
+arguments: 0 3
board: 5x5
xxxxx
-----
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 59d0de6..dc3b321 100644
--- a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java
@@ -157,7 +157,6 @@
"get-overview-current-page-index";
public static final String REQUEST_GET_SPLIT_SELECTION_ACTIVE = "get-split-selection-active";
public static final String REQUEST_ENABLE_ROTATION = "enable_rotation";
- public static final String REQUEST_ENABLE_SUGGESTION = "enable-suggestion";
public static final String REQUEST_MODEL_QUEUE_CLEARED = "model-queue-cleared";
public static boolean sDebugTracing = false;
@@ -174,6 +173,7 @@
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 OVERVIEW_SELECT_TOOLTIP_MISALIGNED = "b/332485341";
+ public static final String PRIVATE_SPACE_SCROLL_FAILURE = "b/339737008";
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/FakeInvariantDeviceProfileTest.kt b/tests/multivalentTests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
index 0538870..954dc8f 100644
--- a/tests/multivalentTests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
@@ -47,14 +47,13 @@
abstract class FakeInvariantDeviceProfileTest {
protected lateinit var context: Context
- protected var inv: InvariantDeviceProfile? = null
- protected val info: Info = mock()
- protected var windowBounds: WindowBounds? = null
- protected var isMultiWindowMode: Boolean = false
- protected var transposeLayoutWithOrientation: Boolean = false
- protected var useTwoPanels: Boolean = false
- protected var isGestureMode: Boolean = true
- protected var isTransientTaskbar: Boolean = true
+ protected lateinit var inv: InvariantDeviceProfile
+ protected val info = mock<Info>()
+ protected lateinit var windowBounds: WindowBounds
+ private var transposeLayoutWithOrientation = false
+ private var useTwoPanels = false
+ private var isGestureMode = true
+ private var isTransientTaskbar = true
@Rule @JvmField val limitDevicesRule = LimitDevicesRule()
@@ -73,7 +72,7 @@
info,
windowBounds,
SparseArray(),
- isMultiWindowMode,
+ /*isMultiWindowMode=*/ false,
transposeLayoutWithOrientation,
useTwoPanels,
isGestureMode,
diff --git a/tests/multivalentTests/src/com/android/launcher3/accessibility/FolderAccessibilityHelperTest.kt b/tests/multivalentTests/src/com/android/launcher3/accessibility/FolderAccessibilityHelperTest.kt
new file mode 100644
index 0000000..1cbe1df
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/accessibility/FolderAccessibilityHelperTest.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.accessibility // Use the original package
+
+// Imports
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.CellLayout
+import com.android.launcher3.folder.FolderPagedView
+import com.android.launcher3.util.ActivityContextWrapper
+import kotlin.math.min
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FolderAccessibilityHelperTest {
+
+ // Context
+ private lateinit var mContext: Context
+ // Mocks
+ @Mock private lateinit var mockParent: FolderPagedView
+ @Mock private lateinit var mockLayout: CellLayout
+
+ private var countX = 4
+ private var countY = 3
+ private var index = 1
+
+ // System under test
+ private lateinit var folderAccessibilityHelper: FolderAccessibilityHelper
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ mContext = ActivityContextWrapper(getApplicationContext())
+ `when`(mockLayout.parent).thenReturn(mockParent)
+ `when`(mockLayout.context).thenReturn(mContext)
+
+ // mStartPosition isn't recalculated after the constructor
+ // If you want to create new tests with different starting params,
+ // rebuild the folderAccessibilityHelper object
+ val countX = 4
+ val countY = 3
+ val index = 1
+ `when`(mockParent.indexOfChild(mockLayout)).thenReturn(index)
+ `when`(mockLayout.countX).thenReturn(countX)
+ `when`(mockLayout.countY).thenReturn(countY)
+
+ folderAccessibilityHelper = FolderAccessibilityHelper(mockLayout)
+ }
+
+ // Test for intersectsValidDropTarget()
+ @Test
+ fun testIntersectsValidDropTarget() {
+ // Setup
+ val id = 5
+ val allocatedContentSize = 20
+ // Make layout function public @VisibleForTesting
+ `when`(mockParent.allocatedContentSize).thenReturn(allocatedContentSize)
+
+ // Execute
+ val result = folderAccessibilityHelper.intersectsValidDropTarget(id)
+
+ // Verify
+ val expectedResult = min(id, allocatedContentSize - (index * countX * countY) - 1)
+ assertEquals(expectedResult, result)
+ }
+
+ // Test for getLocationDescriptionForIconDrop()
+ @Test
+ fun testGetLocationDescriptionForIconDrop() {
+ // Setup
+ val id = 5
+
+ // Execute
+ val result = folderAccessibilityHelper.getLocationDescriptionForIconDrop(id)
+
+ // Verify
+ val expectedResult = "Move to position ${id + (index * countX * countY) + 1}"
+ assertEquals(expectedResult, result)
+ }
+
+ // Test for getConfirmationForIconDrop()
+ @Test
+ fun testGetConfirmationForIconDrop() {
+ // Execute
+ val result =
+ folderAccessibilityHelper.getConfirmationForIconDrop(0) // Id doesn't matter here
+
+ // Verify
+ assertEquals("Item moved", result)
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutTestCaseReader.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutTestCaseReader.java
index 419cb3d..f1403e5 100644
--- a/tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutTestCaseReader.java
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutTestCaseReader.java
@@ -54,7 +54,7 @@
}
public static class Arguments extends TestSection {
- String[] arguments;
+ public String[] arguments;
public Arguments(String[] arguments) {
super(State.ARGUMENTS);
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/WidgetsModelTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/WidgetsModelTest.kt
new file mode 100644
index 0000000..71f7d47
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/model/WidgetsModelTest.kt
@@ -0,0 +1,209 @@
+/*
+ * 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.appwidget.AppWidgetManager
+import android.content.ComponentName
+import android.content.Context
+import android.os.UserHandle
+import android.platform.test.rule.AllowedDevices
+import android.platform.test.rule.DeviceProduct
+import android.platform.test.rule.LimitDevicesRule
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.icons.IconCache
+import com.android.launcher3.model.data.PackageItemInfo
+import com.android.launcher3.pm.UserCache
+import com.android.launcher3.util.ActivityContextWrapper
+import com.android.launcher3.util.ComponentKey
+import com.android.launcher3.util.Executors
+import com.android.launcher3.util.IntSet
+import com.android.launcher3.util.PackageUserKey
+import com.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo
+import com.android.launcher3.widget.WidgetSections
+import com.android.launcher3.widget.WidgetSections.NO_CATEGORY
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.spy
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.any
+import org.mockito.kotlin.whenever
+
+@AllowedDevices(allowed = [DeviceProduct.ROBOLECTRIC])
+@RunWith(AndroidJUnit4::class)
+class WidgetsModelTest {
+ @Rule @JvmField val limitDevicesRule = LimitDevicesRule()
+ @Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+ @Mock private lateinit var appWidgetManager: AppWidgetManager
+ @Mock private lateinit var app: LauncherAppState
+ @Mock private lateinit var iconCacheMock: IconCache
+
+ private lateinit var context: Context
+ private lateinit var idp: InvariantDeviceProfile
+ private lateinit var underTest: WidgetsModel
+
+ private var widgetSectionCategory: Int = 0
+ private lateinit var appAPackage: String
+
+ @Before
+ fun setUp() {
+ val appContext: Context = ApplicationProvider.getApplicationContext()
+ idp = InvariantDeviceProfile.INSTANCE[appContext]
+
+ context =
+ object : ActivityContextWrapper(ApplicationProvider.getApplicationContext()) {
+ override fun getSystemService(name: String): Any? {
+ if (name == "appwidget") {
+ return appWidgetManager
+ }
+ return super.getSystemService(name)
+ }
+
+ override fun getDeviceProfile(): DeviceProfile {
+ return idp.getDeviceProfile(applicationContext).copy(applicationContext)
+ }
+ }
+
+ whenever(iconCacheMock.getTitleNoCache(any<LauncherAppWidgetProviderInfo>()))
+ .thenReturn("title")
+ whenever(app.iconCache).thenReturn(iconCacheMock)
+ whenever(app.context).thenReturn(context)
+ whenever(app.invariantDeviceProfile).thenReturn(idp)
+
+ val widgetToCategoryEntry: Map.Entry<ComponentName, IntSet> =
+ WidgetSections.getWidgetsToCategory(context).entries.first()
+ widgetSectionCategory = widgetToCategoryEntry.value.first()
+ val appAWidgetComponent = widgetToCategoryEntry.key
+ appAPackage = appAWidgetComponent.packageName
+
+ whenever(appWidgetManager.getInstalledProvidersForProfile(any()))
+ .thenReturn(
+ listOf(
+ // First widget from widget sections xml
+ createAppWidgetProviderInfo(appAWidgetComponent),
+ // A widget that belongs to same package as the widget from widget sections
+ // xml, but, because it's not mentioned in xml, it would be included in its
+ // own package section.
+ createAppWidgetProviderInfo(
+ ComponentName.createRelative(appAPackage, APP_A_TEST_WIDGET_NAME)
+ ),
+ // A widget in different package (none of that app's widgets are in widget
+ // sections xml)
+ createAppWidgetProviderInfo(AppBTestWidgetComponent),
+ )
+ )
+
+ val userCache = spy(UserCache.INSTANCE.get(context))
+ whenever(userCache.userProfiles).thenReturn(listOf(UserHandle.CURRENT))
+
+ underTest = WidgetsModel()
+ }
+
+ @Test
+ fun widgetsByPackage_treatsWidgetSectionsAsSeparatePackageItems() {
+ loadWidgets()
+
+ val packages: Map<PackageItemInfo, List<WidgetItem>> = underTest.widgetsByPackageItem
+
+ // expect 3 package items
+ // one for the custom section with widget from appA
+ // one for package section for second widget from appA (that wasn't listed in xml)
+ // and one for package section for appB
+ assertThat(packages).hasSize(3)
+
+ // Each package item when used as a key is distinct (i.e. even if appA is split into custom
+ // package and owner package section, each of them is a distinct key). This ensures that
+ // clicking on a custom widget section doesn't take user to app package section.
+ val distinctPackageUserKeys =
+ packages.map { PackageUserKey.fromPackageItemInfo(it.key) }.distinct()
+ assertThat(distinctPackageUserKeys).hasSize(3)
+
+ val customSections = packages.filter { it.key.widgetCategory == widgetSectionCategory }
+ assertThat(customSections).hasSize(1)
+ val widgetsInCustomSection = customSections.entries.first().value
+ assertThat(widgetsInCustomSection).hasSize(1)
+
+ val packageSections = packages.filter { it.key.widgetCategory == NO_CATEGORY }
+ assertThat(packageSections).hasSize(2)
+
+ // App A's package section
+ val appAPackageSection = packageSections.filter { it.key.packageName == appAPackage }
+ assertThat(appAPackageSection).hasSize(1)
+ val widgetsInAppASection = appAPackageSection.entries.first().value
+ assertThat(widgetsInAppASection).hasSize(1)
+
+ // App B's package section
+ val appBPackageSection =
+ packageSections.filter { it.key.packageName == AppBTestWidgetComponent.packageName }
+ assertThat(appBPackageSection).hasSize(1)
+ val widgetsInAppBSection = appBPackageSection.entries.first().value
+ assertThat(widgetsInAppBSection).hasSize(1)
+ }
+
+ @Test
+ fun widgetComponentMap_returnsWidgets() {
+ loadWidgets()
+
+ val widgetsByComponentKey: Map<ComponentKey, WidgetItem> = underTest.widgetsByComponentKey
+
+ assertThat(widgetsByComponentKey).hasSize(3)
+ widgetsByComponentKey.forEach { entry ->
+ assertThat(entry.key).isEqualTo(entry.value as ComponentKey)
+ }
+ }
+
+ @Test
+ fun widgets_noData_returnsEmpty() {
+ // no loadWidgets()
+
+ assertThat(underTest.widgetsByComponentKey).isEmpty()
+ }
+
+ private fun loadWidgets() {
+ val latch = CountDownLatch(1)
+ Executors.MODEL_EXECUTOR.execute {
+ underTest.update(app, /* packageUser= */ null)
+ latch.countDown()
+ }
+ if (!latch.await(LOAD_WIDGETS_TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
+ fail("Timed out waiting widgets to load")
+ }
+ }
+
+ companion object {
+ // Another widget within app A
+ private const val APP_A_TEST_WIDGET_NAME = "MyProvider"
+
+ private val AppBTestWidgetComponent: ComponentName =
+ ComponentName.createRelative("com.test.package", "TestProvider")
+
+ private const val LOAD_WIDGETS_TIMEOUT_SECONDS = 2L
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/RunnableListTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/RunnableListTest.kt
new file mode 100644
index 0000000..093afc9
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/RunnableListTest.kt
@@ -0,0 +1,113 @@
+/*
+ * 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 androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.reset
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyZeroInteractions
+
+@SmallTest
+class RunnableListTest {
+
+ @Mock private lateinit var runnable1: Runnable
+ @Mock private lateinit var runnable2: Runnable
+
+ private val underTest = RunnableList()
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun not_destroyedByDefault() {
+ assertThat(underTest.isDestroyed).isFalse()
+ }
+
+ @Test
+ fun add_and_run() {
+ underTest.add(runnable1)
+ underTest.add(runnable2)
+
+ underTest.executeAllAndDestroy()
+
+ verify(runnable1).run()
+ verify(runnable2).run()
+ assertThat(underTest.isDestroyed).isTrue()
+ }
+
+ @Test
+ fun add_to_destroyed_runnableList_run_immediately() {
+ underTest.executeAllAndDestroy()
+
+ underTest.add(runnable1)
+
+ verify(runnable1).run()
+ }
+
+ @Test
+ fun second_executeAllAndDestroy_noOp() {
+ underTest.executeAllAndDestroy()
+ underTest.add(runnable1)
+ reset(runnable1)
+
+ underTest.executeAllAndDestroy()
+
+ verifyZeroInteractions(runnable1)
+ }
+
+ @Test
+ fun executeAllAndClear_run_not_destroy() {
+ underTest.add(runnable1)
+ underTest.add(runnable2)
+
+ underTest.executeAllAndClear()
+
+ verify(runnable1).run()
+ verify(runnable2).run()
+ assertThat(underTest.isDestroyed).isFalse()
+ }
+
+ @Test
+ fun executeAllAndClear_not_destroy() {
+ underTest.executeAllAndClear()
+ underTest.add(runnable1)
+ reset(runnable1)
+
+ underTest.executeAllAndClear()
+
+ verify(runnable1).run()
+ }
+
+ @Test
+ fun remove_and_run_not_executed() {
+ underTest.add(runnable1)
+ underTest.add(runnable2)
+
+ underTest.remove(runnable1)
+ underTest.executeAllAndClear()
+
+ verifyZeroInteractions(runnable1)
+ verify(runnable2).run()
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/ViewCacheTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/ViewCacheTest.kt
new file mode 100644
index 0000000..bad21c9
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/ViewCacheTest.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.R
+import com.android.launcher3.util.ViewCache.CacheEntry
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ViewCacheTest {
+
+ private lateinit var underTest: ViewCache
+
+ private val context = InstrumentationRegistry.getInstrumentation().context
+ private val layoutId =
+ context.run { resources.getIdentifier("test_layout_appwidget_blue", "layout", packageName) }
+
+ @Before
+ fun setUp() {
+ underTest = ViewCache()
+ underTest.setCacheSize(layoutId, 5)
+ }
+
+ @Test
+ fun get_view_from_empty_cache() {
+ val view: View = underTest.getView(layoutId, context, null)
+
+ val cacheEntry = view.getTag(R.id.cache_entry_tag_id) as ViewCache.CacheEntry
+ assertThat(cacheEntry).isNotNull()
+ assertThat(cacheEntry.mMaxSize).isEqualTo(5)
+ assertThat(cacheEntry.mCurrentSize).isEqualTo(0)
+ assertThat(cacheEntry.mViews[0]).isNull()
+ }
+
+ @Test
+ fun recyclerView() {
+ val view: View = underTest.getView(layoutId, context, null)
+ val cacheEntry = view.getTag(R.id.cache_entry_tag_id) as ViewCache.CacheEntry
+
+ underTest.recycleView(layoutId, view)
+
+ assertThat(cacheEntry.mMaxSize).isEqualTo(5)
+ assertThat(cacheEntry.mCurrentSize).isEqualTo(1)
+ assertThat(cacheEntry.mViews[0]).isSameInstanceAs(view)
+ }
+
+ @Test
+ fun get_view_from_cache() {
+ val view: View = underTest.getView(layoutId, context, null)
+ underTest.recycleView(layoutId, view)
+
+ val newView = underTest.getView<View>(layoutId, context, null)
+
+ assertThat(view).isSameInstanceAs(newView)
+ }
+
+ @Test
+ fun change_tag_id_recyclerView_noOp() {
+ val view: View = underTest.getView(layoutId, context, null)
+ val cacheEntry = view.getTag(R.id.cache_entry_tag_id) as ViewCache.CacheEntry
+
+ view.setTag(R.id.cache_entry_tag_id, CacheEntry(3))
+ underTest.recycleView(layoutId, view)
+
+ assertThat(cacheEntry.mMaxSize).isEqualTo(5)
+ assertThat(cacheEntry.mCurrentSize).isEqualTo(0)
+ assertThat(cacheEntry.mViews[0]).isNull()
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/LauncherWidgetHolderTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/LauncherWidgetHolderTest.kt
new file mode 100644
index 0000000..1a659e2
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/LauncherWidgetHolderTest.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 governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.widget
+
+import androidx.test.annotation.UiThreadTest
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.BuildConfig.WIDGETS_ENABLED
+import com.android.launcher3.util.ActivityContextWrapper
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.android.launcher3.widget.LauncherWidgetHolder.FLAG_ACTIVITY_RESUMED
+import com.android.launcher3.widget.LauncherWidgetHolder.FLAG_ACTIVITY_STARTED
+import com.android.launcher3.widget.LauncherWidgetHolder.FLAG_STATE_IS_NORMAL
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertSame
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(LauncherMultivalentJUnit::class)
+class LauncherWidgetHolderTest {
+ private lateinit var widgetHolder: LauncherWidgetHolder
+
+ @Before
+ fun setUp() {
+ assertTrue(WIDGETS_ENABLED)
+ widgetHolder =
+ LauncherWidgetHolder(ActivityContextWrapper(getInstrumentation().targetContext)) {}
+ }
+
+ @After
+ fun tearDown() {
+ widgetHolder.destroy()
+ }
+
+ @Test
+ fun widget_holder_start_listening() {
+ val testView = mock(PendingAppWidgetHostView::class.java)
+ widgetHolder.mViews[0] = testView
+ widgetHolder.setListeningFlag(false)
+ assertFalse(widgetHolder.isListening)
+ widgetHolder.startListening()
+ widgetHolder.widgetHolderExecutor.submit {}.get()
+ getInstrumentation().waitForIdleSync()
+ assertTrue(widgetHolder.isListening)
+ verify(testView, times(1)).reInflate()
+ widgetHolder.clearWidgetViews()
+ }
+
+ @Test
+ fun holder_start_listening_after_activity_start() {
+ widgetHolder.setShouldListenFlag(FLAG_STATE_IS_NORMAL or FLAG_ACTIVITY_RESUMED, true)
+ widgetHolder.setActivityStarted(false)
+ widgetHolder.widgetHolderExecutor.submit {}.get()
+ assertFalse(widgetHolder.shouldListen(widgetHolder.mFlags.get()))
+ widgetHolder.setActivityStarted(true)
+ widgetHolder.widgetHolderExecutor.submit {}.get()
+ assertTrue(widgetHolder.shouldListen(widgetHolder.mFlags.get()))
+ }
+
+ @Test
+ fun holder_start_listening_after_activity_resume() {
+ widgetHolder.setShouldListenFlag(FLAG_STATE_IS_NORMAL or FLAG_ACTIVITY_STARTED, true)
+ widgetHolder.setActivityResumed(false)
+ widgetHolder.widgetHolderExecutor.submit {}.get()
+ assertFalse(widgetHolder.shouldListen(widgetHolder.mFlags.get()))
+ widgetHolder.setActivityResumed(true)
+ widgetHolder.widgetHolderExecutor.submit {}.get()
+ assertTrue(widgetHolder.shouldListen(widgetHolder.mFlags.get()))
+ }
+
+ @Test
+ fun holder_start_listening_after_state_normal() {
+ widgetHolder.setShouldListenFlag(FLAG_ACTIVITY_RESUMED or FLAG_ACTIVITY_STARTED, true)
+ widgetHolder.setStateIsNormal(false)
+ widgetHolder.widgetHolderExecutor.submit {}.get()
+ assertFalse(widgetHolder.shouldListen(widgetHolder.mFlags.get()))
+ widgetHolder.setStateIsNormal(true)
+ widgetHolder.widgetHolderExecutor.submit {}.get()
+ assertTrue(widgetHolder.shouldListen(widgetHolder.mFlags.get()))
+ }
+
+ @Test
+ @UiThreadTest
+ fun widget_holder_create_view() {
+ val mockProviderInfo = mock(LauncherAppWidgetProviderInfo::class.java)
+ doReturn(false).whenever(mockProviderInfo).isCustomWidget
+ assertEquals(0, widgetHolder.mViews.size())
+ widgetHolder.createView(APP_WIDGET_ID, mockProviderInfo)
+ assertEquals(1, widgetHolder.mViews.size())
+ assertEquals(APP_WIDGET_ID, widgetHolder.mViews.get(0).appWidgetId)
+ widgetHolder.deleteAppWidgetId(APP_WIDGET_ID)
+ assertEquals(0, widgetHolder.mViews.size())
+ }
+
+ @Test
+ fun holder_add_provider_change_listener() {
+ val listener = LauncherWidgetHolder.ProviderChangedListener {}
+ widgetHolder.addProviderChangeListener(listener)
+ getInstrumentation().waitForIdleSync()
+ assertEquals(1, widgetHolder.mProviderChangedListeners.size)
+ assertSame(widgetHolder.mProviderChangedListeners.first(), listener)
+ widgetHolder.removeProviderChangeListener(listener)
+ }
+
+ @Test
+ fun holder_remove_provider_change_listener() {
+ val listener = LauncherWidgetHolder.ProviderChangedListener {}
+ widgetHolder.addProviderChangeListener(listener)
+ widgetHolder.removeProviderChangeListener(listener)
+ getInstrumentation().waitForIdleSync()
+ assertEquals(0, widgetHolder.mProviderChangedListeners.size)
+ }
+
+ @Test
+ fun widget_holder_stop_listening() {
+ widgetHolder.setListeningFlag(true)
+ assertTrue(widgetHolder.isListening)
+ widgetHolder.stopListening()
+ widgetHolder.widgetHolderExecutor.submit {}.get()
+ assertFalse(widgetHolder.isListening)
+ }
+
+ companion object {
+ private const val APP_WIDGET_ID = 0
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/ListenableHostViewTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/ListenableHostViewTest.kt
new file mode 100644
index 0000000..6c71f36
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/ListenableHostViewTest.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.widget
+
+import android.content.Context
+import android.view.accessibility.AccessibilityNodeInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.util.ActivityContextWrapper
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ListenableHostViewTest {
+
+ private val context: Context
+ get() = ActivityContextWrapper(InstrumentationRegistry.getInstrumentation().targetContext)
+
+ @Test
+ fun updateAppWidget_notifiesListeners() {
+ val hostView = ListenableHostView(context)
+ var wasNotifiedOfUpdate = false
+ val updateListener = Runnable { wasNotifiedOfUpdate = true }
+ hostView.addUpdateListener(updateListener)
+ hostView.beginDeferringUpdates()
+ hostView.updateAppWidget(null)
+ Truth.assertThat(wasNotifiedOfUpdate).isTrue()
+ }
+
+ @Test
+ fun onInitializeAccessibilityNodeInfo_correctlySetsClassName() {
+ val hostView = ListenableHostView(context)
+ val nodeInfo = AccessibilityNodeInfo()
+ hostView.onInitializeAccessibilityNodeInfo(nodeInfo)
+ Truth.assertThat(nodeInfo.className).isEqualTo(LauncherAppWidgetHostView::class.java.name)
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/WidgetAddFlowHandlerTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/WidgetAddFlowHandlerTest.kt
new file mode 100644
index 0000000..242ec7c
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/WidgetAddFlowHandlerTest.kt
@@ -0,0 +1,107 @@
+/*
+ * 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.widget
+
+import android.content.Context
+import android.os.Bundle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.Launcher
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.LauncherAppWidgetInfo
+import com.android.launcher3.util.ActivityContextWrapper
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.any
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WidgetAddFlowHandlerTest {
+
+ private val context: Context
+ get() = ActivityContextWrapper(InstrumentationRegistry.getInstrumentation().targetContext)
+
+ private val providerInfo =
+ LauncherAppWidgetProviderInfo().apply {
+ configure = InstrumentationRegistry.getInstrumentation().componentName
+ }
+ private val appWidgetHolder: LauncherWidgetHolder = mock<LauncherWidgetHolder>()
+ private val launcher: Launcher =
+ mock<Launcher>().also { whenever(it.appWidgetHolder).thenReturn(appWidgetHolder) }
+ private val appWidgetInfo = LauncherAppWidgetInfo().apply { appWidgetId = 123 }
+ private val requestCode = 123
+ private val flowHandler = WidgetAddFlowHandler(providerInfo)
+
+ @Test
+ fun valuesShouldRemainTheSame_beforeAndAfter_parcelization() {
+ with(Bundle()) {
+ val testKey = "testKey"
+ putParcelable(testKey, flowHandler)
+ Truth.assertThat(getParcelable(testKey, WidgetAddFlowHandler::class.java))
+ .isEqualTo(flowHandler)
+ }
+ }
+
+ @Test
+ fun describeContents_shouldReturn_0() {
+ Truth.assertThat(flowHandler.describeContents()).isEqualTo(0)
+ }
+
+ @Test
+ fun startBindFlow_shouldCorrectly_startLauncherFlowBinding() {
+ flowHandler.startBindFlow(launcher, appWidgetInfo.appWidgetId, appWidgetInfo, requestCode)
+ verify(launcher).setWaitingForResult(any())
+ verify(appWidgetHolder)
+ .startBindFlow(launcher, appWidgetInfo.appWidgetId, providerInfo, requestCode)
+ }
+
+ @Test
+ fun startConfigActivityWithCustomAppWidgetId_shouldAskLauncherToStartConfigActivity() {
+ flowHandler.startConfigActivity(
+ launcher,
+ appWidgetInfo.appWidgetId,
+ ItemInfo(),
+ requestCode
+ )
+ verify(launcher).setWaitingForResult(any())
+ verify(appWidgetHolder)
+ .startConfigActivity(launcher, appWidgetInfo.appWidgetId, requestCode)
+ }
+
+ @Test
+ fun startConfigActivity_shouldAskLauncherToStartConfigActivity() {
+ flowHandler.startConfigActivity(launcher, appWidgetInfo, requestCode)
+ verify(launcher).setWaitingForResult(any())
+ verify(appWidgetHolder)
+ .startConfigActivity(launcher, appWidgetInfo.appWidgetId, requestCode)
+ }
+
+ @Test
+ fun needsConfigure_returnsTrue_ifFlagsAndProviderInfoDetermineSo() {
+ Truth.assertThat(flowHandler.needsConfigure()).isTrue()
+ }
+
+ @Test
+ fun getProviderInfo_returnCorrectProviderInfo() {
+ Truth.assertThat(flowHandler.getProviderInfo(context)).isSameInstanceAs(providerInfo)
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/WidgetManagerHelperTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/WidgetManagerHelperTest.kt
new file mode 100644
index 0000000..f1cfb79
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/WidgetManagerHelperTest.kt
@@ -0,0 +1,128 @@
+/*
+ * 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.widget
+
+import android.appwidget.AppWidgetManager
+import android.content.Context
+import android.content.pm.ActivityInfo
+import android.os.Bundle
+import android.os.Process
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.util.ActivityContextWrapper
+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 org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WidgetManagerHelperTest {
+
+ private val context: Context
+ get() = ActivityContextWrapper(InstrumentationRegistry.getInstrumentation().targetContext)
+
+ private val info =
+ LauncherAppWidgetProviderInfo().apply {
+ provider = InstrumentationRegistry.getInstrumentation().componentName
+ providerInfo =
+ mock(ActivityInfo::class.java).apply { applicationInfo = context.applicationInfo }
+ }
+
+ @Mock private lateinit var appWidgetManager: AppWidgetManager
+
+ private lateinit var underTest: WidgetManagerHelper
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ underTest = WidgetManagerHelper(context, appWidgetManager)
+ }
+
+ @Test
+ fun getAllProviders_returnsCorrectWidgetProviderInfo() {
+ val packageUserKey =
+ mock(PackageUserKey::class.java).apply {
+ mPackageName = context.packageName
+ mUser = Process.myUserHandle()
+ }
+ val desiredResult = listOf(info)
+ whenever(
+ appWidgetManager.getInstalledProvidersForPackage(
+ packageUserKey.mPackageName,
+ packageUserKey.mUser
+ )
+ )
+ .thenReturn(desiredResult)
+ Truth.assertThat(underTest.getAllProviders(packageUserKey)).isSameInstanceAs(desiredResult)
+ }
+
+ @Test
+ fun getLauncherAppWidgetInfo_returnsCorrectInfo_ifWidgetExists() {
+ val id = 123
+ whenever(appWidgetManager.getAppWidgetInfo(id)).thenReturn(info)
+ val componentName = InstrumentationRegistry.getInstrumentation().componentName
+ Truth.assertThat(underTest.getLauncherAppWidgetInfo(id, componentName))
+ .isSameInstanceAs(info)
+ }
+
+ @Test
+ fun bindAppWidgetIdIfAllowed_correctly_forwardsBindCommandToAppWidgetManager() {
+ val id = 124
+ val options = Bundle()
+ underTest.bindAppWidgetIdIfAllowed(id, info, options)
+ verify(appWidgetManager).bindAppWidgetIdIfAllowed(id, info.profile, info.provider, options)
+ }
+
+ @Test
+ fun findProvider_returnsNull_ifNoProviderExists() {
+ val info =
+ underTest.getLauncherAppWidgetInfo(
+ 1,
+ InstrumentationRegistry.getInstrumentation().componentName
+ )
+ Truth.assertThat(info).isNull()
+ }
+
+ @Test
+ fun isAppWidgetRestored_returnsTrue_ifWidgetIsRestored() {
+ val id = 126
+ whenever(appWidgetManager.getAppWidgetOptions(id))
+ .thenReturn(
+ Bundle().apply {
+ putBoolean(WidgetManagerHelper.WIDGET_OPTION_RESTORE_COMPLETED, true)
+ }
+ )
+ Truth.assertThat(underTest.isAppWidgetRestored(id)).isTrue()
+ }
+
+ @Test
+ fun loadGeneratedPreview_returnsWidgetPreview_fromAppWidgetManager() {
+ val widgetCategory = 130
+ with(info) {
+ underTest.loadGeneratedPreview(this, widgetCategory)
+ verify(appWidgetManager).getWidgetPreview(provider, profile, widgetCategory)
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java b/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
index 57117cb..430e496 100644
--- a/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
+++ b/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
@@ -22,8 +22,6 @@
import static com.android.launcher3.allapps.UserProfileManager.STATE_ENABLED;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-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.mockito.ArgumentMatchers.any;
@@ -202,7 +200,6 @@
}
@Test
- @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/339109319
public void openPrivateSpaceSettings_triggersCorrectIntent() {
Intent expectedIntent = ApiWrapper.INSTANCE.get(mContext).getPrivateSpaceSettingsIntent();
ArgumentCaptor<Intent> acIntent = ArgumentCaptor.forClass(Intent.class);
diff --git a/tests/src/com/android/launcher3/celllayout/TaplReorderWidgetsTest.java b/tests/src/com/android/launcher3/celllayout/TaplReorderWidgetsTest.java
deleted file mode 100644
index 28a1325..0000000
--- a/tests/src/com/android/launcher3/celllayout/TaplReorderWidgetsTest.java
+++ /dev/null
@@ -1,312 +0,0 @@
-/*
- * Copyright (C) 2022 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.celllayout;
-
-import static android.platform.uiautomator_helpers.DeviceHelpers.getContext;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.graphics.Point;
-import android.net.Uri;
-import android.util.Log;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.MultipageCellLayout;
-import com.android.launcher3.celllayout.board.CellLayoutBoard;
-import com.android.launcher3.celllayout.board.TestWorkspaceBuilder;
-import com.android.launcher3.celllayout.board.WidgetRect;
-import com.android.launcher3.tapl.Widget;
-import com.android.launcher3.tapl.WidgetResizeFrame;
-import com.android.launcher3.ui.AbstractLauncherUiTest;
-import com.android.launcher3.util.ModelTestExtensions;
-import com.android.launcher3.util.rule.ShellCommandRule;
-
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class TaplReorderWidgetsTest extends AbstractLauncherUiTest<Launcher> {
-
- @Rule
- public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
-
- private static final String TAG = TaplReorderWidgetsTest.class.getSimpleName();
-
- private static final List<String> FOLDABLE_GRIDS = List.of("normal", "practical", "reasonable");
-
- TestWorkspaceBuilder mWorkspaceBuilder;
-
- @Before
- public void setup() throws Throwable {
- mWorkspaceBuilder = new TestWorkspaceBuilder(mTargetContext);
- super.setUp();
- }
-
- @After
- public void tearDown() {
- ModelTestExtensions.INSTANCE.clearModelDb(
- LauncherAppState.getInstance(getContext()).getModel()
- );
- }
-
- /**
- * Validate if the given board represent the current CellLayout
- **/
- private boolean validateBoard(List<CellLayoutBoard> testBoards) {
- ArrayList<CellLayoutBoard> workspaceBoards = workspaceToBoards();
- if (workspaceBoards.size() < testBoards.size()) {
- return false;
- }
- for (int i = 0; i < testBoards.size(); i++) {
- if (testBoards.get(i).compareTo(workspaceBoards.get(i)) != 0) {
- return false;
- }
- }
- return true;
- }
-
- private FavoriteItemsTransaction buildWorkspaceFromBoards(List<CellLayoutBoard> boards,
- FavoriteItemsTransaction transaction) {
- for (int i = 0; i < boards.size(); i++) {
- CellLayoutBoard board = boards.get(i);
- mWorkspaceBuilder.buildFromBoard(board, transaction, i);
- }
- return transaction;
- }
-
- private void printCurrentWorkspace() {
- InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(mTargetContext);
- ArrayList<CellLayoutBoard> boards = workspaceToBoards();
- for (int i = 0; i < boards.size(); i++) {
- Log.d(TAG, "Screen number " + i);
- Log.d(TAG, ".\n" + boards.get(i).toString(idp.numColumns, idp.numRows));
- }
- }
-
- private ArrayList<CellLayoutBoard> workspaceToBoards() {
- return getFromLauncher(CellLayoutTestUtils::workspaceToBoards);
- }
-
- private WidgetRect getWidgetClosestTo(Point point) {
- ArrayList<CellLayoutBoard> workspaceBoards = workspaceToBoards();
- int maxDistance = 9999;
- WidgetRect bestRect = null;
- for (int i = 0; i < workspaceBoards.get(0).getWidgets().size(); i++) {
- WidgetRect widget = workspaceBoards.get(0).getWidgets().get(i);
- if (widget.getCellX() == 0 && widget.getCellY() == 0) {
- continue;
- }
- int distance = Math.abs(point.x - widget.getCellX())
- + Math.abs(point.y - widget.getCellY());
- if (distance == 0) {
- break;
- }
- if (distance < maxDistance) {
- maxDistance = distance;
- bestRect = widget;
- }
- }
- return bestRect;
- }
-
- /**
- * This function might be odd, its function is to select a widget and leave it in its place.
- * The idea is to make the test broader and also test after a widgets resized because the
- * underlying code does different things in that case
- */
- private void triggerWidgetResize(ReorderTestCase testCase) {
- WidgetRect widgetRect = getWidgetClosestTo(testCase.moveMainTo);
- if (widgetRect == null) {
- // Some test doesn't have a widget in the final position, in those cases we will ignore
- // them
- return;
- }
- Widget widget = mLauncher.getWorkspace().getWidgetAtCell(widgetRect.getCellX(),
- widgetRect.getCellY());
- WidgetResizeFrame resizeFrame = widget.dragWidgetToWorkspace(widgetRect.getCellX(),
- widgetRect.getCellY(), widgetRect.getSpanX(), widgetRect.getSpanY());
- resizeFrame.dismiss();
- }
-
- private void runTestCase(ReorderTestCase testCase) {
- WidgetRect mainWidgetCellPos = CellLayoutBoard.getMainFromList(
- testCase.mStart);
-
- FavoriteItemsTransaction transaction =
- new FavoriteItemsTransaction(mTargetContext);
- transaction = buildWorkspaceFromBoards(testCase.mStart, transaction);
- transaction.commit();
- mLauncher.waitForLauncherInitialized();
- // resetLoaderState triggers the launcher to start loading the workspace which allows
- // waitForLauncherCondition to wait for that condition, otherwise the condition would
- // always be true and it wouldn't wait for the changes to be applied.
- waitForLauncherCondition("Workspace didn't finish loading", l -> !l.isWorkspaceLoading());
-
- triggerWidgetResize(testCase);
-
- Widget widget = mLauncher.getWorkspace().getWidgetAtCell(mainWidgetCellPos.getCellX(),
- mainWidgetCellPos.getCellY());
- assertNotNull(widget);
- WidgetResizeFrame resizeFrame = widget.dragWidgetToWorkspace(testCase.moveMainTo.x,
- testCase.moveMainTo.y, mainWidgetCellPos.getSpanX(), mainWidgetCellPos.getSpanY());
- resizeFrame.dismiss();
-
- boolean isValid = false;
- for (List<CellLayoutBoard> boards : testCase.mEnd) {
- isValid |= validateBoard(boards);
- if (isValid) break;
- }
- printCurrentWorkspace();
- assertTrue("Non of the valid boards match with the current state", isValid);
- }
-
- /**
- * Run only the test define for the current grid size if such test exist
- *
- * @param testCaseMap map containing all the tests per grid size (Point)
- */
- private boolean runTestCaseMap(Map<Point, ReorderTestCase> testCaseMap, String testName) {
- Point iconGridDimensions = mLauncher.getWorkspace().getIconGridDimensions();
- Log.d(TAG, "Running test " + testName + " for grid " + iconGridDimensions);
- if (!testCaseMap.containsKey(iconGridDimensions)) {
- Log.d(TAG, "The test " + testName + " doesn't support " + iconGridDimensions
- + " grid layout");
- return false;
- }
- runTestCase(testCaseMap.get(iconGridDimensions));
-
- return true;
- }
-
- private void runTestCaseMapForAllGrids(Map<Point, ReorderTestCase> testCaseMap,
- String testName) {
- boolean runAtLeastOnce = false;
- for (String grid : FOLDABLE_GRIDS) {
- applyGridOption(grid);
- mLauncher.waitForLauncherInitialized();
- runAtLeastOnce |= runTestCaseMap(testCaseMap, testName);
- }
- Assume.assumeTrue("None of the grids are supported", runAtLeastOnce);
- }
-
- private void applyGridOption(Object argValue) {
- String testProviderAuthority = mTargetContext.getPackageName() + ".grid_control";
- Uri gridUri = new Uri.Builder()
- .scheme(ContentResolver.SCHEME_CONTENT)
- .authority(testProviderAuthority)
- .appendPath("default_grid")
- .build();
- ContentValues values = new ContentValues();
- values.putObject("name", argValue);
- Assert.assertEquals(1,
- mTargetContext.getContentResolver().update(gridUri, values, null, null));
- }
-
- @Test
- public void simpleReorder() throws Exception {
- runTestCaseMap(getTestMap("ReorderWidgets/simple_reorder_case"),
- "push_reorder_case");
- }
-
- @Test
- public void pushTest() throws Exception {
- runTestCaseMap(getTestMap("ReorderWidgets/push_reorder_case"),
- "push_reorder_case");
- }
-
- @Test
- public void fullReorder() throws Exception {
- runTestCaseMap(getTestMap("ReorderWidgets/full_reorder_case"),
- "full_reorder_case");
- }
-
- @Test
- public void moveOutReorder() throws Exception {
- runTestCaseMap(getTestMap("ReorderWidgets/move_out_reorder_case"),
- "move_out_reorder_case");
- }
-
- @Test
- public void multipleCellLayoutsSimpleReorder() throws Exception {
- Assume.assumeTrue("Test doesn't support foldables", getFromLauncher(
- l -> l.getWorkspace().getScreenWithId(0) instanceof MultipageCellLayout));
- runTestCaseMapForAllGrids(getTestMap("ReorderWidgets/multiple_cell_layouts_simple_reorder"),
- "multiple_cell_layouts_simple_reorder");
- }
-
- @Test
- public void multipleCellLayoutsNoSpaceReorder() throws Exception {
- Assume.assumeTrue("Test doesn't support foldables", getFromLauncher(
- l -> l.getWorkspace().getScreenWithId(0) instanceof MultipageCellLayout));
- runTestCaseMapForAllGrids(
- getTestMap("ReorderWidgets/multiple_cell_layouts_no_space_reorder"),
- "multiple_cell_layouts_no_space_reorder");
- }
-
- @Test
- public void multipleCellLayoutsReorderToOtherSide() throws Exception {
- Assume.assumeTrue("Test doesn't support foldables", getFromLauncher(
- l -> l.getWorkspace().getScreenWithId(0) instanceof MultipageCellLayout));
- runTestCaseMapForAllGrids(
- getTestMap("ReorderWidgets/multiple_cell_layouts_reorder_other_side"),
- "multiple_cell_layouts_reorder_other_side");
- }
-
- private void addTestCase(Iterator<CellLayoutTestCaseReader.TestSection> sections,
- Map<Point, ReorderTestCase> testCaseMap) {
- CellLayoutTestCaseReader.Board startBoard =
- ((CellLayoutTestCaseReader.Board) sections.next());
- CellLayoutTestCaseReader.Arguments point =
- ((CellLayoutTestCaseReader.Arguments) sections.next());
- CellLayoutTestCaseReader.Board endBoard =
- ((CellLayoutTestCaseReader.Board) sections.next());
- Point moveTo = new Point(Integer.parseInt(point.arguments[0]),
- Integer.parseInt(point.arguments[1]));
- testCaseMap.put(endBoard.gridSize,
- new ReorderTestCase(startBoard.board, moveTo, endBoard.board));
- }
-
- private Map<Point, ReorderTestCase> getTestMap(String testPath) throws IOException {
- Map<Point, ReorderTestCase> testCaseMap = new HashMap<>();
- Iterator<CellLayoutTestCaseReader.TestSection> iterableSection =
- CellLayoutTestCaseReader.readFromFile(testPath).parse().iterator();
- while (iterableSection.hasNext()) {
- addTestCase(iterableSection, testCaseMap);
- }
- return testCaseMap;
- }
-}
diff --git a/tests/src/com/android/launcher3/celllayout/integrationtest/TestUtils.kt b/tests/src/com/android/launcher3/celllayout/integrationtest/TestUtils.kt
new file mode 100644
index 0000000..4cecb5a
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/integrationtest/TestUtils.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.celllayout.integrationtest
+
+import android.graphics.Point
+import android.graphics.Rect
+import android.view.View
+import android.view.ViewGroup
+import com.android.launcher3.CellLayout
+import com.android.launcher3.Workspace
+import com.android.launcher3.util.CellAndSpan
+import com.android.launcher3.widget.LauncherAppWidgetHostView
+
+object TestUtils {
+ fun <T> searchChildren(viewGroup: ViewGroup, type: Class<T>): T? where T : View {
+ for (i in 0..<viewGroup.childCount) {
+ val child = viewGroup.getChildAt(i)
+ if (type.isInstance(child)) {
+ return type.cast(child)
+ }
+ if (child is ViewGroup) {
+ val result = searchChildren(child, type)
+ if (result != null) {
+ return result
+ }
+ }
+ }
+ return null
+ }
+
+ fun getWidgetAtCell(
+ workspace: Workspace<*>,
+ cellX: Int,
+ cellY: Int
+ ): LauncherAppWidgetHostView {
+ val view =
+ (workspace.getPageAt(workspace.currentPage) as CellLayout).getChildAt(cellX, cellY)
+ assert(view != null) { "There is no view at $cellX , $cellY" }
+ assert(view is LauncherAppWidgetHostView) { "The view at $cellX , $cellY is not a widget" }
+ return view as LauncherAppWidgetHostView
+ }
+
+ fun getCellTopLeftRelativeToCellLayout(
+ workspace: Workspace<*>,
+ cellAndSpan: CellAndSpan
+ ): Point {
+ val target = Rect()
+ val cellLayout = workspace.getPageAt(workspace.currentPage) as CellLayout
+ cellLayout.cellToRect(
+ cellAndSpan.cellX,
+ cellAndSpan.cellY,
+ cellAndSpan.spanX,
+ cellAndSpan.spanY,
+ target
+ )
+ return Point(target.left, target.top)
+ }
+}
diff --git a/tests/src/com/android/launcher3/celllayout/integrationtest/events/EventsRule.kt b/tests/src/com/android/launcher3/celllayout/integrationtest/events/EventsRule.kt
new file mode 100644
index 0000000..fb61ced
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/integrationtest/events/EventsRule.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.celllayout.integrationtest.events
+
+import android.content.Context
+import com.android.launcher3.debug.TestEvent
+import com.android.launcher3.debug.TestEventEmitter
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Rule to create EventWaiters to wait for events that happens on the Launcher. For reference look
+ * at [TestEvent] for existing events.
+ *
+ * Waiting for event should be used to prevent race conditions, it provides a more precise way of
+ * waiting for events compared to [AbstractLauncherUiTest#waitForLauncherCondition].
+ *
+ * This class overrides the [TestEventEmitter] with [TestEventsEmitterImplementation] and makes sure
+ * to return the [TestEventEmitter] to the previous value when finished.
+ */
+class EventsRule(val context: Context) : TestRule {
+
+ private var prevEventEmitter: TestEventEmitter? = null
+
+ private val eventEmitter = TestEventsEmitterImplementation()
+
+ override fun apply(base: Statement, description: Description?): Statement {
+ return object : Statement() {
+ override fun evaluate() {
+ beforeTest()
+ base.evaluate()
+ afterTest()
+ }
+ }
+ }
+
+ fun createEventWaiter(expectedEvent: TestEvent): EventWaiter {
+ return eventEmitter.createEventWaiter(expectedEvent)
+ }
+
+ private fun beforeTest() {
+ prevEventEmitter = TestEventEmitter.INSTANCE.get(context)
+ TestEventEmitter.INSTANCE.initializeForTesting(eventEmitter)
+ }
+
+ private fun afterTest() {
+ TestEventEmitter.INSTANCE.initializeForTesting(prevEventEmitter)
+ }
+}
diff --git a/tests/src/com/android/launcher3/celllayout/integrationtest/events/TestEventsEmitterImplementation.kt b/tests/src/com/android/launcher3/celllayout/integrationtest/events/TestEventsEmitterImplementation.kt
new file mode 100644
index 0000000..365ad4b
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/integrationtest/events/TestEventsEmitterImplementation.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.celllayout.integrationtest.events
+
+import android.util.Log
+import com.android.launcher3.debug.TestEvent
+import com.android.launcher3.debug.TestEventEmitter
+import java.util.concurrent.TimeUnit
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeoutOrNull
+
+enum class EventStatus() {
+ SUCCESS,
+ FAILURE,
+ TIMEOUT,
+}
+
+class EventWaiter(val eventToWait: TestEvent) {
+ private val deferrable = CompletableDeferred<EventStatus>()
+
+ companion object {
+ private const val TAG = "EventWaiter"
+ }
+
+ fun waitForSignal(timeout: Long = TimeUnit.SECONDS.toMillis(10)) = runBlocking {
+ var status = withTimeoutOrNull(timeout) { deferrable.await() }
+ if (status == null) {
+ status = EventStatus.TIMEOUT
+ }
+ if (status != EventStatus.SUCCESS) {
+ throw Exception("Failure waiting for event $eventToWait, failure = $status")
+ }
+ }
+
+ fun terminate() {
+ deferrable.complete(EventStatus.SUCCESS)
+ }
+}
+
+class TestEventsEmitterImplementation() : TestEventEmitter {
+ companion object {
+ private const val TAG = "TestEvents"
+ }
+
+ private val expectedEvents: ArrayDeque<EventWaiter> = ArrayDeque()
+
+ fun createEventWaiter(expectedEvent: TestEvent): EventWaiter {
+ val eventWaiter = EventWaiter(expectedEvent)
+ expectedEvents.add(eventWaiter)
+ return eventWaiter
+ }
+
+ private fun clearQueue() {
+ expectedEvents.clear()
+ }
+
+ override fun sendEvent(event: TestEvent) {
+ Log.d(TAG, "Signal received $event")
+ Log.d(TAG, "Total expected events ${expectedEvents.size}")
+ if (expectedEvents.isEmpty()) return
+ val eventWaiter = expectedEvents.last()
+ if (eventWaiter.eventToWait == event) {
+ Log.d(TAG, "Removing $event")
+ expectedEvents.removeLast()
+ eventWaiter.terminate()
+ } else {
+ Log.d(TAG, "Not matching $event")
+ }
+ }
+
+ override fun close() {
+ clearQueue()
+ }
+}
diff --git a/tests/src/com/android/launcher3/dragging/TaplDragTest.java b/tests/src/com/android/launcher3/dragging/TaplDragTest.java
index 41abcf8..76c1948 100644
--- a/tests/src/com/android/launcher3/dragging/TaplDragTest.java
+++ b/tests/src/com/android/launcher3/dragging/TaplDragTest.java
@@ -65,6 +65,7 @@
@Test
@PortraitLandscape
@PlatinumTest(focusArea = "launcher")
+ @ScreenRecordRule.ScreenRecord // b/353600888
public void testDragToFolder() {
// TODO: add the use case to drag an icon to an existing folder. Currently it either fails
// on tablets or phones due to difference in resolution.
@@ -97,6 +98,7 @@
* icon left.
*/
@Test
+ @ScreenRecordRule.ScreenRecord // b/353600888
public void testDragOutOfFolder() {
final HomeAppIcon playStoreIcon = createShortcutIfNotExist(STORE_APP_NAME, 0, 1);
final HomeAppIcon photosIcon = createShortcutInCenterIfNotExist(PHOTOS_APP_NAME);
@@ -174,13 +176,13 @@
public void testDragAndCancelAppIcon() {
final HomeAppIcon homeAppIcon = createShortcutInCenterIfNotExist(GMAIL_APP_NAME);
Point positionBeforeDrag =
- mLauncher.getWorkspace().getWorkspaceIconsPositions().get(GMAIL_APP_NAME);
+ mLauncher.getWorkspace().getWorkspaceIconPosition(GMAIL_APP_NAME);
assertNotNull("App not found in Workspace before dragging.", positionBeforeDrag);
mLauncher.getWorkspace().dragAndCancelAppIcon(homeAppIcon);
Point positionAfterDrag =
- mLauncher.getWorkspace().getWorkspaceIconsPositions().get(GMAIL_APP_NAME);
+ mLauncher.getWorkspace().getWorkspaceIconPosition(GMAIL_APP_NAME);
assertNotNull("App not found in Workspace after dragging.", positionAfterDrag);
assertEquals("App not returned to same position in Workspace after drag & cancel",
positionBeforeDrag, positionAfterDrag);
diff --git a/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
index 405dae7..907aa50 100644
--- a/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
+++ b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
@@ -46,7 +46,6 @@
import java.io.IOException;
import java.util.Arrays;
-import java.util.Map;
/**
* Test runs in Out of process (Oop) and In process (Ipc)
@@ -60,6 +59,7 @@
*/
@Test
@PortraitLandscape
+ @ScreenRecordRule.ScreenRecord // b/349439239
public void testDeleteFromWorkspace() {
for (String appName : new String[]{GMAIL_APP_NAME, STORE_APP_NAME, TEST_APP_NAME}) {
final HomeAppIcon homeAppIcon = createShortcutInCenterIfNotExist(appName);
@@ -155,18 +155,14 @@
createShortcutIfNotExist(appNames[i], gridPositions[i]);
}
- Map<String, Point> initialPositions =
- mLauncher.getWorkspace().getWorkspaceIconsPositions();
- assertThat(initialPositions.keySet()).containsAtLeastElementsIn(appNames);
+ Point initialPosition =
+ mLauncher.getWorkspace().getWorkspaceIconPosition(DUMMY_APP_NAME);
+ assertThat(initialPosition).isNotNull();
final Workspace workspace = mLauncher.getWorkspace().getWorkspaceAppIcon(
DUMMY_APP_NAME).uninstall();
workspace.verifyWorkspaceAppIconIsGone(
DUMMY_APP_NAME + " was expected to disappear after uninstall.", DUMMY_APP_NAME);
-
- Log.d(UIOBJECT_STALE_ELEMENT, "second getWorkspaceIconsPositions()");
- Map<String, Point> finalPositions = workspace.getWorkspaceIconsPositions();
- assertThat(finalPositions).doesNotContainKey(DUMMY_APP_NAME);
} finally {
TestUtil.uninstallDummyApp();
}
@@ -177,8 +173,6 @@
*/
@Test
@PortraitLandscape
- @ScreenRecordRule.ScreenRecord // b/338869019
- @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/338869019
public void testAddDeleteShortcutOnHotseat() {
mLauncher.getWorkspace()
.deleteAppIcon(mLauncher.getWorkspace().getHotseatAppIcon(0))
diff --git a/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt b/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt
new file mode 100644
index 0000000..d9af07a
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt
@@ -0,0 +1,235 @@
+/*
+ * 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.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.LauncherActivityInfo
+import android.content.pm.LauncherApps
+import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.AppFilter
+import com.android.launcher3.Flags.FLAG_ENABLE_PRIVATE_SPACE
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherSettings
+import com.android.launcher3.icons.IconCache
+import com.android.launcher3.model.PackageUpdatedTask.OP_ADD
+import com.android.launcher3.model.PackageUpdatedTask.OP_REMOVE
+import com.android.launcher3.model.PackageUpdatedTask.OP_SUSPEND
+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.model.PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE
+import com.android.launcher3.model.data.AppInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.util.Executors
+import com.android.launcher3.util.LauncherModelHelper
+import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
+import com.android.launcher3.util.TestUtil
+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.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@RunWith(AndroidJUnit4::class)
+class PackageUpdatedTaskTest {
+
+ @get:Rule val setFlagsRule = SetFlagsRule()
+
+ private val mUser = UserHandle(0)
+ private val mDataModel: BgDataModel = BgDataModel()
+ private val mLauncherModelHelper = LauncherModelHelper()
+ private val mContext: SandboxModelContext = spy(mLauncherModelHelper.sandboxContext)
+ private val mAppState: LauncherAppState = spy(LauncherAppState.getInstance(mContext))
+
+ private val expectedPackage = "Test.Package"
+ private val expectedComponent = ComponentName(expectedPackage, "TestClass")
+ private val expectedActivityInfo: LauncherActivityInfo = mock<LauncherActivityInfo>()
+ private val expectedWorkspaceItem = spy(WorkspaceItemInfo())
+
+ private val mockIconCache: IconCache = mock()
+ private val mockTaskController: ModelTaskController = mock<ModelTaskController>()
+ private val mockAppFilter: AppFilter = mock<AppFilter>()
+ private val mockApplicationInfo: ApplicationInfo = mock<ApplicationInfo>()
+ private val mockActivityInfo: ActivityInfo = mock<ActivityInfo>()
+
+ private lateinit var mAllAppsList: AllAppsList
+
+ @Before
+ fun setup() {
+ mAllAppsList = spy(AllAppsList(mockIconCache, mockAppFilter))
+ mLauncherModelHelper.sandboxContext.spyService(LauncherApps::class.java).apply {
+ whenever(getActivityList(expectedPackage, mUser))
+ .thenReturn(listOf(expectedActivityInfo))
+ }
+ whenever(mAppState.iconCache).thenReturn(mockIconCache)
+ whenever(mockTaskController.app).thenReturn(mAppState)
+ whenever(mockAppFilter.shouldShowApp(expectedComponent)).thenReturn(true)
+ mockApplicationInfo.apply {
+ uid = 1
+ isArchived = false
+ }
+ mockActivityInfo.isArchived = false
+ expectedActivityInfo.apply {
+ whenever(applicationInfo).thenReturn(mockApplicationInfo)
+ whenever(activityInfo).thenReturn(mockActivityInfo)
+ whenever(componentName).thenReturn(expectedComponent)
+ }
+ expectedWorkspaceItem.apply {
+ itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+ container = LauncherSettings.Favorites.CONTAINER_DESKTOP
+ user = mUser
+ whenever(targetPackage).thenReturn(expectedPackage)
+ whenever(targetComponent).thenReturn(expectedComponent)
+ }
+ }
+
+ @After
+ fun tearDown() {
+ mLauncherModelHelper.destroy()
+ }
+
+ @Test
+ fun `OP_ADD triggers model callbacks and adds new items to AllAppsList`() {
+ // Given
+ val taskUnderTest = PackageUpdatedTask(OP_ADD, mUser, expectedPackage)
+ // When
+ mDataModel.addItem(mContext, expectedWorkspaceItem, true)
+ TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
+ taskUnderTest.execute(mockTaskController, mDataModel, mAllAppsList)
+ }
+ mLauncherModelHelper.loadModelSync()
+ // Then
+ verify(mockIconCache).updateIconsForPkg(expectedPackage, mUser)
+ verify(mAllAppsList).addPackage(mContext, expectedPackage, mUser)
+ verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWorkspaceItem))
+ verify(mockTaskController).bindUpdatedWidgets(mDataModel)
+ assertThat(mAllAppsList.data.firstOrNull()?.componentName)
+ .isEqualTo(AppInfo(mContext, expectedActivityInfo, mUser).componentName)
+ }
+
+ @Test
+ fun `OP_UPDATE triggers model callbacks and updates items in AllAppsList`() {
+ // Given
+ val taskUnderTest = PackageUpdatedTask(OP_UPDATE, mUser, expectedPackage)
+ // When
+ mDataModel.addItem(mContext, expectedWorkspaceItem, true)
+ TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
+ taskUnderTest.execute(mockTaskController, mDataModel, mAllAppsList)
+ }
+ mLauncherModelHelper.loadModelSync()
+ // Then
+ verify(mockIconCache).updateIconsForPkg(expectedPackage, mUser)
+ verify(mAllAppsList).updatePackage(mContext, expectedPackage, mUser)
+ verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWorkspaceItem))
+ assertThat(mAllAppsList.data.firstOrNull()?.componentName)
+ .isEqualTo(AppInfo(mContext, expectedActivityInfo, mUser).componentName)
+ }
+
+ @Test
+ fun `OP_REMOVE triggers model callbacks and removes packages and icons`() {
+ // Given
+ val taskUnderTest = PackageUpdatedTask(OP_REMOVE, mUser, expectedPackage)
+ // When
+ mDataModel.addItem(mContext, expectedWorkspaceItem, true)
+ TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
+ taskUnderTest.execute(mockTaskController, mDataModel, mAllAppsList)
+ }
+ mLauncherModelHelper.loadModelSync()
+ // Then
+ verify(mockIconCache).removeIconsForPkg(expectedPackage, mUser)
+ verify(mAllAppsList).removePackage(expectedPackage, mUser)
+ verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWorkspaceItem))
+ assertThat(mAllAppsList.data).isEmpty()
+ }
+
+ @Test
+ fun `OP_UNAVAILABLE triggers model callbacks and removes package from AllAppsList`() {
+ // Given
+ val taskUnderTest = PackageUpdatedTask(OP_UNAVAILABLE, mUser, expectedPackage)
+ // When
+ mDataModel.addItem(mContext, expectedWorkspaceItem, true)
+ TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
+ taskUnderTest.execute(mockTaskController, mDataModel, mAllAppsList)
+ }
+ mLauncherModelHelper.loadModelSync()
+ // Then
+ verify(mAllAppsList).removePackage(expectedPackage, mUser)
+ verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWorkspaceItem))
+ assertThat(mAllAppsList.data).isEmpty()
+ }
+
+ @Test
+ fun `OP_SUSPEND triggers model callbacks and updates flags in AllAppsList`() {
+ // Given
+ val taskUnderTest = PackageUpdatedTask(OP_SUSPEND, mUser, expectedPackage)
+ // When
+ mDataModel.addItem(mContext, expectedWorkspaceItem, true)
+ mAllAppsList.add(AppInfo(mContext, expectedActivityInfo, mUser), expectedActivityInfo)
+ TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
+ taskUnderTest.execute(mockTaskController, mDataModel, mAllAppsList)
+ }
+ mLauncherModelHelper.loadModelSync()
+ // Then
+ verify(mAllAppsList).updateDisabledFlags(any(), any())
+ verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWorkspaceItem))
+ assertThat(mAllAppsList.getAndResetChangeFlag()).isTrue()
+ }
+
+ @Test
+ fun `OP_UNSUSPEND triggers no callbacks when app not suspended`() {
+ // Given
+ val taskUnderTest = PackageUpdatedTask(OP_UNSUSPEND, mUser, expectedPackage)
+ // When
+ mDataModel.addItem(mContext, expectedWorkspaceItem, true)
+ TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
+ taskUnderTest.execute(mockTaskController, mDataModel, mAllAppsList)
+ }
+ mLauncherModelHelper.loadModelSync()
+ // Then
+ verify(mAllAppsList).updateDisabledFlags(any(), any())
+ verify(mockTaskController).bindUpdatedWorkspaceItems(emptyList())
+ assertThat(mAllAppsList.getAndResetChangeFlag()).isFalse()
+ }
+
+ @EnableFlags(FLAG_ENABLE_PRIVATE_SPACE)
+ @Test
+ fun `OP_USER_AVAILABILITY_CHANGE triggers no callbacks if current user not work or private`() {
+ // Given
+ val taskUnderTest = PackageUpdatedTask(OP_USER_AVAILABILITY_CHANGE, mUser, expectedPackage)
+ // When
+ mDataModel.addItem(mContext, expectedWorkspaceItem, true)
+ TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
+ taskUnderTest.execute(mockTaskController, mDataModel, mAllAppsList)
+ }
+ mLauncherModelHelper.loadModelSync()
+ // Then
+ verify(mAllAppsList).updateDisabledFlags(any(), any())
+ verify(mockTaskController).bindUpdatedWorkspaceItems(emptyList())
+ assertThat(mAllAppsList.data).isEmpty()
+ }
+}
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 6e01f9e..c926ba9 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -22,6 +22,7 @@
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 com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -238,13 +239,13 @@
protected void clearPackageData(String pkg) throws IOException, InterruptedException {
final CountDownLatch count = new CountDownLatch(2);
final SimpleBroadcastReceiver broadcastReceiver =
- new SimpleBroadcastReceiver(i -> count.countDown());
+ new SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, i -> count.countDown());
// We OK to make binder calls on main thread in test.
- broadcastReceiver.registerPkgActionsSync(mTargetContext, pkg,
+ broadcastReceiver.registerPkgActions(mTargetContext, pkg,
Intent.ACTION_PACKAGE_RESTARTED, Intent.ACTION_PACKAGE_DATA_CLEARED);
mDevice.executeShellCommand("pm clear " + pkg);
- assertTrue(pkg + " didn't restart", count.await(10, TimeUnit.SECONDS));
+ assertTrue(pkg + " didn't restart", count.await(20, TimeUnit.SECONDS));
mTargetContext.unregisterReceiver(broadcastReceiver);
}
diff --git a/tests/src/com/android/launcher3/ui/workspace/TaplThemeIconsTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplThemeIconsTest.java
index 5dee322..a148744 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TaplThemeIconsTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TaplThemeIconsTest.java
@@ -40,6 +40,7 @@
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,6 +116,7 @@
@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/ui/workspace/TaplTwoPanelWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
index bc26c00..20c5a25 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
@@ -20,8 +20,6 @@
import static com.android.launcher3.util.TestConstants.AppNames.MAPS_APP_NAME;
import static com.android.launcher3.util.TestConstants.AppNames.MESSAGES_APP_NAME;
import static com.android.launcher3.util.TestConstants.AppNames.STORE_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,7 +38,6 @@
import com.android.launcher3.util.LauncherLayoutBuilder;
import com.android.launcher3.util.TestUtil;
import com.android.launcher3.util.rule.ScreenRecordRule;
-import com.android.launcher3.util.rule.TestStabilityRule;
import org.junit.After;
import org.junit.Before;
@@ -113,8 +110,6 @@
@Test
@PortraitLandscape
- @ScreenRecordRule.ScreenRecord // b/329935119
- @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/329935119
public void testSinglePageDragIconWhenMultiplePageScrollingIsPossible() {
Workspace workspace = mLauncher.getWorkspace();
@@ -169,6 +164,7 @@
@Test
@PortraitLandscape
+ @ScreenRecordRule.ScreenRecord // b/352130094
public void testDragIconToPage2() {
Workspace workspace = mLauncher.getWorkspace();
@@ -289,6 +285,7 @@
@Test
@PortraitLandscape
+ @ScreenRecordRule.ScreenRecord // b/330232490
public void testEmptyPagesGetRemovedIfBothPagesAreEmpty() {
Workspace workspace = mLauncher.getWorkspace();
@@ -352,11 +349,8 @@
}
private void assertPagesExist(Launcher launcher, int... pageIds) {
- waitForLauncherCondition("Existing page count does NOT match. "
- + "Expected: " + pageIds.length
- + ". Actual: " + launcher.getWorkspace().getPageCount(),
- l -> pageIds.length == l.getWorkspace().getPageCount());
int pageCount = launcher.getWorkspace().getPageCount();
+ assertEquals("Existing page count does NOT match.", pageIds.length, pageCount);
for (int i = 0; i < pageCount; i++) {
CellLayout page = (CellLayout) launcher.getWorkspace().getPageAt(i);
int pageId = launcher.getWorkspace().getCellLayoutId(page);
diff --git a/tests/src/com/android/launcher3/util/SimpleBroadcastReceiverTest.kt b/tests/src/com/android/launcher3/util/SimpleBroadcastReceiverTest.kt
new file mode 100644
index 0000000..1de99c5
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/SimpleBroadcastReceiverTest.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.Handler
+import android.os.Looper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
+import com.google.common.truth.Truth.assertThat
+import java.util.function.Consumer
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.ArgumentMatchers.same
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SimpleBroadcastReceiverTest {
+
+ private lateinit var underTest: SimpleBroadcastReceiver
+
+ @Mock private lateinit var intentConsumer: Consumer<Intent>
+ @Mock private lateinit var context: Context
+ @Mock private lateinit var completionRunnable: Runnable
+ @Captor private lateinit var intentFilterCaptor: ArgumentCaptor<IntentFilter>
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ underTest = SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, intentConsumer)
+ if (Looper.getMainLooper() == null) {
+ Looper.prepareMainLooper()
+ }
+ }
+
+ @Test
+ fun async_register() {
+ underTest.register(context, "test_action_1", "test_action_2")
+ awaitTasksCompleted()
+
+ verify(context).registerReceiver(same(underTest), intentFilterCaptor.capture())
+ val intentFilter = intentFilterCaptor.value
+ assertThat(intentFilter.countActions()).isEqualTo(2)
+ assertThat(intentFilter.getAction(0)).isEqualTo("test_action_1")
+ assertThat(intentFilter.getAction(1)).isEqualTo("test_action_2")
+ }
+
+ @Test
+ fun async_register_withCompletionRunnable() {
+ underTest.register(context, completionRunnable, "test_action_1", "test_action_2")
+ awaitTasksCompleted()
+
+ verify(context).registerReceiver(same(underTest), intentFilterCaptor.capture())
+ verify(completionRunnable).run()
+ val intentFilter = intentFilterCaptor.value
+ assertThat(intentFilter.countActions()).isEqualTo(2)
+ assertThat(intentFilter.getAction(0)).isEqualTo("test_action_1")
+ assertThat(intentFilter.getAction(1)).isEqualTo("test_action_2")
+ }
+
+ @Test
+ fun async_register_withCompletionRunnable_and_flag() {
+ underTest.register(context, completionRunnable, 1, "test_action_1", "test_action_2")
+ awaitTasksCompleted()
+
+ verify(context).registerReceiver(same(underTest), intentFilterCaptor.capture(), eq(1))
+ verify(completionRunnable).run()
+ val intentFilter = intentFilterCaptor.value
+ assertThat(intentFilter.countActions()).isEqualTo(2)
+ assertThat(intentFilter.getAction(0)).isEqualTo("test_action_1")
+ assertThat(intentFilter.getAction(1)).isEqualTo("test_action_2")
+ }
+
+ @Test
+ fun async_register_with_package() {
+ underTest.registerPkgActions(context, "pkg", "test_action_1", "test_action_2")
+
+ awaitTasksCompleted()
+ verify(context).registerReceiver(same(underTest), intentFilterCaptor.capture())
+ val intentFilter = intentFilterCaptor.value
+ assertThat(intentFilter.getDataScheme(0)).isEqualTo("package")
+ assertThat(intentFilter.getDataSchemeSpecificPart(0).path).isEqualTo("pkg")
+ assertThat(intentFilter.countActions()).isEqualTo(2)
+ assertThat(intentFilter.getAction(0)).isEqualTo("test_action_1")
+ assertThat(intentFilter.getAction(1)).isEqualTo("test_action_2")
+ }
+
+ @Test
+ fun sync_register_withCompletionRunnable_and_flag() {
+ underTest = SimpleBroadcastReceiver(Handler(Looper.getMainLooper()), intentConsumer)
+
+ underTest.register(context, completionRunnable, 1, "test_action_1", "test_action_2")
+
+ verify(context).registerReceiver(same(underTest), intentFilterCaptor.capture(), eq(1))
+ verify(completionRunnable).run()
+ val intentFilter = intentFilterCaptor.value
+ assertThat(intentFilter.countActions()).isEqualTo(2)
+ assertThat(intentFilter.getAction(0)).isEqualTo("test_action_1")
+ assertThat(intentFilter.getAction(1)).isEqualTo("test_action_2")
+ }
+
+ @Test
+ fun async_unregister() {
+ underTest.unregisterReceiverSafely(context)
+
+ awaitTasksCompleted()
+ verify(context).unregisterReceiver(same(underTest))
+ }
+
+ @Test
+ fun sync_unregister() {
+ underTest = SimpleBroadcastReceiver(Handler(Looper.getMainLooper()), intentConsumer)
+
+ underTest.unregisterReceiverSafely(context)
+
+ verify(context).unregisterReceiver(same(underTest))
+ }
+
+ @Test
+ fun getPackageFilter() {
+ val intentFilter =
+ SimpleBroadcastReceiver.getPackageFilter("pkg", "test_action_1", "test_action_2")
+
+ assertThat(intentFilter.getDataScheme(0)).isEqualTo("package")
+ assertThat(intentFilter.getDataSchemeSpecificPart(0).path).isEqualTo("pkg")
+ assertThat(intentFilter.countActions()).isEqualTo(2)
+ assertThat(intentFilter.getAction(0)).isEqualTo("test_action_1")
+ assertThat(intentFilter.getAction(1)).isEqualTo("test_action_2")
+ }
+
+ private fun awaitTasksCompleted() {
+ UI_HELPER_EXECUTOR.submit<Any> { null }.get()
+ }
+}
diff --git a/tests/src/com/android/launcher3/util/ViewOnDrawExecutorTest.kt b/tests/src/com/android/launcher3/util/ViewOnDrawExecutorTest.kt
new file mode 100644
index 0000000..011d138
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/ViewOnDrawExecutorTest.kt
@@ -0,0 +1,186 @@
+/*
+ * 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.view.View
+import android.view.ViewTreeObserver
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.Launcher
+import com.android.launcher3.Workspace
+import com.android.launcher3.pageindicators.PageIndicator
+import java.util.function.Consumer
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.reset
+import org.mockito.kotlin.same
+import org.mockito.kotlin.verifyZeroInteractions
+
+@RunWith(AndroidJUnit4::class)
+class ViewOnDrawExecutorTest<T> where T : View, T : PageIndicator {
+
+ @Mock private lateinit var runnable: Runnable
+ @Mock private lateinit var consumer: Consumer<ViewOnDrawExecutor>
+ @Mock private lateinit var launcher: Launcher
+ @Mock private lateinit var workspace: Workspace<T>
+ @Mock private lateinit var rootView: View
+ @Mock private lateinit var viewTreeObserver: ViewTreeObserver
+
+ private lateinit var underTest: ViewOnDrawExecutor
+ private lateinit var runnableList: RunnableList
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ runnableList = RunnableList()
+ runnableList.add(runnable)
+ underTest = ViewOnDrawExecutor(runnableList, consumer)
+
+ `when`(launcher.workspace).thenReturn(workspace)
+ `when`(workspace.rootView).thenReturn(rootView)
+ `when`(workspace.viewTreeObserver).thenReturn(viewTreeObserver)
+ }
+
+ @Test
+ fun attachToLauncher_alreadyAttachedToWindow() {
+ `when`(workspace.isAttachedToWindow).thenReturn(true)
+
+ underTest.attachTo(launcher)
+
+ verify(workspace).addOnAttachStateChangeListener(same(underTest))
+ verify(viewTreeObserver).addOnDrawListener(same(underTest))
+ verify(rootView).invalidate()
+ }
+
+ @Test
+ fun attachToLauncher_notAttachedToWindow() {
+ `when`(workspace.isAttachedToWindow).thenReturn(false)
+
+ underTest.attachTo(launcher)
+
+ verify(workspace).addOnAttachStateChangeListener(same(underTest))
+ verifyZeroInteractions(viewTreeObserver)
+ verifyZeroInteractions(rootView)
+ }
+
+ @Test
+ fun onViewAttachedToWindow_registerObserver() {
+ `when`(workspace.isAttachedToWindow).thenReturn(false)
+ underTest.attachTo(launcher)
+
+ underTest.onViewAttachedToWindow(rootView)
+
+ verify(viewTreeObserver).addOnDrawListener(same(underTest))
+ verify(rootView).invalidate()
+ }
+
+ @Test
+ fun complete_then_onViewAttachedToWindow_registerObserver() {
+ underTest.markCompleted()
+ reset(viewTreeObserver)
+ reset(rootView)
+
+ underTest.onViewAttachedToWindow(rootView)
+
+ verifyZeroInteractions(viewTreeObserver)
+ verifyZeroInteractions(rootView)
+ }
+
+ @Test
+ fun onDraw_postRunnable() {
+ underTest.attachTo(launcher)
+
+ underTest.onDraw()
+
+ verify(workspace).post(same(underTest))
+ }
+
+ @Test
+ fun run_before_onDraw_noOp() {
+ underTest.run()
+
+ verifyZeroInteractions(runnable)
+ verifyZeroInteractions(viewTreeObserver)
+ verifyZeroInteractions(workspace)
+ verifyZeroInteractions(consumer)
+ }
+
+ @Test
+ fun first_run_executeRunnable() {
+ underTest.attachTo(launcher)
+ underTest.onDraw()
+
+ underTest.run()
+
+ verify(runnable).run()
+ verify(viewTreeObserver).removeOnDrawListener(same(underTest))
+ verify(workspace).removeOnAttachStateChangeListener(same(underTest))
+ verify(consumer).accept(same(underTest))
+ }
+
+ @Test
+ fun second_run_noOp() {
+ underTest.attachTo(launcher)
+ underTest.onDraw()
+ underTest.run()
+ reset(runnable)
+ reset(viewTreeObserver)
+ reset(workspace)
+ reset(consumer)
+
+ underTest.run()
+
+ verifyZeroInteractions(runnable)
+ verifyZeroInteractions(viewTreeObserver)
+ verifyZeroInteractions(workspace)
+ verifyZeroInteractions(consumer)
+ }
+
+ @Test
+ fun markCompleted_viewNotAttached() {
+ underTest.markCompleted()
+
+ verify(runnable).run()
+ verify(consumer).accept(underTest)
+ verifyZeroInteractions(workspace)
+ }
+
+ @Test
+ fun markCompleted_viewAttached() {
+ underTest.attachTo(launcher)
+
+ underTest.markCompleted()
+
+ verify(runnable).run()
+ verify(consumer).accept(underTest)
+ verify(workspace).removeOnAttachStateChangeListener(same(underTest))
+ verify(viewTreeObserver).removeOnDrawListener(same(underTest))
+ }
+
+ @Test
+ fun cancel_notRun() {
+ underTest.cancel()
+
+ verifyZeroInteractions(runnable)
+ verify(consumer).accept(underTest)
+ verifyZeroInteractions(workspace)
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index f02a0c2..25eae44 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -455,10 +455,6 @@
getTestInfo(TestProtocol.REQUEST_ENABLE_ROTATION, Boolean.toString(on));
}
- public void setEnableSuggestion(boolean enableSuggestion) {
- getTestInfo(TestProtocol.REQUEST_ENABLE_SUGGESTION, Boolean.toString(enableSuggestion));
- }
-
public boolean hadNontestEvents() {
return getTestInfo(TestProtocol.REQUEST_GET_HAD_NONTEST_EVENTS)
.getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD);
@@ -2459,7 +2455,8 @@
}
float getWindowCornerRadius() {
- // TODO(b/197326121): Check if the touch is overlapping with the corners by offsetting
+ // Return a larger corner radius to ensure gesture calculated from the radius are offset to
+ // prevent overlapping
final float tmpBuffer = 100f;
final Resources resources = getResources();
if (!supportsRoundedCornersOnWindows(resources)) {
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 9ac6768..748d576 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -345,17 +345,34 @@
* @return map of text -> center of the view. In case of icons with the same name, the one with
* lower x coordinate is selected.
*/
- public Map<String, Point> getWorkspaceIconsPositions() {
+ public Map<String, Point> getAllWorkspaceIconsPositions() {
final UiObject2 workspace = verifyActiveContainer();
- mLauncher.waitForLauncherInitialized(); // b/319501259
List<UiObject2> workspaceIcons =
mLauncher.waitForObjectsInContainer(workspace, AppIcon.getAnyAppIconSelector());
- return workspaceIcons.stream()
+ return getIconPositionMap(workspaceIcons);
+ }
+
+ /**
+ * @return point where icon is found for given the app name,
+ * point is visible center of the icon.
+ */
+ @NonNull
+ public Point getWorkspaceIconPosition(String appName) {
+ final UiObject2 workspace = verifyActiveContainer();
+
+ UiObject2 workspaceIcon =
+ mLauncher.waitForObjectInContainer(workspace,
+ AppIcon.getAppIconSelector(appName, mLauncher));
+ return workspaceIcon.getVisibleCenter();
+ }
+
+ private Map<String, Point> getIconPositionMap(List<UiObject2> icons) {
+ return icons.stream()
.collect(
Collectors.toMap(
/* keyMapper= */ uiObject21 -> {
- Log.d(UIOBJECT_STALE_ELEMENT, "keyText: " +
- uiObject21.getText());
+ Log.d(UIOBJECT_STALE_ELEMENT, "keyText: "
+ + uiObject21.getText());
return uiObject21.getText();
},
/* valueMapper= */ uiObject2 -> {