Merge "Remove all external usages of `GroupTask.task1/2` from Launcher3" into main
diff --git a/Android.bp b/Android.bp
index 5b986ab..1e1e0ad 100644
--- a/Android.bp
+++ b/Android.bp
@@ -452,6 +452,7 @@
"AndroidManifest-common.xml",
],
lint: {
+ extra_check_modules: ["Launcher3LintChecker"],
baseline_filename: "lint-baseline.xml",
},
}
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 9051ca8..6948133 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -10,4 +10,3 @@
[Hook Scripts]
checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --config_xml tools/checkstyle.xml --sha ${PREUPLOAD_COMMIT}
-flag_hook = ${REPO_ROOT}/frameworks/base/packages/SystemUI/flag_check.py --msg=${PREUPLOAD_COMMIT_MESSAGE} --files=${PREUPLOAD_FILES} --project=${REPO_PATH}
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 1726eca..72b3c27 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -65,13 +65,6 @@
}
flag {
- name: "enable_taskbar_connected_displays"
- namespace: "launcher"
- description: "Enables connected displays in taskbar."
- bug: "362720616"
-}
-
-flag {
name: "enable_taskbar_customization"
namespace: "launcher"
description: "Enables taskbar customization framework."
@@ -584,3 +577,10 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "enable_mouse_interaction_changes"
+ namespace: "launcher"
+ description: "Changes mouse interaction behavior"
+ bug: "388897603"
+}
diff --git a/aconfig/launcher_overview.aconfig b/aconfig/launcher_overview.aconfig
index 30b0d40..f379e22 100644
--- a/aconfig/launcher_overview.aconfig
+++ b/aconfig/launcher_overview.aconfig
@@ -89,3 +89,10 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "enable_overview_on_connected_displays"
+ namespace: "launcher_overview"
+ description: "Enable overview on connected displays."
+ bug: "363251602"
+}
diff --git a/checks/Android.bp b/checks/Android.bp
new file mode 100644
index 0000000..dfd701e
--- /dev/null
+++ b/checks/Android.bp
@@ -0,0 +1,46 @@
+// Copyright (C) 2025 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library_host {
+ name: "Launcher3LintChecker",
+ srcs: ["src/**/*.kt"],
+ plugins: ["auto_service_plugin"],
+ libs: [
+ "auto_service_annotations",
+ "lint_api",
+ ],
+ kotlincflags: ["-Xjvm-default=all"],
+}
+
+java_test_host {
+ name: "Launcher3LintCheckerTest",
+ defaults: ["AndroidLintCheckerTestDefaults"],
+ srcs: ["tests/**/*.kt"],
+ data: [
+ ":androidx.annotation_annotation",
+ ":dagger2",
+ ":kotlinx-coroutines-core",
+ ],
+ device_common_data: [
+ ":framework",
+ ],
+ static_libs: [
+ "Launcher3LintChecker",
+ ],
+}
diff --git a/checks/src/com/android/internal/launcher3/lint/CustomDialogDetector.kt b/checks/src/com/android/internal/launcher3/lint/CustomDialogDetector.kt
new file mode 100644
index 0000000..37358bb
--- /dev/null
+++ b/checks/src/com/android/internal/launcher3/lint/CustomDialogDetector.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import org.jetbrains.uast.UClass
+
+/** Detector to identify custom usage of Android's Dialog within the Launcher3 codebase. */
+class CustomDialogDetector : Detector(), SourceCodeScanner {
+
+ override fun applicableSuperClasses(): List<String> {
+ return listOf(DIALOG_CLASS_NAME)
+ }
+
+ override fun visitClass(context: JavaContext, declaration: UClass) {
+ val superTypeClassNames = declaration.superTypes.mapNotNull { it.resolve()?.qualifiedName }
+ if (superTypeClassNames.contains(DIALOG_CLASS_NAME)) {
+ context.report(
+ ISSUE,
+ declaration,
+ context.getNameLocation(declaration),
+ "Class implements Dialog",
+ )
+ }
+ }
+
+ companion object {
+ private const val DIALOG_CLASS_NAME = "android.app.Dialog"
+
+ @JvmField
+ val ISSUE =
+ Issue.create(
+ id = "IllegalUseOfCustomDialog",
+ briefDescription = "dialogs should not be used in Launcher",
+ explanation =
+ """
+ Don't use custom Dialogs within the launcher code base, instead consider utilizing
+ AbstractFloatingView to display content that should float above the launcher where
+ it can be correctly managed for dismissal.
+ """
+ .trimIndent(),
+ category = Category.CORRECTNESS,
+ priority = 10,
+ severity = Severity.ERROR,
+ implementation =
+ Implementation(CustomDialogDetector::class.java, Scope.JAVA_FILE_SCOPE),
+ )
+ }
+}
diff --git a/checks/src/com/android/internal/launcher3/lint/Launcher3IssueRegistry.kt b/checks/src/com/android/internal/launcher3/lint/Launcher3IssueRegistry.kt
new file mode 100644
index 0000000..c77c42b
--- /dev/null
+++ b/checks/src/com/android/internal/launcher3/lint/Launcher3IssueRegistry.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.launcher3.lint
+
+import CustomDialogDetector
+import com.android.tools.lint.client.api.IssueRegistry
+import com.android.tools.lint.client.api.Vendor
+import com.android.tools.lint.detector.api.CURRENT_API
+import com.android.tools.lint.detector.api.Issue
+import com.google.auto.service.AutoService
+
+@AutoService(IssueRegistry::class)
+@Suppress("UnstableApiUsage")
+class Launcher3IssueRegistry : IssueRegistry() {
+ override val issues: List<Issue>
+ get() = listOf(CustomDialogDetector.ISSUE)
+
+ override val api: Int
+ get() = CURRENT_API
+
+ override val minApi: Int
+ get() = 8
+
+ override val vendor: Vendor =
+ Vendor(
+ vendorName = "Android",
+ feedbackUrl = "http://b/issues/new?component=78010",
+ contact = "abegovic@google.com",
+ )
+}
diff --git a/checks/tests/com/android/internal/launcher3/lint/CustomDialogDetectorTest.kt b/checks/tests/com/android/internal/launcher3/lint/CustomDialogDetectorTest.kt
new file mode 100644
index 0000000..2a37953
--- /dev/null
+++ b/checks/tests/com/android/internal/launcher3/lint/CustomDialogDetectorTest.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.launcher3.lint
+
+import CustomDialogDetector
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+/** Test for [CustomDialogDetector]. */
+class CustomDialogDetectorTest : Launcher3LintDetectorTest() {
+ override fun getDetector(): Detector = CustomDialogDetector()
+
+ override fun getIssues(): List<Issue> = listOf(CustomDialogDetector.ISSUE)
+
+ @Test
+ fun classDoesNotExtendDialog_noViolation() {
+ lint()
+ .files(
+ TestFiles.kotlin(
+ """
+ package test.pkg
+
+ class SomeClass
+ """
+ .trimIndent()
+ ),
+ *androidStubs,
+ )
+ .issues(CustomDialogDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
+ fun classDoesExtendDialog_violation() {
+ lint()
+ .files(
+ TestFiles.kotlin(
+ """
+ package test.pkg
+
+ import android.app.Dialog
+
+ class SomeClass(context: Context) : Dialog(context)
+ """
+ .trimIndent()
+ ),
+ *androidStubs,
+ )
+ .issues(CustomDialogDetector.ISSUE)
+ .run()
+ .expect(
+ ("""
+ src/test/pkg/SomeClass.kt:5: Error: Class implements Dialog [IllegalUseOfCustomDialog]
+ class SomeClass(context: Context) : Dialog(context)
+ ~~~~~~~~~
+ 1 errors, 0 warnings
+ """)
+ .trimIndent()
+ )
+ }
+}
diff --git a/checks/tests/com/android/internal/launcher3/lint/Launcher3LintDetectorTest.kt b/checks/tests/com/android/internal/launcher3/lint/Launcher3LintDetectorTest.kt
new file mode 100644
index 0000000..09085c7
--- /dev/null
+++ b/checks/tests/com/android/internal/launcher3/lint/Launcher3LintDetectorTest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.launcher3.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import java.io.File
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+/**
+ * Abstract class that should be used by any test for launcher 3 lint detectors.
+ *
+ * When you write your test, ensure that you pass [androidStubs] as part of your [TestFiles]
+ * definition.
+ */
+@RunWith(JUnit4::class)
+abstract class Launcher3LintDetectorTest : LintDetectorTest() {
+
+ /**
+ * Customize the lint task to disable SDK usage completely. This ensures that running the tests
+ * in Android Studio has the same result as running the tests in atest
+ */
+ override fun lint(): TestLintTask =
+ super.lint().allowMissingSdk(true).sdkHome(File("/dev/null"))
+
+ companion object {
+ private val libraryNames =
+ arrayOf(
+ "androidx.annotation_annotation.jar",
+ "dagger2.jar",
+ "framework.jar",
+ "kotlinx-coroutines-core.jar",
+ )
+
+ /**
+ * This file contains stubs of framework APIs and System UI classes for testing purposes
+ * only. The stubs are not used in the lint detectors themselves.
+ */
+ val androidStubs =
+ libraryNames
+ .map { TestFiles.LibraryReferenceTestFile(File(it).canonicalFile) }
+ .toTypedArray()
+ }
+}
diff --git a/quickstep/res/drawable/ic_close_option.xml b/quickstep/res/drawable/ic_close_option.xml
new file mode 100644
index 0000000..5681cb5
--- /dev/null
+++ b/quickstep/res/drawable/ic_close_option.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M256,760L200,704L424,480L200,256L256,200L480,424L704,200L760,256L536,480L760,704L704,760L480,536L256,760Z" />
+</vector>
diff --git a/quickstep/res/values-af/strings.xml b/quickstep/res/values-af/strings.xml
index eba4ae6..4c4a403 100644
--- a/quickstep/res/values-af/strings.xml
+++ b/quickstep/res/values-af/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Programvoorstelle is geaktiveer"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Programvoorstelle is gedeaktiveer"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Voorspelde app: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Gebaarnavigasietutoriaal"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Draai jou toestel"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Draai asseblief jou toestel om die tutoriaal oor gebaarnavigasie te voltooi"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Maak seker dat jy van die rand heel regs of heel links af swiep"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Taakbalk word gewys"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Taakbalk en borrels wys links"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Taakbalk en borrels wys regs"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Taakbalk is versteek"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Taakbalk en borrels is versteek"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigasiebalk"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Wys altyd Taakbalk"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Verander navigasiemodus"</string>
diff --git a/quickstep/res/values-am/strings.xml b/quickstep/res/values-am/strings.xml
index 019850d..2716fd5 100644
--- a/quickstep/res/values-am/strings.xml
+++ b/quickstep/res/values-am/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"የመተግበሪያ አስተያየት ጥቆማዎች ነቅቷል"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"የመተግበሪያ አስተያየቶች ቦዝነዋል"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"የተገመተው መተግበሪያ፦ <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"የእጅ ምልክት ዳሰሳ አጋዥ ሥልጠና"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"መሣሪያዎን ያሽከርክሩ"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"የእጅ ምልክት ዳሰሳ አጋዥ ሥልጠናን ለማጠናቀቅ እባክዎ መሣሪያዎን ያሽከርክሩ"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"ከቀኝ ጥግ ወይም ከግራ ጥግ ጠርዝ ጀምሮ ማንሸራተትዎን ያረጋግጡ"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"የተግባር አሞሌ ይታያል"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"የተግባር አሞሌ እና አረፋዎች በግራ በኩል ይታያሉ"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"የተግባር አሞሌ እና አረፋዎች በቀኝ በኩል ይታያሉ"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"የተግባር አሞሌ ተደብቋል"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"የተግባር አሞሌ እና አረፋዎች ተደብቀዋል"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"የአሰሳ አሞሌ"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"ሁልጊዜ የተግባር አሞሌ ያሳዩ"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"የአሰሳ ሁነታን ይለውጡ"</string>
diff --git a/quickstep/res/values-ar/strings.xml b/quickstep/res/values-ar/strings.xml
index a77335f..a866bb9 100644
--- a/quickstep/res/values-ar/strings.xml
+++ b/quickstep/res/values-ar/strings.xml
@@ -132,8 +132,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"تم إظهار شريط التطبيقات"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"يتم عرض \"شريط التطبيقات\" و\"فقاعات المحادثات\" يمينًا"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"يتم عرض \"شريط التطبيقات\" و\"فقاعات المحادثات\" يسارًا"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"تم إخفاء شريط التطبيقات"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"تم إخفاء \"شريط التطبيقات\" و\"فقاعات المحادثات\""</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"شريط التنقل"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"إظهار شريط التطبيقات دائمًا"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"تغيير وضع التنقل"</string>
diff --git a/quickstep/res/values-as/strings.xml b/quickstep/res/values-as/strings.xml
index a7a66e9..4292a76 100644
--- a/quickstep/res/values-as/strings.xml
+++ b/quickstep/res/values-as/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"এপৰ পৰামৰ্শসমূহ সক্ষম কৰা আছে"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"এপৰ পৰামৰ্শসমূহ অক্ষম কৰা আছে"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"পূৰ্বানুমান কৰা এপ্: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"আঙুলিৰ স্পৰ্শৰ নিৰ্দেশেৰে কৰা নেভিগেশ্বন সম্পৰ্কীয় টিউট’ৰিয়েল"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"আপোনাৰ ডিভাইচটো ঘূৰাওক"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"আঙুলিৰ স্পৰ্শৰ নিৰ্দেশেৰে কৰা নেভিগেশ্বনৰ টিউট’ৰিয়েল শেষ কৰিবলৈ অনুগ্ৰহ কৰি আপোনাৰ ডিভাইচটো ঘূৰাওক"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"আপুনি সোঁ অথবা বাওঁ কাষৰ একেবাৰে সীমাৰ পৰা ছোৱাইপ কৰাটো নিশ্চিত কৰক"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"টাস্কবাৰ দেখুওৱা হৈছে"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"টাস্কবাৰ আৰু বাবল বাওঁফালে দেখুওৱা হৈছে"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"টাস্কবাৰ আৰু বাবল সোঁফালে দেখুওৱা হৈছে"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"টাস্কবাৰ লুকুৱাই থোৱা হৈছে"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"টাস্কবাৰ আৰু বাবল লুকুওৱা হৈছে"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"নেভিগেশ্বনৰ দণ্ড"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"টাস্কবাৰ সদায় দেখুৱাওক"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"নেভিগেশ্বন ম’ড সলনি কৰক"</string>
diff --git a/quickstep/res/values-az/strings.xml b/quickstep/res/values-az/strings.xml
index 0e0181a..a7bd553 100644
--- a/quickstep/res/values-az/strings.xml
+++ b/quickstep/res/values-az/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Tətbiq təklifləri aktivdir"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Tətbiq təklifləri deaktivdir"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Proqnozlaşdırılan tətbiq: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Jest naviqasiyası üçün öyrədici"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Cihazı fırladın"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Jest naviqasiyası təlimatını tamamlamaq üçün cihazı fırladın"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Ən sağ və ya sol kənardan sürüşdürün"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"İşləmə paneli göstərilir"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"İşləmə paneli, yumrucuqlar solda göstərilir"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"İşləmə paneli, yumrucuqlar sağda göstərilir"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"İşləmə paneli gizlədilib"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"İşləmə paneli, yumrucuqlar gizlədilib"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Naviqasiya paneli"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"İşləmə paneli həmişə görünsün"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Naviqasiya rejimini dəyişin"</string>
diff --git a/quickstep/res/values-b+sr+Latn/strings.xml b/quickstep/res/values-b+sr+Latn/strings.xml
index 95767e2..ef46503 100644
--- a/quickstep/res/values-b+sr+Latn/strings.xml
+++ b/quickstep/res/values-b+sr+Latn/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Predlozi aplikacija su omogućeni"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Predlozi aplikacija su onemogućeni"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predviđamo aplikaciju: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Vodič za navigaciju pomoću pokreta"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rotirajte uređaj"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Rotirajte uređaj da biste dovršili vodič za navigaciju pomoću pokreta"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Obavezno prevucite od same desne ili leve ivice"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Traka zadataka je prikazana"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Prikaz zadataka/oblačića levo"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Prikaz zadataka/oblačića desno"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Traka zadataka je skrivena"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Skriveni zadaci/oblačići"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Traka za navigaciju"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Uvek prikazuj traku zadataka"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Promeni režim navigacije"</string>
diff --git a/quickstep/res/values-be/strings.xml b/quickstep/res/values-be/strings.xml
index 0261c0f..205fe9d 100644
--- a/quickstep/res/values-be/strings.xml
+++ b/quickstep/res/values-be/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Прапановы праграм уключаны"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Прапановы праграм выключаны"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Праграма з падказкі: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Дапаможнік па навігацыі жэстамі"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Павярніце прыладу"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Каб пачаць азнаямленне з дапаможнікам па навігацыі жэстамі, павярніце прыладу"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Правядзіце пальцам справа налева ці злева направа ад самага краю экрана"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Панэль задач паказана"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Панэль задач і ўсплывальныя чаты паказваюцца злева"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Панэль задач і ўсплывальныя чаты паказваюцца справа"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Панэль задач схавана"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Панэль задач і ўсплывальныя чаты схаваны"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Панэль навігацыі"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Заўсёды паказваць панэль задач"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Змяніць рэжым навігацыі"</string>
diff --git a/quickstep/res/values-bg/strings.xml b/quickstep/res/values-bg/strings.xml
index 660d816..048806f 100644
--- a/quickstep/res/values-bg/strings.xml
+++ b/quickstep/res/values-bg/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Предложенията за приложения са активирани"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Функцията „Предложения за приложения“ е деактивирана"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Предвидено приложение: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Урок за навигирането с жестове"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Завъртете устройството си"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Моля, завъртете устройството си, за да завършите урока за навигиране с жестове"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Трябва да плъзнете пръст от най-дясната или най-лявата част на екрана"</string>
@@ -88,7 +87,7 @@
<string name="gesture_tutorial_nice" msgid="2936275692616928280">"Чудесно!"</string>
<string name="gesture_tutorial_step" msgid="1279786122817620968">"Урок <xliff:g id="CURRENT">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="allset_title" msgid="5021126669778966707">"Готово!"</string>
- <string name="allset_hint" msgid="459504134589971527">"Плъзнете пръст нагоре, за да отворите началния екран"</string>
+ <string name="allset_hint" msgid="459504134589971527">"Плъзнете с пръст нагоре, за да отворите началния екран"</string>
<string name="allset_button_hint" msgid="2395219947744706291">"Докоснете бутона „Начало“, за да преминете към началния екран"</string>
<string name="allset_description_generic" msgid="5385500062202019855">"Можете да започнете да използвате <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="default_device_name" msgid="6660656727127422487">"устройството"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Лентата на задачите се показва"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Лентата и балончетата са вляво"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Лентата и балончетата са вдясно"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Лентата на задачите е скрита"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Лентата и балончетата са скрити"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Лента за навигация"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Лентата на задачите винаги да се показва"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Промяна на режима на навигация"</string>
diff --git a/quickstep/res/values-bn/strings.xml b/quickstep/res/values-bn/strings.xml
index b5b0a5e..dec7443 100644
--- a/quickstep/res/values-bn/strings.xml
+++ b/quickstep/res/values-bn/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"অ্যাপ সাজেশন চালু করা আছে"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"অ্যাপ সাজেশন বন্ধ করা আছে"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"আপনার প্রয়োজন হতে পারে এমন অ্যাপ: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"জেসচার নেভিগেশন সম্পর্কিত টিউটোরিয়াল"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"আপনার ডিভাইস ঘোরান"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"জেসচার নেভিগেশন টিউটোরিয়াল সম্পূর্ণ করতে আপনার ডিভাইসটি ঘোরান"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"স্ক্রিনের একেবারে ডান বা বাঁদিকের প্রান্ত থেকে সোয়াইপ করেছেন কিনা তা দেখে নিন"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"\'টাস্কবার\' দেখানো হয়েছে"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"টাস্কবার ও বাবল বাঁদিকে দেখানো হয়েছে"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"টাস্কবার ও বাবল ডানদিকে দেখানো হয়েছে"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"\'টাস্কবার\' লুকানো রয়েছে"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"টাস্কবার ও বাবল লুকানো হয়েছে"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"নেভিগেশন বার"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"সবসময় টাস্কবার দেখুন"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"\'নেভিগেশন\' মোড পরিবর্তন করুন"</string>
diff --git a/quickstep/res/values-bs/strings.xml b/quickstep/res/values-bs/strings.xml
index 69a5cdb..626279f 100644
--- a/quickstep/res/values-bs/strings.xml
+++ b/quickstep/res/values-bs/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Prijedlozi aplikacija su omogućeni"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Prijedlozi aplikacija su onemogućeni"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predviđena aplikacija: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Vodič za navigaciju pokretima"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rotirajte uređaj"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Rotirajte uređaj da završite vodič za navigaciju pokretima"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Prevucite s krajnjeg desnog ili lijevog ruba"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Traka zadataka je prikazana"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Traka zadataka/oblačići prik. lijevo"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Traka zadataka/oblačići prik. desno"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Traka zadataka je sakrivena"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Traka zadataka/oblačići sakriveni"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigaciona traka"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Uvijek prikaži traku zadataka"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Promijeni način navigacije"</string>
diff --git a/quickstep/res/values-ca/strings.xml b/quickstep/res/values-ca/strings.xml
index cc643a6..810e8f8 100644
--- a/quickstep/res/values-ca/strings.xml
+++ b/quickstep/res/values-ca/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Els suggeriments d\'aplicacions estan activats"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Els suggeriments d\'aplicacions estan desactivats"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predicció d\'aplicació: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Tutorial de navegació amb gestos"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Gira el dispositiu"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Gira el dispositiu per completar el tutorial de navegació amb gestos"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Assegura\'t de lliscar des de l\'extrem dret o esquerre de la pantalla."</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Es mostra la Barra de tasques"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Barra i bombolles a l\'esquerra"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Barra i bombolles a la dreta"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"S\'ha amagat la Barra de tasques"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Barra i bombolles amagades"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Barra de navegació"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Barra de tasques sempre visible"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Canvia el mode de navegació"</string>
diff --git a/quickstep/res/values-cs/strings.xml b/quickstep/res/values-cs/strings.xml
index eb0e8c8..9fd2fa5 100644
--- a/quickstep/res/values-cs/strings.xml
+++ b/quickstep/res/values-cs/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Návrhy aplikací jsou povoleny"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Návrhy aplikací jsou zakázány"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Předpokládaná aplikace: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Výukový program navigace gesty"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Otočte zařízení"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Pokud chcete dokončit výukový program navigace gesty, otočte zařízení"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Přejeďte prstem z úplného pravého nebo levého okraje obrazovky"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Panel aplikací je zobrazen"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Panel a bubliny vlevo zobr."</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Panel a bubliny vpravo zobr."</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Panel aplikací je skrytý"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Panel a bubliny jsou skryty"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigační panel"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Vždy zobrazovat panel aplikací"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Změnit navigační režim"</string>
diff --git a/quickstep/res/values-da/strings.xml b/quickstep/res/values-da/strings.xml
index a7b7d04..b2a5f33 100644
--- a/quickstep/res/values-da/strings.xml
+++ b/quickstep/res/values-da/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Appforslag er aktiveret"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Appforslag er deaktiveret"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"App, du forventes at skulle bruge: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Vejledning i navigation med bevægelser"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rotér din enhed"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Rotér enheden for at gennemgå vejledningen i navigation med bevægelser"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Stryg fra kanten yderst til højre eller venstre"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Proceslinjen vises"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Proceslinje og bobler til venstre"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Proceslinje og bobler til højre"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Proceslinjen er skjult"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Proceslinje og bobler skjult"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigationslinje"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Vis altid proceslinjen"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Skift navigationstilstand"</string>
diff --git a/quickstep/res/values-de/strings.xml b/quickstep/res/values-de/strings.xml
index dd7b467..93fba3b 100644
--- a/quickstep/res/values-de/strings.xml
+++ b/quickstep/res/values-de/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Funktion „App-Vorschläge“ aktiviert"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Funktion \"App-Vorschläge\" deaktiviert"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Vorgeschlagene App: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Anleitung zur Bedienung über Gesten"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Gerät drehen"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Bitte drehe dein Gerät, um die Anleitung für die Bedienung über Gesten abzuschließen"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Wische vom äußersten rechten oder linken Displayrand"</string>
@@ -92,7 +91,7 @@
<string name="allset_button_hint" msgid="2395219947744706291">"Startbildschirmtaste drücken, um zum Startbildschirm zu gehen"</string>
<string name="allset_description_generic" msgid="5385500062202019855">"Du kannst dein <xliff:g id="DEVICE">%1$s</xliff:g> jetzt verwenden"</string>
<string name="default_device_name" msgid="6660656727127422487">"Gerät"</string>
- <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Einstellungen der Systemsteuerung"</annotation></string>
+ <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Einstellungen für die Systemnavigation"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Teilen"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
<string name="action_split" msgid="2098009717623550676">"Splitscreen"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Taskleiste eingeblendet"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Taskleiste & Bubbles links eingeblendet"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Taskleiste & Bubbles rechts eingeblendet"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Taskleiste ausgeblendet"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Taskleiste & Bubbles ausgeblendet"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigationsleiste"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Taskleiste immer anzeigen"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Navigationsmodus ändern"</string>
diff --git a/quickstep/res/values-el/strings.xml b/quickstep/res/values-el/strings.xml
index 058d7ba..960831e 100644
--- a/quickstep/res/values-el/strings.xml
+++ b/quickstep/res/values-el/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Οι προτεινόμενες εφαρμογές ενεργοποιήθηκαν"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Οι προτεινόμενες εφαρμογές είναι απενεργοποιημένες"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Εφαρμογή από πρόβλεψη: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Οδηγός για την πλοήγηση με κινήσεις"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Περιστρέψτε τη συσκευή σας"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Περιστρέψτε τη συσκευή σας για να ολοκληρώσετε τον οδηγό πλοήγησης με κινήσεις"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Φροντίστε να σύρετε από το άκρο της δεξιάς ή της αριστερής πλευράς."</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Η γραμμή εργαλείων εμφανίζεται"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Εμφάν. αριστ. γρ. εργ. και συν."</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Εμφάν. δεξιάς γρ. εργ. και συν."</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Η γραμμή εργαλείων είναι κρυφή"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Απόκρυψη εργαλείων και συννεφ."</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Γραμμή πλοήγησης"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Εμφάνιση Γραμμής εργαλείων"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Αλλαγή τρόπου πλοήγησης"</string>
diff --git a/quickstep/res/values-en-rAU/strings.xml b/quickstep/res/values-en-rAU/strings.xml
index 506e36d..bf115f2 100644
--- a/quickstep/res/values-en-rAU/strings.xml
+++ b/quickstep/res/values-en-rAU/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"App suggestions enabled"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"App suggestions are disabled"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predicted app: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Gesture navigation tutorial"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rotate your device"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Please rotate your device to complete the gesture navigation tutorial"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Make sure you swipe from the far-right or far-left edge"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Taskbar shown"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Taskbar and bubbles left shown"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Taskbar and bubbles right shown"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Taskbar hidden"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Taskbar and bubbles hidden"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigation bar"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Always show Taskbar"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Change navigation mode"</string>
diff --git a/quickstep/res/values-en-rCA/strings.xml b/quickstep/res/values-en-rCA/strings.xml
index f91f4ee..cd6abc3 100644
--- a/quickstep/res/values-en-rCA/strings.xml
+++ b/quickstep/res/values-en-rCA/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"App suggestions enabled"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"App suggestions are disabled"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predicted app: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Gesture Navigation Tutorial"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rotate your device"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Please rotate your device to complete the gesture navigation tutorial"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Make sure you swipe from the far-right or far-left edge"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Taskbar shown"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Taskbar & bubbles left shown"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Taskbar & bubbles right shown"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Taskbar hidden"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Taskbar & bubbles hidden"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigation bar"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Always show Taskbar"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Change navigation mode"</string>
diff --git a/quickstep/res/values-en-rGB/strings.xml b/quickstep/res/values-en-rGB/strings.xml
index 506e36d..bf115f2 100644
--- a/quickstep/res/values-en-rGB/strings.xml
+++ b/quickstep/res/values-en-rGB/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"App suggestions enabled"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"App suggestions are disabled"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predicted app: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Gesture navigation tutorial"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rotate your device"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Please rotate your device to complete the gesture navigation tutorial"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Make sure you swipe from the far-right or far-left edge"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Taskbar shown"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Taskbar and bubbles left shown"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Taskbar and bubbles right shown"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Taskbar hidden"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Taskbar and bubbles hidden"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigation bar"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Always show Taskbar"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Change navigation mode"</string>
diff --git a/quickstep/res/values-en-rIN/strings.xml b/quickstep/res/values-en-rIN/strings.xml
index 506e36d..bf115f2 100644
--- a/quickstep/res/values-en-rIN/strings.xml
+++ b/quickstep/res/values-en-rIN/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"App suggestions enabled"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"App suggestions are disabled"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predicted app: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Gesture navigation tutorial"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rotate your device"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Please rotate your device to complete the gesture navigation tutorial"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Make sure you swipe from the far-right or far-left edge"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Taskbar shown"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Taskbar and bubbles left shown"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Taskbar and bubbles right shown"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Taskbar hidden"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Taskbar and bubbles hidden"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigation bar"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Always show Taskbar"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Change navigation mode"</string>
diff --git a/quickstep/res/values-es-rUS/strings.xml b/quickstep/res/values-es-rUS/strings.xml
index dd969d7..0955f28 100644
--- a/quickstep/res/values-es-rUS/strings.xml
+++ b/quickstep/res/values-es-rUS/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Sugerencias de apps habilitadas"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Las sugerencias de aplicaciones están inhabilitadas"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predicción de app: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Instructivo de navegación por gestos"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rota el dispositivo"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Rota el dispositivo para completar el instructivo de la navegación por gestos"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Asegúrate de deslizar desde el extremo derecho o izquierdo"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Barra de tareas visible"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Barra tareas y burb. a la izq."</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Barra tareas y burb. a la der."</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Barra de tareas oculta"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Barra tareas y burb. ocultas"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Barra de navegación"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Barra de tareas visible"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Cambiar el modo de navegación"</string>
diff --git a/quickstep/res/values-es/strings.xml b/quickstep/res/values-es/strings.xml
index 1da35e6..a5ac1d2 100644
--- a/quickstep/res/values-es/strings.xml
+++ b/quickstep/res/values-es/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Sugerencias de aplicaciones habilitadas"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Las sugerencias de aplicaciones están inhabilitadas"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Aplicación sugerida: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Tutorial de navegación por gestos"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Gira el dispositivo"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Gira el dispositivo para completar el tutorial de navegación por gestos"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Asegúrate de deslizar desde el borde derecho o izquierdo de la pantalla"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Barra de tareas visible"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Tareas y burbujas a izquierda"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Tareas y burbujas a derecha"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Barra de tareas oculta"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Tareas y burbujas ocultas"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Barra de navegación"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Barra de tareas visible"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Cambiar el modo de navegación"</string>
diff --git a/quickstep/res/values-et/strings.xml b/quickstep/res/values-et/strings.xml
index 70acc52..bb56954 100644
--- a/quickstep/res/values-et/strings.xml
+++ b/quickstep/res/values-et/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Rakenduste soovitused on lubatud"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Rakenduste soovitused on keelatud"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Ennustatud rakendus: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Liigutustega navigeerimise juhend"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Pöörake seadet"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Pöörake seadet, et liigutustega navigeerimise õpetused lõpetada"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Pühkige kindlasti parem- või vasakpoolsest servast"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Tegumiriba on kuvatud"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Tegumiriba ja mullid kuvatakse"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Tegumiriba ja mullid kuvatakse paremal"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Tegumiriba on peidetud"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Tegumiriba ja mullid on peidetud"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigeerimisriba"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Kuva tegumiriba alati"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Navigeerimisrežiimi muutmine"</string>
diff --git a/quickstep/res/values-eu/strings.xml b/quickstep/res/values-eu/strings.xml
index af5962a..9ee1255 100644
--- a/quickstep/res/values-eu/strings.xml
+++ b/quickstep/res/values-eu/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Gaituta daude aplikazioen iradokizunak"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Desgaituta daude aplikazioen iradokizunak"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Iragarritako aplikazioa: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Keinu bidezko nabigazioaren tutoriala"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Biratu gailua"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Keinu bidezko nabigazioaren tutoriala osatzeko, biratu gailua"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Ziurtatu hatza pantailaren eskuineko edo ezkerreko ertzetik hasten zarela pasatzen"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Zereginen barra ikusgai dago"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Zereginen barra eta burbuilak ezkerrean ikusgai"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Zereginen barra eta burbuilak eskuinean ikusgai"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Zereginen barra itxita dago"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Zereginen barra eta burbuilak ezkutatuta"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Nabigazio-barra"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Erakutsi beti zereginen barra"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Aldatu nabigazio modua"</string>
diff --git a/quickstep/res/values-fa/strings.xml b/quickstep/res/values-fa/strings.xml
index 40469f5..d1ca8ed 100644
--- a/quickstep/res/values-fa/strings.xml
+++ b/quickstep/res/values-fa/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"«پیشنهاد برنامه» فعال است"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"«پیشنهاد برنامه» غیرفعال است"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"برنامه پیشبینیشده: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"آموزش گامبهگام پیمایش اشارهای"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"دستگاهتان را بچرخانید"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"لطفاً برای تکمیل آموزش گامبهگام پیمایش اشارهای، دستگاهتان را بچرخانید"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"دقت کنید که از انتهای لبه سمت راست یا سمت چپ تند بکشید"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"نوار وظیفه نمایان است"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"نمایش نوار وظیفه و حبابکها در چپ"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"نمایش نوار وظیفه و حبابکها در راست"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"نوار وظیفه پنهان است"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"نوار وظیفه و حبابکها پنهان هستند"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"نوار پیمایش"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"نوار وظیفه همیشه نشان داده شود"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"تغییر حالت پیمایش"</string>
diff --git a/quickstep/res/values-fi/strings.xml b/quickstep/res/values-fi/strings.xml
index 6907e49..13750dc 100644
--- a/quickstep/res/values-fi/strings.xml
+++ b/quickstep/res/values-fi/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Sovellusehdotukset käytössä"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Sovellusehdotukset on poistettu käytöstä"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Ennakoitu sovellus: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Eleillä navigoinnin ohje"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Käännä laite"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Käännä laite, niin voit katsoa esittelyn eleillä navigoinnista"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Pyyhkäise aivan oikeasta tai vasemmasta reunasta"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Tehtäväpalkki näkyvissä"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Vas. palkki ja kuplat näkyvissä"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Oik. palkki ja kuplat näkyvissä"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Tehtäväpalkki piilotettu"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Palkki ja kuplat piilotettu"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigointipalkki"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Näytä tehtäväpalkki aina"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Vaihda navigointitilaa"</string>
diff --git a/quickstep/res/values-fr-rCA/strings.xml b/quickstep/res/values-fr-rCA/strings.xml
index d57479d..b83e33c 100644
--- a/quickstep/res/values-fr-rCA/strings.xml
+++ b/quickstep/res/values-fr-rCA/strings.xml
@@ -46,8 +46,7 @@
<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>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Tutoriel de navigation par gestes"</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>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Barre des tâches affichée"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Barre des tâches/bulles à gauche"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Barre des tâches/bulles à droite"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Barre des tâches masquée"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Barre des tâches/bulles masquées"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Barre de navigation"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Tjrs afficher barre des tâches"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Changer de mode de navigation"</string>
diff --git a/quickstep/res/values-fr/strings.xml b/quickstep/res/values-fr/strings.xml
index b185e0c..66102d5 100644
--- a/quickstep/res/values-fr/strings.xml
+++ b/quickstep/res/values-fr/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Suggestions d\'applications 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>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Tutoriel sur la navigation par gestes"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Faire pivoter l\'appareil"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Veuillez faire pivoter votre appareil pour effectuer le tutoriel de navigation par gestes"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Veillez à bien balayer l\'écran depuis le bord gauche ou droit"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Barre des tâches affichée"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Barre des tâches et bulles affichées à gauche"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Barre des tâches et bulles affichées à droite"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Barre des tâches masquée"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Barre des tâches et bulles masquées"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Barre de navigation"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Barre des tâches tjs visible"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Modifier le mode de navigation"</string>
diff --git a/quickstep/res/values-gl/strings.xml b/quickstep/res/values-gl/strings.xml
index 8990a5f..16c448c 100644
--- a/quickstep/res/values-gl/strings.xml
+++ b/quickstep/res/values-gl/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"As suxestións de aplicacións están activadas"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"As suxestións de aplicacións están desactivadas"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Aplicación predita: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Titorial de navegación con xestos"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Xira o dispositivo"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Xira o dispositivo para completar o titorial de navegación con xestos"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Asegúrate de pasar o dedo desde o bordo dereito ou esquerdo"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Estase mostrando a barra de tarefas"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Barra e burbullas á esquerda"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Barra e burbullas á dereita"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Non se está mostrando a barra de tarefas"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Barra e burbullas ocultas"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Barra de navegación"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Ver sempre a barra de tarefas"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Cambiar modo de navegación"</string>
diff --git a/quickstep/res/values-gu/strings.xml b/quickstep/res/values-gu/strings.xml
index cb8a35a..0cc6091 100644
--- a/quickstep/res/values-gu/strings.xml
+++ b/quickstep/res/values-gu/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"ઍપના સુઝાવો ચાલુ છે"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ઍપના સુઝાવો બંધ છે"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"પૂર્વાનુમાનિત ઍપ: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"સંકેતથી નૅવિગેશનનું ટ્યૂટૉરિઅલ"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"તમારા ડિવાઇસને ફેરવો"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"સંકેતથી નૅવિગેશન ટ્યૂટૉરિઅલ પૂર્ણ કરવા માટે કૃપા કરીને તમારા ડિવાઇસને ફેરવો"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"ખાતરી કરો કે તમે એકદમ દૂરની જમણી કે ડાબી કિનારીએથી સ્વાઇપ કરો છો"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"ટાસ્કબાર બતાવવામાં આવ્યો"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"ટાસ્કબાર અને બબલ ડાબે બતાવ્યા"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"ટાસ્કબાર અને બબલ જમણે બતાવ્યા"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"ટાસ્કબાર છુપાવવામાં આવ્યો"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"ટાસ્કબાર અને બબલ છુપાવેલા છે"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"નૅવિગેશન બાર"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"હંમેશાં ટાસ્કબાર બતાવો"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"નૅવિગેશન મોડ બદલો"</string>
diff --git a/quickstep/res/values-hi/strings.xml b/quickstep/res/values-hi/strings.xml
index b7ca582..2135dac 100644
--- a/quickstep/res/values-hi/strings.xml
+++ b/quickstep/res/values-hi/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"सुझाए गए ऐप्लिकेशन की सुविधा चालू है"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"सुझाए गए ऐप्लिकेशन की सुविधा बंद है"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"सुझाया गया ऐप्लिकेशन: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"जेस्चर वाले नेविगेशन के लिए ट्यूटोरियल"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"अपना डिवाइस घुमाएं"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"जेस्चर वाले नेविगेशन से जुड़े ट्यूटोरियल को पूरा करने के लिए अपने डिवाइस को घुमाएं"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"स्क्रीन पर बिलकुल दाएं या बाएं किनारे से स्वाइप करें"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"टास्कबार दिखाया गया"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"टास्कबार और बबल बाईं ओर हैं"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"टास्कबार और बबल दाईं ओर हैं"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"टास्कबार छिपाया गया"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"टास्कबार और बबल छिपाए गए"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"नेविगेशन बार"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"टास्कबार हमेशा दिखाएं"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"नेविगेशन का मोड बदलें"</string>
diff --git a/quickstep/res/values-hr/strings.xml b/quickstep/res/values-hr/strings.xml
index cb7d9b9..0fd3123 100644
--- a/quickstep/res/values-hr/strings.xml
+++ b/quickstep/res/values-hr/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Predlaganje apl. omogućeno"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Predlaganje apl. onemogućeno"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predviđena aplikacija: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Vodič za navigaciju pokretima"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Zakrenite uređaj"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Zakrenite uređaj da biste dovršili vodič o navigaciji pokretima"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Prijeđite prstom od krajnjeg desnog ili krajnjeg lijevog ruba"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Traka sa zadacima prikazana"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Traka sa zadacima/oblačići prikazani lijevo"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Traka sa zadacima/oblačići prikazani desno"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Traka sa zadacima skrivena"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Traka sa zadacima/oblačići skriveni"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigacijska traka"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Uvijek prikaži traku zadataka"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Promijeni način navigacije"</string>
diff --git a/quickstep/res/values-hu/strings.xml b/quickstep/res/values-hu/strings.xml
index 184fef0..92ada47 100644
--- a/quickstep/res/values-hu/strings.xml
+++ b/quickstep/res/values-hu/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Alkalmazásjavaslatok engedélyezve"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Alkalmazásjavaslatok letiltva"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Várható alkalmazás: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Kézmozdulatokkal való navigáció útmutatója"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Forgassa el eszközét"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Forgassa el eszközét a kézmozdulatokkal való navigáció útmutatójának befejezéséhez"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Csúsztasson a képernyő jobb vagy bal széléről."</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Feladatsáv megjelenítve"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Feladatsáv és buborék balra"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Feladatsáv és buborék jobbra"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Feladatsáv elrejtve"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Rejtett feladatsáv és buborék"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigációs sáv"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Mindig megjelenő Feladatsáv"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Navigációs mód módosítása"</string>
diff --git a/quickstep/res/values-hy/strings.xml b/quickstep/res/values-hy/strings.xml
index 8da1a6d..7751ea1 100644
--- a/quickstep/res/values-hy/strings.xml
+++ b/quickstep/res/values-hy/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"«Առաջարկվող հավելվածներ» գործառույթը միացված է"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"«Առաջարկվող հավելվածներ» գործառույթն անջատված է"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Առաջարկվող հավելված՝ <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Ժեստերով նավիգացիայի ուղեցույց"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Պտտեք սարքը"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Պտտեք սարքը՝ ժեստերով նավիգացիայի ուղեցույցն ավարտելու համար"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Համոզվեք, որ մատը սահեցնում եք էկրանի աջ կամ ձախ եզրից"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Խնդրագոտին ցուցադրվում է"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Հավելվածների վահանակն ու ամպիկները տեսանելի են ձախ կողմում"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Հավելվածների վահանակն ու ամպիկները տեսանելի են աջ կողմում"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Խնդրագոտին թաքցված է"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Հավելվածների վահանակն ու ամպիկները թաքցված են"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Նավիգացիայի գոտի"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Միշտ ցույց տալ վահանակը"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Փոխել նավիգացիայի ռեժիմը"</string>
diff --git a/quickstep/res/values-in/strings.xml b/quickstep/res/values-in/strings.xml
index eb592db..1909376 100644
--- a/quickstep/res/values-in/strings.xml
+++ b/quickstep/res/values-in/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Saran aplikasi diaktifkan"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Saran aplikasi dinonaktifkan"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Aplikasi yang diprediksi: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Tutorial Navigasi Gestur"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Putar perangkat Anda"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Putar perangkat Anda untuk menyelesaikan tutorial navigasi gestur"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Pastikan Anda menggeser dari tepi ujung kanan atau ujung kiri"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Taskbar ditampilkan"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Taskbar & balon kiri ditampilkan"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Taskbar & bubbles kanan ditampilkan"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Taskbar disembunyikan"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Taskbar & balon disembunyikan"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Menu navigasi"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Selalu tampilkan Taskbar"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Ubah mode navigasi"</string>
diff --git a/quickstep/res/values-is/strings.xml b/quickstep/res/values-is/strings.xml
index 3500001..30d6dad 100644
--- a/quickstep/res/values-is/strings.xml
+++ b/quickstep/res/values-is/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Kveikt á tillögum að forritum"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Slökkt er á tillögðum forritum"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Tillaga að forriti: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Leiðsögn fyrir bendingastjórnun"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Snúðu tækinu"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Snúðu tækinu til að ljúka leiðsögn um bendingastjórnun"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Passaðu að strjúka frá jaðri hægri eða vinstri brúnar"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Forritastika sýnd"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Verkstika og blöðrur sýndar til vinstri"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Verkstika og blöðrur sýndar til hægri"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Forritastika falin"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Verkstika og blöðrur eru faldar"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Yfirlitsstika"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Alltaf sýna forritastiku"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Breyta leiðsagnarstillingu"</string>
diff --git a/quickstep/res/values-it/strings.xml b/quickstep/res/values-it/strings.xml
index b8fa813..7f80107 100644
--- a/quickstep/res/values-it/strings.xml
+++ b/quickstep/res/values-it/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"La funzionalità app suggerite è attiva"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"La funzionalità app suggerite è disattivata"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"App prevista: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Tutorial sulla navigazione tramite gesti"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Ruota il dispositivo"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Ruota il dispositivo per completare il tutorial relativo alla navigazione tramite gesti"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Assicurati di scorrere dal bordo all\'estrema destra o all\'estrema sinistra"</string>
@@ -92,7 +91,7 @@
<string name="allset_button_hint" msgid="2395219947744706291">"Tocca il pulsante Home per andare alla schermata Home"</string>
<string name="allset_description_generic" msgid="5385500062202019855">"Puoi iniziare a usare il tuo <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="default_device_name" msgid="6660656727127422487">"dispositivo"</string>
- <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Impostazioni Navigazione del sistema"</annotation></string>
+ <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Impostazioni di navigazione del sistema"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Condividi"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
<string name="action_split" msgid="2098009717623550676">"Dividi"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Barra delle app visualizzata"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Barra app e bolle most. sinis."</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Barra app e bolle most. destr."</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Barra delle app nascosta"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Barra app e bolle nascoste"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Barra di navigazione"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Mostra sempre barra app"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Cambia modalità di navigazione"</string>
diff --git a/quickstep/res/values-iw/strings.xml b/quickstep/res/values-iw/strings.xml
index c6fc845..f93f741 100644
--- a/quickstep/res/values-iw/strings.xml
+++ b/quickstep/res/values-iw/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"התכונה \'הצעות לאפליקציות\' מופעלת"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ההצעות לאפליקציות מושבתות"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"האפליקציות החזויות: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"מדריך לניווט באמצעות תנועות"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"צריך לסובב את המכשיר"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"צריך לסובב את המכשיר כדי להשלים את המדריך לניווט באמצעות תנועות"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"חשוב להחליק מהקצה השמאלי או הימני"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"סרגל האפליקציות מוצג"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"סרגל האפליקציות והבועות מוצגים משמאל"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"סרגל האפליקציות והבועות מוצגים מימין"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"סרגל האפליקציות מוסתר"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"סרגל האפליקציות והבועות הוסתרו"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"סרגל הניווט"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"סרגל האפליקציות מוצג תמיד"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"שינוי מצב הניווט"</string>
diff --git a/quickstep/res/values-ja/strings.xml b/quickstep/res/values-ja/strings.xml
index 5fdcf4a..e3625cb 100644
--- a/quickstep/res/values-ja/strings.xml
+++ b/quickstep/res/values-ja/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"アプリの候補表示が有効です"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"アプリの候補は無効です"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"予測されたアプリ: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"ジェスチャー ナビゲーションのチュートリアル"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"デバイスを回転してください"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"ジェスチャー ナビゲーションのチュートリアルを終了するには、デバイスを回転してください"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"右端または左端からスワイプしてください"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"タスクバー表示"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"タスクバーとバブルを表示"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"タスクバーとバブルを右側に表示"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"タスクバー非表示"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"タスクバーとバブルを非表示"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"ナビゲーション バー"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"常にタスクバーを表示する"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"ナビゲーション モードを変更"</string>
diff --git a/quickstep/res/values-ka/strings.xml b/quickstep/res/values-ka/strings.xml
index 01becd7..623319d 100644
--- a/quickstep/res/values-ka/strings.xml
+++ b/quickstep/res/values-ka/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"აპის შეთავაზებები ჩართულია"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"აპის შეთავაზებები გათიშულია"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"ნაწინასწარმეტყველები აპი: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"ჟესტებით ნავიგაციის სახელმძღვანელო"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"შეატრიალეთ მოწყობილობა"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"ჟესტებით ნავიგაციის სახელმძღვანელოს დასასრულებლად შეატრიალეთ თქვენი მოწყობილობა"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"გადაფურცლეთ უკიდურესი მარჯვენა ან მარცხენა ბოლოდან"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"ამოცანათა ზოლი ნაჩვენებია"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"ამოცანათა ზოლი და ბუშტები ჩანს მარცხნივ"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"ამოცანათა ზოლი და ბუშტები ჩანს მარჯვნივ"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"ამოცანათა ზოლი დამალულია"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"ამოცანათა ზოლი და ბუშტები დამალულია"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"ნავიგაციის ზოლი"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"ამოცანათა ზოლის მუდამ ჩვენება"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"შეცვალეთ ნავიგაციის რეჟიმი"</string>
diff --git a/quickstep/res/values-kk/strings.xml b/quickstep/res/values-kk/strings.xml
index 3280908..f707689 100644
--- a/quickstep/res/values-kk/strings.xml
+++ b/quickstep/res/values-kk/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"\"Қолданба ұсыныстары\" функциясы қосылды."</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"\"Қолданба ұсыныстары\" функциясы өшірулі."</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Болжалды қолданба: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Қимылмен басқару оқулығы"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Құрылғыны бұрыңыз"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Қимылмен басқару нұсқаулығын аяқтау үшін құрылғыны бұрыңыз."</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Экранның оң немесе сол жиегінен сырғытыңыз."</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Тапсырмалар жолағы көрсетілді"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Тапсырмалар жолағы мен қалқыма терезелер сол жақта көрсетілген"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Тапсырмалар жолағы мен қалқыма терезелер оң жақта көрсетілген"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Тапсырмалар жолағы жасырылды"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Тапсырмалар жолағы мен қалқыма терезелер жасырылған"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Навигация жолағы"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Тапсырма жолағын үнемі көрсету"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Навигация режимін өзгерту"</string>
diff --git a/quickstep/res/values-km/strings.xml b/quickstep/res/values-km/strings.xml
index d05ef16..25e1ef2 100644
--- a/quickstep/res/values-km/strings.xml
+++ b/quickstep/res/values-km/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"បានបើកការណែនាំកម្មវិធី"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"បានបិទការណែនាំកម្មវិធី"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"កម្មវិធីដែលបានព្យាករ៖ <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"មេរៀនអំពីការរុករកដោយប្រើចលនា"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"បង្វិលឧបករណ៍របស់អ្នក"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"សូមបង្វិលឧបករណ៍របស់អ្នក ដើម្បីបញ្ចប់មេរៀនអំពីការរុករកដោយប្រើចលនា"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"ត្រូវប្រាកដថាអ្នកអូសពីគែមខាងស្ដាំ ឬខាងឆ្វេង"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"បានបង្ហាញរបារកិច្ចការ"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"បានបង្ហាញរបារកិច្ចការ និងផ្ទាំងអណ្ដែតនៅខាងឆ្វេង"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"បានបង្ហាញរបារកិច្ចការ និងផ្ទាំងអណ្ដែតនៅខាងស្ដាំ"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"បានលាក់របារកិច្ចការ"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"បានលាក់របារកិច្ចការ និងផ្ទាំងអណ្ដែត"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"របាររុករក"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"បង្ហាញរបារកិច្ចការជានិច្ច"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"ប្ដូរមុខងាររុករក"</string>
diff --git a/quickstep/res/values-kn/strings.xml b/quickstep/res/values-kn/strings.xml
index 3240b2a..ed4b806 100644
--- a/quickstep/res/values-kn/strings.xml
+++ b/quickstep/res/values-kn/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"ಆ್ಯಪ್ ಸಲಹೆಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ಆ್ಯಪ್ ಸಲಹೆಗಳನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"ಶಿಫಾರಸು ಮಾಡಿದ ಆ್ಯಪ್: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"ಗೆಸ್ಚರ್ ನ್ಯಾವಿಗೇಶನ್ ಟುಟೋರಿಯಲ್"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"ನಿಮ್ಮ ಸಾಧನವನ್ನು ತಿರುಗಿಸಿ"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"ಗೆಸ್ಚರ್ ನ್ಯಾವಿಗೇಶನ್ ಟುಟೋರಿಯಲ್ ಅನ್ನು ಪೂರ್ಣಗೊಳಿಸಲು, ನಿಮ್ಮ ಸಾಧನವನ್ನು ತಿರುಗಿಸಿ"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"ನೀವು ಬಲಕೊನೆಯ ಅಂಚಿನಿಂದ ಅಥವಾ ಎಡಕೊನೆಯ ಅಂಚಿನಿಂದ ಸ್ವೈಪ್ ಮಾಡುತ್ತಿದ್ದೀರಿ ಎಂದು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಿ"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"ಟಾಸ್ಕ್ಬಾರ್ ತೋರಿಸಲಾಗಿದೆ"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"ಟಾಸ್ಕ್ಬಾರ್ & ಬಬಲ್ಸ್ ತೋರಿಸಲಾಗಿದೆ"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"ಟಾಸ್ಕ್ಬಾರ್ & ಬಬಲ್ಸ್ ಬಲಭಾಗದಲ್ಲಿ ತೋರಿಸಲಾಗಿದೆ"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"ಟಾಸ್ಕ್ಬಾರ್ ಮರೆಮಾಡಲಾಗಿದೆ"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"ಟಾಸ್ಕ್ಬಾರ್ & ಬಬಲ್ಸ್ ಮರೆಮಾಡಲಾಗಿದೆ"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"ನ್ಯಾವಿಗೇಷನ್ ಬಾರ್"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"ಯಾವಾಗಲೂ ಟಾಸ್ಕ್ಬಾರ್ ತೋರಿಸಿ"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"ನ್ಯಾವಿಗೇಶನ್ ಮೋಡ್ ಬದಲಾಯಿಸಿ"</string>
diff --git a/quickstep/res/values-ko/strings.xml b/quickstep/res/values-ko/strings.xml
index c8b8674..d11e0b2 100644
--- a/quickstep/res/values-ko/strings.xml
+++ b/quickstep/res/values-ko/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"앱 제안이 사용 설정됨"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"앱 제안이 사용 중지됨"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"예상 앱: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"동작 탐색 튜토리얼"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"기기를 회전시키세요"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"동작 탐색 튜토리얼을 완료하려면 기기를 회전시키세요."</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"오른쪽 또는 왼쪽 가장자리 끝에서 스와이프하세요."</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"태스크 바 표시"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"태스크 바와 대화창을 왼쪽에 표시"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"태스크 바와 대화창을 오른쪽에 표시"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"태스크 바 숨김"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"태스크 바 및 대화창 숨김"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"탐색 메뉴"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"태스크 바 항상 표시"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"탐색 모드 변경"</string>
diff --git a/quickstep/res/values-ky/strings.xml b/quickstep/res/values-ky/strings.xml
index 6dafcd0..3d59332 100644
--- a/quickstep/res/values-ky/strings.xml
+++ b/quickstep/res/values-ky/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Сунушталган колдонмолор функциясы иштетилди"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Сунушталган колдонмолор функциясы өчүрүлгөн"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Божомолдонгон колдонмо: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Жаңсап чабыттоо боюнча үйрөткүч"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Түзмөгүңүздү буруңуз"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Жаңсап чабыттоо үйрөткүчүнүн аягына чыгуу үчүн түзмөктү буруңуз"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Экранды эң четинен оңдон солго же солдон оңго карай сүрүңүз"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Тапшырмалар панели көрсөтүлдү"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Тапшырмалар панели, калкыма билдирмелер сол жакта көрсөтүлдү"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Тапшырмалар панели, калкыма билдирмелер оң жакта көрсөтүлгөн"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Тапшырмалар панели жашырылды"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Тапшырмалар панели жана калкып чыкма билдирмелер жашырылган"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Чабыттоо тилкеси"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Такта ар дайым көрүнсүн"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Өтүү режимин өзгөртүү"</string>
diff --git a/quickstep/res/values-lo/strings.xml b/quickstep/res/values-lo/strings.xml
index a13da69..e480766 100644
--- a/quickstep/res/values-lo/strings.xml
+++ b/quickstep/res/values-lo/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"ເປີດການນຳໃຊ້ການແນະນຳແອັບແລ້ວ"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ປິດການນຳໃຊ້ການແນະນຳແອັບແລ້ວ"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"ແອັບທີ່ຄາດເດົາໄວ້: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"ສອນການນຳໃຊ້ການນຳທາງແບບທ່າທາງ"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"ໝຸນອຸປະກອນຂອງທ່ານ"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"ກະລຸນາໝຸນອຸປະກອນຂອງທ່ານເພື່ອເຮັດຕາມການສອນການນຳໃຊ້ກ່ຽວກັບການນຳທາງແບບທ່າທາງໃຫ້ສຳເລັດ"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"ກະລຸນາກວດສອບວ່າທ່ານປັດຈາກຂອບຂວາສຸດ ຫຼື ຊ້າຍສຸດ"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"ແຖບໜ້າວຽກທີ່ສະແດງຢູ່"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"ແຖບໜ້າວຽກ ແລະ ຟອງສະແດງຢູ່ເບື້ອງຊ້າຍ"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"ແຖບໜ້າວຽກ ແລະ ຟອງສະແດງຢູ່ເບື້ອງຂວາ"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"ແຖບໜ້າວຽກທີ່ເຊື່ອງໄວ້ຢູ່"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"ແຖບໜ້າວຽກ ແລະ ຟອງຖືກເຊື່ອງໄວ້"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"ແຖບການນຳທາງ"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"ສະແດງແຖບໜ້າວຽກສະເໝີ"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"ປ່ຽນໂໝດການນຳທາງ"</string>
diff --git a/quickstep/res/values-lt/strings.xml b/quickstep/res/values-lt/strings.xml
index 7334b0a..2bcbb6d 100644
--- a/quickstep/res/values-lt/strings.xml
+++ b/quickstep/res/values-lt/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Siūlomų programų funkcija įgalinta"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Siūlomų programų funkcija išjungta"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Numatoma programa: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Naršymo gestais mokomoji medžiaga"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Pasukite įrenginį"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Pasukite įrenginį, kad pereitumėte į naršymo gestais mokomąją medžiagą"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Turite perbraukti nuo dešiniojo ar kairiojo krašto"</string>
@@ -90,7 +89,7 @@
<string name="allset_title" msgid="5021126669778966707">"Paruošta!"</string>
<string name="allset_hint" msgid="459504134589971527">"Perbraukite aukštyn, kad grįžtumėte į pagrindinį ekraną"</string>
<string name="allset_button_hint" msgid="2395219947744706291">"Norėdami eiti į pagrindinį ekraną, palieskite pagrindinio ekrano mygtuką"</string>
- <string name="allset_description_generic" msgid="5385500062202019855">"Esate pasirengę pradėti naudoti <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
+ <string name="allset_description_generic" msgid="5385500062202019855">"Jau parengta naudoti: <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="default_device_name" msgid="6660656727127422487">"įrenginys"</string>
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Sistemos naršymo nustatymai"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Bendrinti"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Užduočių juosta rodoma"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Užduočių juosta ir burbulai rodomi kairėje"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Užduočių juosta ir burbulai rodomi dešinėje"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Užduočių juosta paslėpta"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Užduočių juosta ir burbulai paslėpti"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Naršymo juosta"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Visada rodyti užduočių juostą"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Keisti naršymo režimą"</string>
diff --git a/quickstep/res/values-lv/strings.xml b/quickstep/res/values-lv/strings.xml
index bbf45c3..7d88140 100644
--- a/quickstep/res/values-lv/strings.xml
+++ b/quickstep/res/values-lv/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Ieteicamās lietotnes ir iespējotas"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Ieteicamās lietotnes ir atspējotas"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Prognozētā lietotne: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Žestu navigācijas pamācība"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Pagrieziet ierīci"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Lūdzu, pagrieziet savu ierīci, lai pabeigtu žestu navigācijas apmācību."</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Jāvelk no pašas labās vai kreisās malas."</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Uzdevumu josla tiek rādīta"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Uzdevumu josla/burbuļi pa kreisi"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Uzdevumu josla/burbuļi pa labi"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Uzdevumu josla paslēpta"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Uzdevumu josla/burbuļi paslēpti"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigācijas josla"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Vienmēr rādīt uzdevumu joslu"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Mainīt navigācijas režīmu"</string>
diff --git a/quickstep/res/values-mk/strings.xml b/quickstep/res/values-mk/strings.xml
index 5340854..632196b 100644
--- a/quickstep/res/values-mk/strings.xml
+++ b/quickstep/res/values-mk/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Предлозите за апликации се овозможени"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Предлозите за апликации се оневозможени"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Предвидена апликација: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Упатство за навигација со движења"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Ротирајте го уредот"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Ротирајте го уредот за да го завршите упатството за навигација со движење"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Повлечете од крајниот десен или крајниот лев раб"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Лентата со задачи е прикажана"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Лен. со зад. и бал. се лево"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Лен. со зад. и бал. се десно"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Лентата со задачи е скриена"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Лен. со зад. и бал. се скриени"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Лента за навигација"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Секогаш прикажувај „Лента“"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Променете режим на навигација"</string>
diff --git a/quickstep/res/values-ml/strings.xml b/quickstep/res/values-ml/strings.xml
index d92ddbc..2246889 100644
--- a/quickstep/res/values-ml/strings.xml
+++ b/quickstep/res/values-ml/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"ആപ്പ് നിർദ്ദേശങ്ങൾ പ്രവർത്തനക്ഷമമാക്കി"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ആപ്പ് നിർദ്ദേശങ്ങൾ പ്രവർത്തനരഹിതമാക്കി"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"പ്രവചിച്ച ആപ്പ്: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"ജെസ്ച്ചർ നാവിഗേഷൻ ട്യൂട്ടോറിയൽ"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"നിങ്ങളുടെ ഉപകരണം റൊട്ടേറ്റ് ചെയ്യുക"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"ജെസ്ച്ചർ നാവിഗേഷൻ ട്യൂട്ടോറിയൽ പൂർത്തിയാക്കാൻ നിങ്ങളുടെ ഉപകരണം റൊട്ടേറ്റ് ചെയ്യുക"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"വലത്തേയറ്റത്തെയോ ഇടത്തേയറ്റത്തെയോ അരികിൽ നിന്നാണ് സ്വെെപ്പ് ചെയ്യുന്നതെന്ന് ഉറപ്പാക്കുക"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"ടാസ്ക്ബാർ കാണിച്ചിരിക്കുന്നു"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"ടാസ്ക്ബാറും ബബിളും ഇടതുവശത്ത്"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"ടാസ്ക്ബാറും ബബിളും വലതുവശത്ത്"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"ടാസ്ക്ബാർ മറച്ചിരിക്കുന്നു"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"ടാസ്ക്ബാറും ബബിളും മറച്ചു"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"നാവിഗേഷൻ ബാർ"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"ടാസ്ക്ബാർ എപ്പോഴും കാണിക്കൂ"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"നാവിഗേഷൻ മോഡ് മാറ്റുക"</string>
diff --git a/quickstep/res/values-mn/strings.xml b/quickstep/res/values-mn/strings.xml
index 2b335a9..94adf88 100644
--- a/quickstep/res/values-mn/strings.xml
+++ b/quickstep/res/values-mn/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Санал болгож буй аппуудыг идэвхжүүлсэн"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Санал болгож буй аппуудыг идэвхгүй болгосон"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Таамаглаж буй апп: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Зангааны навигацын заавар"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Төхөөрөмжөө эргүүлэх"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Зангааны навигацын практик хичээлийг дуусгахын тулд төхөөрөмжөө эргүүлнэ үү"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Та баруун зах эсвэл зүүн захын ирмэгээс шударна уу"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Ажлын хэсгийг харуулсан"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Ажлын хэсэг, бөмбөлгийг зүүн талд харуулсан"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Ажлын хэсэг, бөмбөлгийг баруун талд харуулсан"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Ажлын хэсгийг нуусан"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Ажлын хэсэг, бөмбөлгийг нуусан"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Навигацын самбар"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Ажлын хэсгийг үргэлж харуулах"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Навигацын горимыг өөрчлөх"</string>
diff --git a/quickstep/res/values-mr/strings.xml b/quickstep/res/values-mr/strings.xml
index 647e88d..d2fdb42 100644
--- a/quickstep/res/values-mr/strings.xml
+++ b/quickstep/res/values-mr/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"अॅप सूचना सुरू केल्या आहेत"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"अॅप सूचना बंद केल्या आहेत"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"पूर्वानुमान केलेले अॅप: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"जेश्चर नेव्हिगेशन ट्यूटोरियल"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"तुमचे डिव्हाइस फिरवा"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"कृपया जेश्चर नेव्हिगेशन ट्यूटोरियल पूर्ण करण्यासाठी तुमचे डिव्हाइस फिरवा"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"तुम्ही स्क्रीनच्या अगदी उजव्या किंवा अगदी डाव्या कडेपासून स्वाइप करत आहात खात्री करा"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"टास्कबार दाखवलेला आहे"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"टास्कबार आणि डावे बबल दाखवले"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"टास्कबार आणि उजवे बबल लपवले"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"टास्कबार लपवलेले आहे"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"टास्कबार आणि बबल लपवले आहेत"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"नेव्हिगेशन बार"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"नेहमी टास्कबार दाखवा"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"नेव्हिगेशन मोड बदला"</string>
diff --git a/quickstep/res/values-ms/strings.xml b/quickstep/res/values-ms/strings.xml
index baffcbb..66c5bdb 100644
--- a/quickstep/res/values-ms/strings.xml
+++ b/quickstep/res/values-ms/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Cadangan apl didayakan"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Cadangan apl dilumpuhkan"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Apl yang diramalkan: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Tutorial Navigasi Gerak Isyarat"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Putarkan peranti anda"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Sila putarkan peranti anda untuk melengkapkan tutorial navigasi gerak isyarat"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Pastikan anda meleret dari hujung sebelah kanan atau hujung sebelah kiri"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Bar Tugas dipaparkan"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Bar Tugas & gelembung dipaparkan di sebelah kiri"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Bar Tugas & gelembung dipaparkan di sebelah kanan"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Bar Tugas disembunyikan"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Bar Tugas & gelembung disembunyikan"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Bar navigasi"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Papar Bar Tugas selalu"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Tukar mod navigasi"</string>
diff --git a/quickstep/res/values-my/strings.xml b/quickstep/res/values-my/strings.xml
index f89920b..c9ac441 100644
--- a/quickstep/res/values-my/strings.xml
+++ b/quickstep/res/values-my/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"အက်ပ်အကြံပြုချက်များ ဖွင့်ထားသည်"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"အက်ပ်အကြံပြုချက်များကို ပိတ်ထားသည်"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"ကြိုတင်မှန်းဆထားသော အက်ပ်− <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"လက်ဟန်ဖြင့် လမ်းညွှန်ခြင်း အသုံးပြုနည်း"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"သင့်စက်ကို လှည့်ပါ"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"လက်ဟန်ဖြင့် လမ်းညွှန်ခြင်း ရှင်းလင်းပို့ချချက်အား အပြီးသတ်ရန် သင့်စက်ကို လှည့်ပါ"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"ညာ (သို့) ဘယ်ဘက်အစွန်ဆုံးမှ ပွတ်ဆွဲကြောင်း သေချာပါစေ"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Taskbar ပြထားသည်"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"ဘယ်ဘက်၌ Taskbar နှင့် ပူဖောင်းကွက် ပြထားသည်"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"ညာဘက်၌ Taskbar နှင့် ပူဖောင်းကွက် ပြထားသည်"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Taskbar ဖျောက်ထားသည်"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Taskbar နှင့် ပူဖောင်းကွက် ဖျောက်ထားသည်"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"လမ်းညွှန်ဘား"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Taskbar အမြဲပြရန်"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"ရွှေ့ကြည့်သည့်မုဒ် ပြောင်းရန်"</string>
diff --git a/quickstep/res/values-nb/strings.xml b/quickstep/res/values-nb/strings.xml
index 3be8d68..aa19606 100644
--- a/quickstep/res/values-nb/strings.xml
+++ b/quickstep/res/values-nb/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Appforslag er på"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Appforslag er slått av"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Foreslått app: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Veiledning for navigasjon med bevegelser"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Roter enheten"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Roter enheten for å fullføre veiledningen for navigasjon med bevegelser"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Sørg for at du sveiper fra kanten helt til høyre eller venstre"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Oppgavelinjen vises"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Venstre oppgavelinje/boble vises"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Høyre oppgavelinje/bobler vises"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Oppgavelinjen er skjult"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Oppgavelinje og bobler skjult"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigasjonsrad"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Vis alltid oppgavelinjen"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Endre navigasjonsmodus"</string>
diff --git a/quickstep/res/values-ne/strings.xml b/quickstep/res/values-ne/strings.xml
index 4ed8bb9..4793ad3 100644
--- a/quickstep/res/values-ne/strings.xml
+++ b/quickstep/res/values-ne/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"सिफारिस गरिएका एप देखाउने सुविधा अन गरिएको छ"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"सिफारिस गरिएका एपहरू देखाउने सुविधा असक्षम पारिएको छ"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"पूर्वानुमान गरिएको एप: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"जेस्चर नेभिगेसनसम्बन्धी ट्युटोरियल"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"आफ्नो डिभाइस रोटेट गर्नुहोस्"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"जेस्चर नेभिगेसनको ट्युटोरियल पूरा गर्न कृपया आफ्नो डिभाइस रोटेट गर्नुहोस्"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"स्क्रिनको सबैभन्दा दायाँ किनारा वा सबैभन्दा बायाँ किनाराबाट स्वाइप गर्नुहोस्"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"टास्कबार देखाइएको छ"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"टास्कबार र बबल बार बायाँतिर देखाइएका छन्"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"टास्कबार र बबल बार दायाँतिर देखाइएका छन्"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"टास्कबार लुकाइएको छ"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"टास्कबार र बबल बार लुकाइएका छन्"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"नेभिगेसन बार"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"टास्कबार सधैँ देखाउनुहोस्"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"नेभिगेसन मोड बदल्नुहोस्"</string>
diff --git a/quickstep/res/values-nl/strings.xml b/quickstep/res/values-nl/strings.xml
index c1c1a22..3cd9457 100644
--- a/quickstep/res/values-nl/strings.xml
+++ b/quickstep/res/values-nl/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"App-suggesties staan aan"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"App-suggesties staan uit"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Voorspelde app: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Tutorial voor navigatie met gebaren"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Het apparaat draaien"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Draai het apparaat om de tutorial voor navigatie met gebaren af te ronden"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Swipe vanaf de rechter- of linkerrand"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Taakbalk wordt getoond"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Taakbalk en bubbels links getoond"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Taakbalk en bubbels rechts getoond"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Taakbalk is verborgen"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Taakbalk en bubbels verborgen"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigatiebalk"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Taakbalk altijd tonen"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Navigatiemodus wijzigen"</string>
diff --git a/quickstep/res/values-or/strings.xml b/quickstep/res/values-or/strings.xml
index 789ba0a..20420d9 100644
--- a/quickstep/res/values-or/strings.xml
+++ b/quickstep/res/values-or/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"ଆପ୍ ପରାମର୍ଶଗୁଡ଼ିକୁ ସକ୍ଷମ କରାଯାଇଛି"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ଆପ୍ ପରାମର୍ଶଗୁଡ଼ିକୁ ଅକ୍ଷମ କରାଯାଇଛି"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"ପୂର୍ବାନୁମାନ କରାଯାଇଥିବା ଆପ୍: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"ଜେଶ୍ଚର ନାଭିଗେସନ ଟ୍ୟୁଟୋରିଆଲ"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"ଆପଣଙ୍କ ଡିଭାଇସକୁ ରୋଟେଟ କରନ୍ତୁ"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"ଜେଶ୍ଚର ନାଭିଗେସନ ଟ୍ୟୁଟୋରିଆଲ ସମ୍ପୂର୍ଣ୍ଣ କରିବାକୁ ଦୟାକରି ଆପଣଙ୍କ ଡିଭାଇସ ରୋଟେଟ କରନ୍ତୁ"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"ଆପଣ ସ୍କ୍ରିନର ଏକଦମ-ଡାହାଣ ବା ବାମ ଧାରରୁ ସ୍ୱାଇପ କରୁଥିବା ସୁନିଶ୍ଚିତ କରନ୍ତୁ।"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"ଟାସ୍କବାର ଦେଖାଯାଇଛି"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"ଟାସ୍କବାର ଓ ବବଲ ବାମରେ ଦେଖାଯାଇଛି"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"ଟାସ୍କବାର ଓ ବବଲ ଡାହାଣରେ ଦେଖାଯାଇଛି"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"ଟାସ୍କବାର ଲୁଚାଯାଇଛି"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"ଟାସ୍କବାର ଓ ବବଲ ଲୁଚାଯାଇଛି"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"ନାଭିଗେସନ ବାର"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"ସର୍ବଦା ଟାସ୍କବାର ଦେଖାନ୍ତୁ"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"ନାଭିଗେସନ ମୋଡ ପରିବର୍ତ୍ତନ କରନ୍ତୁ"</string>
diff --git a/quickstep/res/values-pa/strings.xml b/quickstep/res/values-pa/strings.xml
index 2392890..5cab154 100644
--- a/quickstep/res/values-pa/strings.xml
+++ b/quickstep/res/values-pa/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"ਐਪ ਸੁਝਾਵਾਂ ਨੂੰ ਚਾਲੂ ਕੀਤਾ ਗਿਆ"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ਐਪ ਸੁਝਾਵਾਂ ਨੂੰ ਬੰਦ ਕੀਤਾ ਗਿਆ"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"ਪੂਰਵ ਅਨੁਮਾਨਿਤ ਐਪ: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"ਇਸ਼ਾਰਾ ਨੈਵੀਗੇਸ਼ਨ ਸੰਬੰਧੀ ਟਿਊਟੋਰੀਅਲ"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"ਆਪਣੇ ਡੀਵਾਈਸ ਨੂੰ ਘੁੰਮਾਓ"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"ਕਿਰਪਾ ਕਰਕੇ ਇਸ਼ਾਰਾ ਨੈਵੀਗੇਸ਼ਨ ਟਿਊਟੋਰੀਅਲ ਨੂੰ ਪੂਰਾ ਕਰਨ ਲਈ ਆਪਣੇ ਡੀਵਾਈਸ ਨੂੰ ਘੁੰਮਾਓ"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"ਇਹ ਪੱਕਾ ਕਰੋ ਕਿ ਤੁਸੀਂ ਸੱਜੇ ਜਾਂ ਖੱਬੇ ਪਾਸੇ ਦੇ ਬਿਲਕੁਲ ਕਿਨਾਰੇ ਤੋਂ ਸਵਾਈਪ ਕਰਦੇ ਹੋ"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"ਟਾਸਕਬਾਰ ਨੂੰ ਦਿਖਾਇਆ ਗਿਆ"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"ਟਾਸਕਬਾਰ ਤੇ ਬਬਲ ਨੂੰ ਖੱਬੇ ਦਿਖਾਇਆ"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"ਟਾਸਕਬਾਰ ਤੇ ਬਬਲ ਨੂੰ ਸੱਜੇ ਦਿਖਾਇਆ"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"ਟਾਸਕਬਾਰ ਨੂੰ ਲੁਕਾਇਆ ਗਿਆ"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"ਟਾਸਕਬਾਰ ਅਤੇ ਬਬਲ ਨੂੰ ਲੁਕਾਇਆ ਗਿਆ"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"ਨੈਵੀਗੇਸ਼ਨ ਵਾਲੀ ਪੱਟੀ"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"ਹਮੇਸ਼ਾਂ ਟਾਸਕਬਾਰ ਦਿਖਾਓ"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"ਨੈਵੀਗੇਸ਼ਨ ਮੋਡ ਬਦਲੋ"</string>
diff --git a/quickstep/res/values-pl/strings.xml b/quickstep/res/values-pl/strings.xml
index 74aec66..94e39d0 100644
--- a/quickstep/res/values-pl/strings.xml
+++ b/quickstep/res/values-pl/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Włączono sugestie aplikacji"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Sugestie aplikacji są wyłączone"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Przewidywana aplikacja: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Samouczek dotyczący nawigacji przy użyciu gestów"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Obróć urządzenie"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Obróć urządzenie, aby ukończyć samouczek nawigacji przy użyciu gestów"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Pamiętaj, aby przesuwać palcem od samej krawędzi (prawej lub lewej)"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Pasek aplikacji widoczny"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Pasek i dymki po lewej"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Pasek i dymki po prawej"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Pasek aplikacji ukryty"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Pasek i dymki są ukryte"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Pasek nawigacyjny"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Zawsze pokazuj pasek aplikacji"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Zmień tryb nawigacji"</string>
diff --git a/quickstep/res/values-pt-rPT/strings.xml b/quickstep/res/values-pt-rPT/strings.xml
index 56d5514..cace987 100644
--- a/quickstep/res/values-pt-rPT/strings.xml
+++ b/quickstep/res/values-pt-rPT/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Sugestões de apps ativadas"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"As sugestões de apps estão desativadas"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"App prevista: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Tutorial da navegação por gestos"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rode o dispositivo"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Rode o seu dispositivo para concluir o tutorial de navegação por gestos"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Deslize a partir da extremidade mais à direita ou mais à esquerda"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Barra de tarefas apresentada"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Barra de tarefas/balões à esq."</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Barra de tarefas/balões à dir."</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Barra de tarefas ocultada"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Barra tarefas/balões ocultos"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Barra de navegação"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Ver sempre Barra de tarefas"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Alterar modo de navegação"</string>
diff --git a/quickstep/res/values-pt/strings.xml b/quickstep/res/values-pt/strings.xml
index bdcad13..434cf6b 100644
--- a/quickstep/res/values-pt/strings.xml
+++ b/quickstep/res/values-pt/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"O recurso \"sugestões de apps\" está ativado"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"O recurso \"sugestões de apps\" está desativado"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"App previsto: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Tutorial da navegação por gestos"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Gire o dispositivo"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Gire o dispositivo para concluir o tutorial da navegação por gestos"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Deslize da borda direita ou esquerda"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Barra de tarefas visível"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Barra de tar. e balões à esq."</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Barra de tar. e balões à dir."</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Barra de tarefas oculta"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Barra de tar. e balões ocultos"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Barra de navegação"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Sempre mostrar a Barra de tarefas"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Mudar o modo de navegação"</string>
diff --git a/quickstep/res/values-ro/strings.xml b/quickstep/res/values-ro/strings.xml
index 6a05909..ac25e6d 100644
--- a/quickstep/res/values-ro/strings.xml
+++ b/quickstep/res/values-ro/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Sugestiile de aplicații au fost activate"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Sugestiile de aplicații au fost dezactivate"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Aplicația estimată: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Tutorial de navigare prin gesturi"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rotește dispozitivul"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Rotește dispozitivul pentru a încheia tutorialul de navigare prin gesturi"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Glisează dinspre marginea dreaptă îndepărtată sau dinspre marginea stângă îndepărtată"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Bara de activități este afișată"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Bară și baloane stânga afișate"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Bară & baloane dreapta afișate"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Bara de activități este ascunsă"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Bară și baloane ascunse"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Bară de navigare"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Afișează mereu bara"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Schimbă modul de navigare"</string>
diff --git a/quickstep/res/values-ru/strings.xml b/quickstep/res/values-ru/strings.xml
index 69f560d..9700f16 100644
--- a/quickstep/res/values-ru/strings.xml
+++ b/quickstep/res/values-ru/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Функция \"Рекомендуемые приложения\" включена."</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Функция \"Рекомендуемые приложения\" отключена."</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Рекомендуемое приложение: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Руководство: навигация с помощью жестов"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Поверните устройство"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Чтобы перейти к руководству по жестам, нужно повернуть устройство."</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Проведите справа налево или слева направо от самого края экрана."</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Панель задач показана"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Слева панель задач, подсказки"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Справа панель задач, подсказки"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Панель задач скрыта"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Скрыты панель задач, подсказки"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Панель навигации"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Всегда показывать панель задач"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Изменить режим навигации"</string>
diff --git a/quickstep/res/values-si/strings.xml b/quickstep/res/values-si/strings.xml
index 2b1f147..cd33d09 100644
--- a/quickstep/res/values-si/strings.xml
+++ b/quickstep/res/values-si/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"යෙදුම් යෝජනා සබලිතයි"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"යෙදුම් යෝජනා අබල කර ඇත"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"පුරෝකථනය කළ යෙදුම: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"අභින සංචාලන නිබන්ධනය"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"ඔබේ උපාංගය කරකවන්න"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"අභින සංචාලන නිබන්ධනය සම්පූර්ණ කිරීම සඳහා ඔබේ උපාංගය කරකවන්න"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"ඔබ ඈත දකුණු හෝ ඈත වම් දාරයේ සිට ස්වයිප් කරන බව සහතික කර ගන්න"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"කාර්ය තීරුව පෙන්වා ඇත"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"කාර්ය තීරුව සහ බුබුළු පෙන්වා ඇත"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"කාර්ය තීරුව සහ බුබුළු දකුණට පෙන්වා ඇත"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"කාර්ය තීරුව සඟවා ඇත"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"කාර්ය තීරුව සහ බුබුළු සඟවා ඇත"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"සංචලන තීරුව"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"සෑම විටම කාර්ය තීරුව පෙන්වන්න"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"සංචාලන ප්රකාරය වෙනස් කරන්න"</string>
diff --git a/quickstep/res/values-sk/strings.xml b/quickstep/res/values-sk/strings.xml
index c2e6b85..7f44a23 100644
--- a/quickstep/res/values-sk/strings.xml
+++ b/quickstep/res/values-sk/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Návrhy aplikácií zapnuté"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Návrhy aplikácií vypnuté"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predpovedaná aplikácia: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Návod na navigáciu gestami"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Otočte zariadenie"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Otočte zariadenie a dokončite tak návod, ako navigovať gestami"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Musíte potiahnuť úplne z pravého alebo ľavého okraja."</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Panel aplikácií je zobrazený"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Panel aplik. a bubl. sú vľavo"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Panel aplik. a bubl. sú vpravo"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Panel aplikácií je skrytý"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Panel aplik. a bubl. sú skryté"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigačný panel"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Zobrazovať panel aplikácií"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Zmeniť režim navigácie"</string>
diff --git a/quickstep/res/values-sl/strings.xml b/quickstep/res/values-sl/strings.xml
index 0922f24..f504abc 100644
--- a/quickstep/res/values-sl/strings.xml
+++ b/quickstep/res/values-sl/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Predlogi aplikacij so omogočeni."</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Predlogi aplikacij so onemogočeni."</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predvidena aplikacija: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Vadnica za krmarjenje s potezami"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Zasukajte napravo"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Zasukajte napravo, če si želite ogledati vadnico za krmarjenje s potezami"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Pazite, da povlečete s skrajno desnega ali skrajno levega roba."</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Opravilna vrstica je prikazana"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Prikazani so opravilna vrstica in oblački na levi"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Prikazani so opravilna vrstica in oblački na desni"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Opravilna vrstica je skrita"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Opravilna vrstica in oblački so skriti"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Vrstica za krmarjenje"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Stalen prikaz oprav. vrstice"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Spreminjanje načina navigacije"</string>
diff --git a/quickstep/res/values-sq/strings.xml b/quickstep/res/values-sq/strings.xml
index 50b9fa1..36769cb 100644
--- a/quickstep/res/values-sq/strings.xml
+++ b/quickstep/res/values-sq/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Aplikacionet e sugjeruara janë aktivizuar"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Sugjerimet e aplikacioneve janë çaktivizuar"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Aplikacioni i parashikuar: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Udhëzuesi për navigimin me gjeste"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rrotullo pajisjen"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Rrotullo pajisjen për të përfunduar udhëzuesin e navigimit me gjeste"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Sigurohu që të rrëshqasësh shpejt nga skaji më i djathtë ose më i majtë"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Shiriti i detyrave i shfaqur"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Shiriti i detyrave dhe flluskat majtas janë shfaqur"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Shiriti i detyrave dhe flluskat djathtas janë shfaqur"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Shiriti i detyrave i fshehur"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Shiriti i detyrave dhe flluskat janë fshehur"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Shiriti i navigimit"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Shfaq gjithmonë shiritin e detyrave"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Ndrysho modalitetin e navigimit"</string>
diff --git a/quickstep/res/values-sr/strings.xml b/quickstep/res/values-sr/strings.xml
index a20e927..0d89f74 100644
--- a/quickstep/res/values-sr/strings.xml
+++ b/quickstep/res/values-sr/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Предлози апликација су омогућени"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Предлози апликација су онемогућени"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Предвиђамо апликацију: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Водич за навигацију помоћу покрета"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Ротирајте уређај"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Ротирајте уређај да бисте довршили водич за навигацију помоћу покрета"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Обавезно превуците од саме десне или леве ивице"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Трака задатака је приказана"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Приказ задатака/облачића лево"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Приказ задатака/облачића десно"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Трака задатака је скривена"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Скривени задаци/облачићи"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Трака за навигацију"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Увек приказуј траку задатака"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Промени режим навигације"</string>
diff --git a/quickstep/res/values-sv/strings.xml b/quickstep/res/values-sv/strings.xml
index 7c826cd..9941818 100644
--- a/quickstep/res/values-sv/strings.xml
+++ b/quickstep/res/values-sv/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Appförslag har aktiverats"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Appförslag har inaktiverats"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Appförslag: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Guide för navigering med rörelser"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rotera enheten"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Rotera enheten för att slutföra guiden för navigering med rörelser"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Se till att du sveper ända från högerkanten eller vänsterkanten"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Aktivitetsfältet visas"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Vänster fält och bubblor visas"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Höger fält och bubblor visas"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Aktivitetsfältet är dolt"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Fält och bubblor dolda"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigeringsfält"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Visa alltid aktivitetsfältet"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Ändra navigeringsläge"</string>
diff --git a/quickstep/res/values-sw/strings.xml b/quickstep/res/values-sw/strings.xml
index 9181599..c7e5c30 100644
--- a/quickstep/res/values-sw/strings.xml
+++ b/quickstep/res/values-sw/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Mapendekezo ya programu yamewashwa"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Umezima mapendekezo ya programu"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Programu iliyotabiriwa: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Mafunzo ya Usogezaji kwa Kutumia Ishara"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Zungusha kifaa chako"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Tafadhali zungusha kifaa chako ili ukamilishe mafunzo ya usogezaji kwa kutumia ishara"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Hakikisha unatelezesha kidole kutoka ukingo wa kulia au kushoto kabisa"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Upauzana umeonyeshwa"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Upauzana na viputo vinaonyeshwa kushoto"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Upauzana na viputo vinaonyeshwa kulia"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Upauzana umefichwa"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Upauzana na viputo vimefichwa"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Sehemu ya viungo muhimu"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Onyesha Zana kila wakati"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Badilisha hali ya usogezaji"</string>
diff --git a/quickstep/res/values-ta/strings.xml b/quickstep/res/values-ta/strings.xml
index 4e3a075..042679c 100644
--- a/quickstep/res/values-ta/strings.xml
+++ b/quickstep/res/values-ta/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"ஆப்ஸ் பரிந்துரைகள் இயக்கப்பட்டுள்ளன"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ஆப்ஸ் பரிந்துரைகள் முடக்கப்பட்டுள்ளன"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"கணித்த ஆப்ஸ்: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"சைகை வழிசெலுத்தலுக்கான பயிற்சி"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"உங்கள் சாதனத்தைச் சுழற்றுங்கள்"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"சைகை வழிசெலுத்தல் பயிற்சியை நிறைவுசெய்ய உங்கள் சாதனத்தைச் சுழற்றுங்கள்"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"வலது அல்லது இடது ஓரத்தின் விளிம்பிலிருந்து ஸ்வைப் செய்வதை உறுதிசெய்துகொள்ளுங்கள்"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"செயல் பட்டி காட்டப்படுகிறது"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"செயல் பட்டி & குமிழை இடதுபுறம் காட்டும்"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"செயல் பட்டி & குமிழை வலதுபுறம் காட்டும்"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"செயல் பட்டி மறைக்கப்பட்டுள்ளது"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"செயல் பட்டி & குமிழை மறைக்கும்"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"வழிசெலுத்தல் பட்டி"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"செயல் பட்டியை எப்போதும் காட்டு"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"வழிசெலுத்தல் பயன்முறையை மாற்று"</string>
diff --git a/quickstep/res/values-te/strings.xml b/quickstep/res/values-te/strings.xml
index 94fac1b..081708c 100644
--- a/quickstep/res/values-te/strings.xml
+++ b/quickstep/res/values-te/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"యాప్ సలహాలు ఎనేబుల్ చేయబడ్డాయి"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"యాప్ సూచనలు డిజేబుల్ చేయబడ్డాయి"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"సూచించబడిన యాప్: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"సంజ్ఞ నావిగేషన్ ట్యుటోరియల్"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"మీ పరికరాన్ని రొటేట్ చేయండి"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"సంజ్ఞ నావిగేషన్ ట్యుటోరియల్ను పూర్తి చేయడానికి దయచేసి మీ పరికరాన్ని రొటేట్ చేయండి"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"కుడి వైపు చిట్ట చివరి లేదా ఎడమ వైపు చిట్ట చివరి అంచు నుండి స్వైప్ చేస్తున్నారని నిర్ధారించుకోండి"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"టాస్క్బార్ చూపబడింది"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"టాస్క్బార్, బబుల్స్ ఎడమవైపున చూపబడ్డాయి"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"టాస్క్బార్, బబుల్స్ కుడివైపున చూపబడ్డాయి"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"టాస్క్బార్ దాచబడింది"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"టాస్క్బార్, బబుల్స్ దాచబడినవి"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"నావిగేషన్ బార్"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"టాస్క్బార్ను నిరంతరం చూపండి"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"నావిగేషన్ మోడ్ను మార్చండి"</string>
diff --git a/quickstep/res/values-th/strings.xml b/quickstep/res/values-th/strings.xml
index ee515b9..61dfaa6 100644
--- a/quickstep/res/values-th/strings.xml
+++ b/quickstep/res/values-th/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"เปิดใช้แอปแนะนำแล้ว"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ปิดใช้คำแนะนำเกี่ยวกับแอปอยู่"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"แอปที่คาดว่าจะใช้: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"บทแนะนำการไปยังส่วนต่างๆ ด้วยท่าทางสัมผัส"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"หมุนอุปกรณ์ของคุณ"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"โปรดหมุนอุปกรณ์เพื่อทำตามบทแนะนำการนำทางด้วยท่าทางสัมผัสให้เสร็จสมบูรณ์"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"ปัดจากขอบด้านขวาสุดหรือซ้ายสุด"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"แถบงานแสดงอยู่"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"แถบงานและบับเบิลแสดงไว้ทางซ้าย"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"แถบงานและบับเบิลแสดงไว้ทางขวา"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"แถบงานซ่อนอยู่"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"แถบงานและบับเบิลซ่อนอยู่"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"แถบนำทาง"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"แสดงแถบงานเสมอ"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"เปลี่ยนโหมดการนําทาง"</string>
diff --git a/quickstep/res/values-tl/strings.xml b/quickstep/res/values-tl/strings.xml
index 1bc4a3d..8374628 100644
--- a/quickstep/res/values-tl/strings.xml
+++ b/quickstep/res/values-tl/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Naka-enable ang mga iminumungkahing app"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Naka-disable ang mga iminumungkahing app"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Hinulaang app: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Tutorial sa Navigation gamit ang Galaw"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"I-rotate ang iyong device"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Paki-rotate ang iyong device para tapusin ang tutorial sa navigation gamit ang galaw"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Tiyaking magsa-swipe ka mula sa dulong kanan o dulong kaliwang gilid"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Ipinapakita ang taskbar"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Taskbar at bubble sa kaliwa"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Taskbar at bubble sa kanan"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Nakatago ang taskbar"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Nakatago ang taskbar at bubble"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigation bar"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Ipakita lagi ang Taskbar"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Magpalit ng navigation mode"</string>
diff --git a/quickstep/res/values-tr/strings.xml b/quickstep/res/values-tr/strings.xml
index 691e6c0..9a1bf54 100644
--- a/quickstep/res/values-tr/strings.xml
+++ b/quickstep/res/values-tr/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Uygulama önerileri etkinleştirildi"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Uygulama önerileri devre dışı bırakıldı"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Tahmin edilen uygulama: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Hareketle Gezinme Eğitimi"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Cihazınızı döndürün"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Hareketle gezinme eğitimini tamamlamak için lütfen cihazınızı döndürün"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"En sağ veya en sol kenardan kaydırdığınızdan emin olun"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Görev çubuğu gösteriliyor"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Görev çubuğu ve baloncuklar solda gösteriliyor"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Görev çubuğu ve baloncuklar sağda gösteriliyor"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Görev çubuğu gizlendi"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Görev çubuğu ve baloncuklar gizli"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Gezinme çubuğu"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Görev çubuğunu daima göster"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Gezinme modunu değiştir"</string>
diff --git a/quickstep/res/values-uk/strings.xml b/quickstep/res/values-uk/strings.xml
index 1668b4f..1036700 100644
--- a/quickstep/res/values-uk/strings.xml
+++ b/quickstep/res/values-uk/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Рекомендовані додатки ввімкнено"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Рекомендовані додатки вимкнено"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Передбачений додаток: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Посібник із навігації за допомогою жестів"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Оберніть пристрій"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Обертайте пристрій, щоб ознайомитися з посібником із навігації за допомогою жестів"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Проведіть пальцем від самого краю екрана (правого або лівого)"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Панель завдань показано"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Панель завдань і чати – зліва"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Панель завдань і чати – справа"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Панель завдань приховано"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Панель і чати приховано"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Панель навігації"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Завжди показув. панель завдань"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Змінити режим навігації"</string>
diff --git a/quickstep/res/values-ur/strings.xml b/quickstep/res/values-ur/strings.xml
index 5b780c3..fde634f 100644
--- a/quickstep/res/values-ur/strings.xml
+++ b/quickstep/res/values-ur/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"ایپ کی تجاویز فعال ہیں"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ایپ کی تجاویز غیر فعال ہیں"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"پیشن گوئی کردہ ایپ: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"اشاروں والی نیویگیشن ٹیوٹوریل"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"اپنا آلہ گھمائیں"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"براہ کرم اشاروں والی نیویگیشن کا ٹیوٹوریل مکمل کرنے کے لیے اپنا آلہ گھمائیں"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"یقینی بنائیں کہ آپ دائیں یا بائیں کنارے سے دور سے سوائپ کریں"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"ٹاشک بار دکھایا گیا"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"ٹاسک بار و بلبلے بائیں طرف ہیں"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"ٹاسک بار و بلبلے دائیں طرف ہیں"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"ٹاسک بار چھپایا گیا"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"ٹاسک بار اور بلبلے پوشیدہ ہیں"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"نیویگیشن بار"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"ہمیشہ ٹاسک بار دکھائیں"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"نیویگیشن موڈ تبدیل کریں"</string>
diff --git a/quickstep/res/values-uz/strings.xml b/quickstep/res/values-uz/strings.xml
index 7de3648..ab514b3 100644
--- a/quickstep/res/values-uz/strings.xml
+++ b/quickstep/res/values-uz/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Ilova tavsiyalari yoqildi"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Endi ilova takliflari chiqmaydi"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Taklif etilgan ilova: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Ishorali navigatsiya darsligi"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Qurilmangizni buring"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Ishorali navigatsiya darsligini tugatish uchun qurilmani buring"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Ekran chetidan boshlab oʻngdan yoki chapdan suring"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Vazifalar paneli ochiq"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Panel va bulutchalar chapda"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Panel va bulutchalar oʻngda"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Vazifalar paneli yopiq"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Panel va bulutchalar berk"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigatsiya paneli"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Vazifalar paneli doim chiqarilsin"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Navigatsiya rejimini oʻzgartirish"</string>
diff --git a/quickstep/res/values-vi/strings.xml b/quickstep/res/values-vi/strings.xml
index dffcd6c..f35138b 100644
--- a/quickstep/res/values-vi/strings.xml
+++ b/quickstep/res/values-vi/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Đã bật tính năng Ứng dụng đề xuất"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Tính năng Ứng dụng đề xuất bị tắt"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Ứng dụng dự đoán: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Hướng dẫn thực hiện thao tác bằng cử chỉ"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Xoay thiết bị của bạn"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Vui lòng xoay thiết bị của bạn để hoàn tất hướng dẫn thao tác bằng cử chỉ"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Hãy vuốt từ mép ngoài cùng bên phải hoặc ngoài cùng bên trái"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Đã hiện thanh thao tác"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Hiện thanh tác vụ, b.bóng trái"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Hiện thanh tác vụ, b.bóng phải"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Đã ẩn thanh thao tác"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Đã ẩn thanh tác vụ & bong bóng"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Thanh điều hướng"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Luôn hiện Thanh tác vụ"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Thay đổi chế độ điều hướng"</string>
diff --git a/quickstep/res/values-zh-rCN/strings.xml b/quickstep/res/values-zh-rCN/strings.xml
index 4d35d39..9665800 100644
--- a/quickstep/res/values-zh-rCN/strings.xml
+++ b/quickstep/res/values-zh-rCN/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"已启用应用建议"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"已停用应用建议"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"预测的应用:<xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"手势导航教程"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"请旋转设备"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"请旋转设备,完成手势导航教程"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"确保从最右侧或最左侧边缘开始滑动"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"任务栏已显示"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"已显示任务栏和左侧消息气泡"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"已显示任务栏和右侧消息气泡"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"任务栏已隐藏"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"已隐藏任务栏和消息气泡"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"导航栏"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"始终显示任务栏"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"更改导航模式"</string>
diff --git a/quickstep/res/values-zh-rHK/strings.xml b/quickstep/res/values-zh-rHK/strings.xml
index fd11ba1..d65aba1 100644
--- a/quickstep/res/values-zh-rHK/strings.xml
+++ b/quickstep/res/values-zh-rHK/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"已啟用應用程式建議"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"已停用應用程式建議"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"預測應用程式:<xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"手勢導覽教學課程"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"旋轉裝置方向"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"請旋轉裝置方向以完成手勢導覽教學課程"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"請確保從螢幕最右側或最左側邊緣滑動"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"顯示咗工作列"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"工作列和對話氣泡在左邊顯示"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"工作列和對話氣泡在右邊顯示"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"隱藏咗工作列"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"工作列和對話氣泡已隱藏"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"導覽列"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"一律顯示工作列"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"變更導覽模式"</string>
diff --git a/quickstep/res/values-zh-rTW/strings.xml b/quickstep/res/values-zh-rTW/strings.xml
index 729f7fd..0b94911 100644
--- a/quickstep/res/values-zh-rTW/strings.xml
+++ b/quickstep/res/values-zh-rTW/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"應用程式建議功能已啟用"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"應用程式建議功能已停用"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"預測的應用程式:<xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"手勢操作教學課程"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"旋轉裝置螢幕方向"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"如要完成手勢操作教學課程,請旋轉裝置螢幕方向"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"請務必從螢幕最右側或最左側滑動"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"已顯示工作列"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"工作列和對話框顯示在左側"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"工作列和對話框顯示在右側"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"已隱藏工作列"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"已隱藏工作列和對話框"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"導覽列"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"一律顯示工作列"</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"變更操作模式"</string>
diff --git a/quickstep/res/values-zu/strings.xml b/quickstep/res/values-zu/strings.xml
index 5209ac1..8ad0b94 100644
--- a/quickstep/res/values-zu/strings.xml
+++ b/quickstep/res/values-zu/strings.xml
@@ -46,8 +46,7 @@
<string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Iziphakamiso zohlelo lokusebenza zinikwe amandla"</string>
<string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Iziphakamiso zohlelo lokusebenza zikhutshaziwe"</string>
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Uhlelo lokusebenza olubikezelwe: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
- <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
- <skip />
+ <string name="gesture_tutorial_title" msgid="2750751261768388354">"Okokufundisa Kokuzulazula Kokuthinta"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Zungezisa idivayisi yakho"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Sicela uzungezise idivayisi yakho ukuze uqedele okokufundisa kokufuna ngokuthinta"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Qinisekisa ukuthi uswayipha ukusuka onqenqemeni olukude ngakwesokudla noma olukude ngakwesokunxele"</string>
@@ -132,8 +131,6 @@
<string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Ibha yomsebenzi ibonisiwe"</string>
<string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"ITaskbar namabhamuza aboniswe kwesokunxele"</string>
<string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"ITaskbar namabhamuza aboniswe kwesokudla"</string>
- <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Ibha yomsebenzi ifihliwe"</string>
- <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"ITaskbar namabhamuza afihliwe"</string>
<string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Ibha yokufuna"</string>
<string name="always_show_taskbar" msgid="3608801276107751229">"Bonisa i-Taskbar njalo."</string>
<string name="change_navigation_mode" msgid="9088393078736808968">"Shintsha imodi yokufuna"</string>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 0474000..8e70a2b 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -30,6 +30,8 @@
<string name="recent_task_option_desktop">Desktop</string>
<!-- Title and content description for an option to move app to external display. -->
<string name="recent_task_option_external_display">Move to external display</string>
+ <!-- Title and content description for an option to close the app [CHAR LIMIT=30] -->
+ <string name="recent_task_option_close">Close</string>
<!-- Title and content description for Desktop tile in Recents screen that contains apps opened inside desktop windowing mode [CHAR LIMIT=NONE] -->
<string name="recent_task_desktop">Desktop</string>
@@ -304,10 +306,6 @@
<string name="taskbar_a11y_shown_with_bubbles_left_title">Taskbar & bubbles left shown</string>
<!-- Accessibility title for the Taskbar window appearing together with bubble bar on right. [CHAR_LIMIT=30] -->
<string name="taskbar_a11y_shown_with_bubbles_right_title">Taskbar & bubbles right shown</string>
- <!-- Accessibility title for the Taskbar window being closed. [CHAR_LIMIT=30] -->
- <string name="taskbar_a11y_hidden_title">Taskbar hidden</string>
- <!-- Accessibility title for the Taskbar window being closed together with bubble bar. [CHAR_LIMIT=30] -->
- <string name="taskbar_a11y_hidden_with_bubbles_title">Taskbar & bubbles hidden</string>
<!-- Accessibility title for the Taskbar window on phones. [CHAR_LIMIT=NONE] -->
<string name="taskbar_phone_a11y_title">Navigation bar</string>
<!-- Text in popup dialog for user to switch between always showing Taskbar or not. [CHAR LIMIT=30] -->
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index f38693d..4ba4e2b 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -1567,8 +1567,7 @@
private boolean isFreeformAnimation(RemoteAnimationTarget[] appTargets) {
return DesktopModeStatus.canEnterDesktopMode(mLauncher.getApplicationContext())
- && (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS.isTrue()
- || DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX.isTrue())
+ && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX.isTrue()
&& Arrays.stream(appTargets)
.anyMatch(app -> app.taskInfo != null && app.taskInfo.isFreeform());
}
@@ -1746,6 +1745,10 @@
if (rectFSpringAnim != null && anim.getChildAnimations().isEmpty()) {
addCujInstrumentation(rectFSpringAnim, Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME);
} else {
+ if (isFreeformAnimation(appTargets)) {
+ addCujInstrumentation(anim,
+ Cuj.CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE);
+ }
addCujInstrumentation(anim, playFallBackAnimation
? Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK
: Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME);
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManager.kt b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManager.kt
index 6cf9b9e..36c5fba 100644
--- a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManager.kt
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManager.kt
@@ -74,8 +74,7 @@
private fun shouldRegisterTransitions(): Boolean =
DesktopModeStatus.canEnterDesktopMode(context) &&
- (DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue ||
- DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX.isTrue)
+ DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX.isTrue
companion object {
private fun buildAppLaunchFilter(): TransitionFilter {
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
index 010a5c8..40cfe92 100644
--- a/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
@@ -60,7 +60,7 @@
callback,
)
val transition = RemoteTransition(animRunner, appThread, "RecentsToDesktop")
- systemUiProxy.showDesktopApps(desktopTaskView.display.displayId, transition)
+ systemUiProxy.showDesktopApps(desktopTaskView.displayId, transition)
}
/** Launch desktop tasks from recents view */
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
index 56945ba..70868c5 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
@@ -17,7 +17,6 @@
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
import static com.android.launcher3.model.PredictionHelper.getAppTargetFromItemInfo;
-import static com.android.launcher3.model.PredictionHelper.isTrackedForHotseatPrediction;
import static com.android.launcher3.model.PredictionHelper.wrapAppTargetWithItemLocation;
import android.app.prediction.AppTarget;
@@ -27,9 +26,12 @@
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
+import com.android.launcher3.model.PredictionHelper;
import com.android.launcher3.model.data.ItemInfo;
import java.util.ArrayList;
+import java.util.Objects;
+import java.util.stream.Collectors;
/**
* Model helper for app predictions in workspace
@@ -43,13 +45,18 @@
*/
public static Bundle convertDataModelToAppTargetBundle(Context context, BgDataModel dataModel) {
Bundle bundle = new Bundle();
- ArrayList<AppTargetEvent> events = new ArrayList<>();
- ArrayList<ItemInfo> workspaceItems = dataModel.getAllWorkspaceItems();
- for (ItemInfo item : workspaceItems) {
- AppTarget target = getAppTargetFromItemInfo(context, item);
- if (target != null && !isTrackedForHotseatPrediction(item)) continue;
- events.add(wrapAppTargetWithItemLocation(target, AppTargetEvent.ACTION_PIN, item));
- }
+ ArrayList<AppTargetEvent> events = dataModel.itemsIdMap
+ .stream()
+ .filter(PredictionHelper::isTrackedForHotseatPrediction)
+ .map(item -> {
+ AppTarget target = getAppTargetFromItemInfo(context, item);
+ return target != null
+ ? wrapAppTargetWithItemLocation(target, AppTargetEvent.ACTION_PIN, item)
+ : null;
+ })
+ .filter(Objects::nonNull)
+ .collect(Collectors.toCollection(ArrayList::new));
+
ArrayList<AppTarget> currentTargets = new ArrayList<>();
FixedContainerItems hotseatItems = dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION);
if (hotseatItems != null) {
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index 25e1813..40e8fc2 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -462,7 +462,7 @@
private Bundle getBundleForWidgetsOnWorkspace(Context context, BgDataModel dataModel) {
Bundle bundle = new Bundle();
ArrayList<AppTargetEvent> widgetEvents =
- dataModel.getAllWorkspaceItems().stream()
+ dataModel.itemsIdMap.stream()
.filter(PredictionHelper::isTrackedForWidgetPrediction)
.map(item -> {
AppTarget target = getAppTargetFromItemInfo(context, item);
diff --git a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
index 9d9054e..40e1c10 100644
--- a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
+++ b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
@@ -18,6 +18,7 @@
import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;
import static com.android.launcher3.Flags.enableTieredWidgetsByDefaultInPicker;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
+import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toMap;
@@ -69,9 +70,11 @@
@NonNull AllAppsList apps) {
Predicate<WidgetItem> predictedWidgetsFilter = enableTieredWidgetsByDefaultInPicker()
? dataModel.widgetsModel.getPredictedWidgetsFilter() : null;
- Set<ComponentKey> widgetsInWorkspace = dataModel.appWidgets.stream().map(
- widget -> new ComponentKey(widget.providerName, widget.user)).collect(
- Collectors.toSet());
+ Set<ComponentKey> widgetsInWorkspace = dataModel.itemsIdMap
+ .stream()
+ .filter(WIDGET_FILTER)
+ .map(item -> new ComponentKey(item.getTargetComponent(), item.user))
+ .collect(Collectors.toSet());
// Widgets (excluding shortcuts & already added widgets) that belong to apps eligible for
// being in predictions.
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 289e720..2e48910 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -40,9 +40,10 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISABLED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISMISS_IME;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_VISIBLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
@@ -133,7 +134,14 @@
private static final int FLAG_IME_SWITCHER_BUTTON_VISIBLE = 1 << 0;
/** Whether the IME is visible. */
private static final int FLAG_IME_VISIBLE = 1 << 1;
- private static final int FLAG_ROTATION_BUTTON_VISIBLE = 1 << 2;
+ /**
+ * The back button is visually adjusted to indicate that it will dismiss the IME when pressed.
+ * This only takes effect while the IME is visible. By default, it is set while the IME is
+ * visible, but may be overridden by the
+ * {@link android.inputmethodservice.InputMethodService.BackDispositionMode backDispositionMode}
+ * set by the IME.
+ */
+ private static final int FLAG_BACK_DISMISS_IME = 1 << 2;
private static final int FLAG_A11Y_VISIBLE = 1 << 3;
private static final int FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE = 1 << 4;
private static final int FLAG_KEYGUARD_VISIBLE = 1 << 5;
@@ -275,13 +283,14 @@
}
protected void setupController() {
- boolean isThreeButtonNav = mContext.isThreeButtonNav();
+ final boolean isThreeButtonNav = mContext.isThreeButtonNav();
+ final boolean isPhoneMode = mContext.isPhoneMode();
DeviceProfile deviceProfile = mContext.getDeviceProfile();
Resources resources = mContext.getResources();
int setupSize = mControllers.taskbarActivityContext.getSetupWindowSize();
- Point p = DimensionUtils.getTaskbarPhoneDimensions(deviceProfile, resources,
- mContext.isPhoneMode(), mContext.isGestureNav());
+ Point p = DimensionUtils.getTaskbarPhoneDimensions(deviceProfile, resources, isPhoneMode,
+ mContext.isGestureNav());
ViewGroup.LayoutParams navButtonsViewLayoutParams = mNavButtonsView.getLayoutParams();
navButtonsViewLayoutParams.width = p.x;
if (!mContext.isUserSetupComplete()) {
@@ -302,9 +311,10 @@
mImeSwitcherButton = addButton(switcherResId, BUTTON_IME_SWITCH,
isThreeButtonNav ? mStartContextualContainer : mEndContextualContainer,
mControllers.navButtonController, R.id.ime_switcher);
+ // A11y and IME Switcher buttons overlap on phone mode, show only a11y if both visible.
mPropertyHolders.add(new StatePropertyHolder(mImeSwitcherButton,
- flags -> ((flags & FLAG_IME_SWITCHER_BUTTON_VISIBLE) != 0)
- && ((flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0)));
+ flags -> (flags & FLAG_IME_SWITCHER_BUTTON_VISIBLE) != 0
+ && !(isPhoneMode && (flags & FLAG_A11Y_VISIBLE) != 0)));
}
mPropertyHolders.add(new StatePropertyHolder(
@@ -318,7 +328,7 @@
.get(ALPHA_INDEX_SMALL_SCREEN),
flags -> (flags & FLAG_SMALL_SCREEN) == 0));
- if (!mContext.isPhoneMode()) {
+ if (!isPhoneMode) {
mPropertyHolders.add(new StatePropertyHolder(mControllers.taskbarDragLayerController
.getKeyguardBgTaskbar(), flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0));
}
@@ -333,10 +343,10 @@
// Make sure to remove nav bar buttons translation when any of the following occur:
// - Notification shade is expanded
- // - IME is showing (add separate translation for IME)
+ // - IME is visible (add separate translation for IME)
// - VoiceInteractionWindow (assistant) is showing
// - Keyboard shortcuts helper is showing
- if (!mContext.isPhoneMode()) {
+ if (!isPhoneMode) {
int flagsToRemoveTranslation = FLAG_NOTIFICATION_SHADE_EXPANDED | FLAG_IME_VISIBLE
| FLAG_VOICE_INTERACTION_WINDOW_SHOWING | FLAG_KEYBOARD_SHORTCUT_HELPER_SHOWING;
mPropertyHolders.add(new StatePropertyHolder(mNavButtonInAppDisplayProgressForSysui,
@@ -364,9 +374,9 @@
initButtons(mNavButtonContainer, mEndContextualContainer,
mControllers.navButtonController);
updateButtonLayoutSpacing();
- updateStateForFlag(FLAG_SMALL_SCREEN, mContext.isPhoneMode());
+ updateStateForFlag(FLAG_SMALL_SCREEN, isPhoneMode);
- if (!mContext.isPhoneMode()) {
+ if (!isPhoneMode) {
mPropertyHolders.add(new StatePropertyHolder(
mControllers.taskbarDragLayerController.getNavbarBackgroundAlpha(),
flags -> (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0));
@@ -393,7 +403,7 @@
R.bool.floating_rotation_button_position_left);
mControllers.rotationButtonController.setRotationButton(mFloatingRotationButton,
mRotationButtonListener);
- if (mContext.isPhoneMode()) {
+ if (isPhoneMode) {
mTaskbarTransitions.init();
}
@@ -449,7 +459,7 @@
flags -> (flags & FLAG_IME_VISIBLE) == 0));
}
mPropertyHolders.add(new StatePropertyHolder(mBackButton,
- flags -> (flags & FLAG_IME_VISIBLE) != 0,
+ flags -> (flags & FLAG_BACK_DISMISS_IME) != 0,
ROTATION_DRAWABLE_PERCENT, 1f, 0f));
// Translate back button to be at end/start of other buttons for keyguard (only after SUW
// since it is laid to align with SUW actions while in that state)
@@ -498,8 +508,7 @@
endContainer, navButtonController, R.id.accessibility_button,
R.layout.taskbar_contextual_button);
mPropertyHolders.add(new StatePropertyHolder(mA11yButton,
- flags -> (flags & FLAG_A11Y_VISIBLE) != 0
- && (flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0));
+ flags -> (flags & FLAG_A11Y_VISIBLE) != 0));
mSpace = new Space(mNavButtonsView.getContext());
mSpace.setOnClickListener(view -> navButtonController.onButtonClick(BUTTON_SPACE, view));
@@ -510,8 +519,9 @@
private void parseSystemUiFlags(@SystemUiStateFlags long sysUiStateFlags) {
mSysuiStateFlags = sysUiStateFlags;
boolean isImeSwitcherButtonVisible =
- (sysUiStateFlags & SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING) != 0;
- boolean isImeVisible = (sysUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0;
+ (sysUiStateFlags & SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE) != 0;
+ boolean isImeVisible = (sysUiStateFlags & SYSUI_STATE_IME_VISIBLE) != 0;
+ boolean isBackDismissIme = (sysUiStateFlags & SYSUI_STATE_BACK_DISMISS_IME) != 0;
boolean a11yVisible = (sysUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
boolean isHomeDisabled = (sysUiStateFlags & SYSUI_STATE_HOME_DISABLED) != 0;
boolean isRecentsDisabled = (sysUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0;
@@ -527,6 +537,7 @@
updateStateForFlag(FLAG_IME_SWITCHER_BUTTON_VISIBLE, isImeSwitcherButtonVisible);
updateStateForFlag(FLAG_IME_VISIBLE, isImeVisible);
+ updateStateForFlag(FLAG_BACK_DISMISS_IME, isBackDismissIme);
updateStateForFlag(FLAG_A11Y_VISIBLE, a11yVisible);
updateStateForFlag(FLAG_DISABLE_HOME, isHomeDisabled);
updateStateForFlag(FLAG_DISABLE_RECENTS, isRecentsDisabled);
@@ -1231,7 +1242,7 @@
appendFlag(str, flags, FLAG_IME_SWITCHER_BUTTON_VISIBLE,
"FLAG_IME_SWITCHER_BUTTON_VISIBLE");
appendFlag(str, flags, FLAG_IME_VISIBLE, "FLAG_IME_VISIBLE");
- appendFlag(str, flags, FLAG_ROTATION_BUTTON_VISIBLE, "FLAG_ROTATION_BUTTON_VISIBLE");
+ appendFlag(str, flags, FLAG_BACK_DISMISS_IME, "FLAG_BACK_DISMISS_IME");
appendFlag(str, flags, FLAG_A11Y_VISIBLE, "FLAG_A11Y_VISIBLE");
appendFlag(str, flags, FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE,
"FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE");
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 6bfbfdd..3963d40 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -43,6 +43,7 @@
import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
+import static com.android.window.flags.Flags.enableStartLaunchTransitionFromTaskbarBugfix;
import static com.android.wm.shell.Flags.enableTinyTaskbar;
import static java.lang.invoke.MethodHandles.Lookup.PROTECTED;
@@ -1518,9 +1519,7 @@
BubbleTextView.RunningAppState runningAppState =
mControllers.taskbarRecentAppsController.getRunningAppState(taskId);
return runningAppState == RunningAppState.MINIMIZED
- && (DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS.isTrue()
- || DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS_BUGFIX.isTrue()
- );
+ && DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS_BUGFIX.isTrue();
}
private RemoteTransition createDesktopAppLaunchRemoteTransition(
@@ -1694,7 +1693,11 @@
}
// There is no task associated with this launch - launch a new task through an intent
ActivityOptionsWrapper opts = getActivityLaunchDesktopOptions();
- mSysUiProxy.startLaunchIntentTransition(intent, opts.options.toBundle(), displayId);
+ if (enableStartLaunchTransitionFromTaskbarBugfix()) {
+ mSysUiProxy.startLaunchIntentTransition(intent, opts.options.toBundle(), displayId);
+ } else {
+ startActivity(intent, opts.options.toBundle());
+ }
}
/** Expands a folder icon when it is clicked */
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index b46b0dc..17fb959 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -91,6 +91,7 @@
private static final boolean DEBUG = false;
// TODO(b/382378283) remove all logs with this tag
public static final String NULL_TASKBAR_ROOT_LAYOUT_TAG = "b/382378283";
+ public static final String ILLEGAL_ARGUMENT_WM_ADD_VIEW = "b/391653300";
/**
* All the configurations which do not initiate taskbar recreation.
@@ -306,6 +307,7 @@
int configDiff = mOldConfig.diff(newConfig) & ~SKIP_RECREATE_CONFIG_CHANGES;
if ((configDiff & ActivityInfo.CONFIG_UI_MODE) != 0) {
+ Log.d(ILLEGAL_ARGUMENT_WM_ADD_VIEW, "onConfigurationChanged: theme changed");
// Only recreate for theme changes, not other UI mode changes such as docking.
int oldUiNightMode = (mOldConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK);
int newUiNightMode = (newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK);
@@ -360,6 +362,7 @@
}
private void destroyTaskbarForDisplay(int displayId) {
+ Log.d(ILLEGAL_ARGUMENT_WM_ADD_VIEW, "destroyTaskbarForDisplay: " + displayId);
TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
debugWhyTaskbarNotDestroyed("destroyTaskbarForDisplay: " + taskbar, displayId);
if (taskbar != null) {
@@ -522,6 +525,7 @@
private void recreateTaskbarForDisplay(int displayId) {
Trace.beginSection("recreateTaskbar");
try {
+ Log.d(ILLEGAL_ARGUMENT_WM_ADD_VIEW, "recreateTaskbarForDisplay: " + displayId);
// TODO: make this code display specific
DeviceProfile dp = mUserUnlocked ?
LauncherAppState.getIDP(mWindowContext).getDeviceProfile(mWindowContext) : null;
@@ -711,9 +715,9 @@
/**
* Signal from SysUI indicating that a non-mirroring display was just connected to the
- * primary device.
+ * primary device or a previously mirroring display is switched to extended mode.
*/
- public void onDisplayReady(int displayId) {
+ public void onDisplayAddSystemDecorations(int displayId) {
}
/**
@@ -723,6 +727,11 @@
public void onDisplayRemoved(int displayId) {
}
+ /**
+ * Signal from SysUI indicating that system decorations should be removed from the display.
+ */
+ public void onDisplayRemoveSystemDecorations(int displayId) {}
+
private void removeActivityCallbacksAndListeners() {
if (mActivity != null) {
mActivity.removeOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
@@ -789,9 +798,15 @@
}
if (!isTaskbarRootLayoutAddedForDisplay(displayId)) {
- mWindowManager.addView(getTaskbarRootLayoutForDisplay(displayId),
- taskbar.getWindowLayoutParams());
- mAddedRootLayouts.put(displayId, true);
+ FrameLayout rootLayout = getTaskbarRootLayoutForDisplay(displayId);
+ if (rootLayout != null) {
+ mWindowManager.addView(getTaskbarRootLayoutForDisplay(displayId),
+ taskbar.getWindowLayoutParams());
+ mAddedRootLayouts.put(displayId, true);
+ } else {
+ Log.d(ILLEGAL_ARGUMENT_WM_ADD_VIEW,
+ "addTaskbarRootViewToWindow - root layout null | displayId=" + displayId);
+ }
} else {
Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG,
"addTaskbarRootViewToWindow - root layout already added | displayId="
@@ -800,6 +815,7 @@
}
private void removeTaskbarRootViewFromWindow(int displayId) {
+ Log.d(ILLEGAL_ARGUMENT_WM_ADD_VIEW, "removeTaskbarRootViewFromWindow: " + displayId);
FrameLayout rootLayout = getTaskbarRootLayoutForDisplay(displayId);
if (!enableTaskbarNoRecreate() || rootLayout == null) {
return;
@@ -875,6 +891,7 @@
* @param displayId The ID of the display for which to create the taskbar root layout.
*/
private void createTaskbarRootLayout(int displayId) {
+ Log.d(ILLEGAL_ARGUMENT_WM_ADD_VIEW, "createTaskbarRootLayout: " + displayId);
FrameLayout newTaskbarRootLayout = new FrameLayout(mWindowContext) {
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
@@ -902,6 +919,7 @@
* @return The taskbar root layout {@link FrameLayout} for a given display or {@code null}.
*/
private FrameLayout getTaskbarRootLayoutForDisplay(int displayId) {
+ Log.d(ILLEGAL_ARGUMENT_WM_ADD_VIEW, "getTaskbarRootLayoutForDisplay: " + displayId);
FrameLayout frameLayout = mRootLayouts.get(displayId);
if (frameLayout != null) {
return frameLayout;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
index f905c5f..6815f97 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
@@ -25,7 +25,6 @@
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
@@ -39,9 +38,9 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.function.Predicate;
/**
@@ -114,15 +113,9 @@
return modified;
}
-
@Override
- public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) {
- updateWorkspaceItems(updated, mContext);
- }
-
- @Override
- public void bindRestoreItemsChange(HashSet<ItemInfo> updates) {
- updateRestoreItems(updates, mContext);
+ public void bindItemsUpdated(Set<ItemInfo> updates) {
+ updateContainerItems(updates, mContext);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 68114f1..1ca3dfb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -34,8 +34,7 @@
import static com.android.quickstep.util.SystemUiFlagUtils.isTaskbarHidden;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_VISIBLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
@@ -260,9 +259,7 @@
private @Nullable AnimatorSet mAnimator;
private boolean mIsSystemGestureInProgress;
/** Whether the IME is visible. */
- private boolean mIsImeShowing;
- /** Whether the IME Switcher button is visible. */
- private boolean mIsImeSwitcherButtonShowing;
+ private boolean mIsImeVisible;
private final Alarm mTimeoutAlarm = new Alarm();
private boolean mEnableBlockingTimeoutDuringTests = false;
@@ -1122,7 +1119,7 @@
*/
@VisibleForTesting
long getTaskbarStashStartDelayForIme() {
- if (mIsImeShowing) {
+ if (mIsImeVisible) {
// Only delay when IME is exiting, not entering.
return 0;
}
@@ -1148,9 +1145,7 @@
updateStateForFlag(FLAG_STASHED_DEVICE_LOCKED,
SystemUiFlagUtils.isLocked(systemUiStateFlags));
- mIsImeShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SHOWING);
- mIsImeSwitcherButtonShowing =
- hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING);
+ mIsImeVisible = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_VISIBLE);
if (updateStateForFlag(FLAG_STASHED_IME, shouldStashForIme())) {
animDuration = TASKBAR_STASH_DURATION_FOR_IME;
startDelay = getTaskbarStashStartDelayForIme();
@@ -1166,7 +1161,7 @@
}
/**
- * We stash when IME or IME switcher is showing.
+ * We stash when the IME is visible.
*
* <p>Do not stash if in small screen, with 3 button nav, and in landscape (or seascape).
* <p>Do not stash if taskbar is transient.
@@ -1202,7 +1197,7 @@
return false;
}
- return mIsImeShowing || mIsImeSwitcherButtonShowing;
+ return mIsImeVisible;
}
/**
@@ -1377,8 +1372,7 @@
pw.println(prefix + "\tappliedState=" + getStateString(mStatePropertyHolder.mPrevFlags));
pw.println(prefix + "\tmState=" + getStateString(mState));
pw.println(prefix + "\tmIsSystemGestureInProgress=" + mIsSystemGestureInProgress);
- pw.println(prefix + "\tmIsImeShowing=" + mIsImeShowing);
- pw.println(prefix + "\tmIsImeSwitcherButtonShowing=" + mIsImeSwitcherButtonShowing);
+ pw.println(prefix + "\tmIsImeVisible=" + mIsImeVisible);
}
private static String getStateString(long flags) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index c7f33e9..a9b772a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -33,7 +33,6 @@
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.os.Bundle;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.view.DisplayCutout;
@@ -41,7 +40,6 @@
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import androidx.annotation.LayoutRes;
@@ -312,16 +310,6 @@
mShouldTryStartAlign = mActivityContext.shouldStartAlignTaskbar();
}
- @Override
- public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
- if (action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) {
- announceTaskbarShown();
- } else if (action == AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS) {
- announceTaskbarHidden();
- }
- return super.performAccessibilityActionInternal(action, arguments);
- }
-
private void announceTaskbarShown() {
BubbleBarLocation bubbleBarLocation = mControllerCallbacks.getBubbleBarLocationIfVisible();
if (bubbleBarLocation == null) {
@@ -335,21 +323,12 @@
}
}
- private void announceTaskbarHidden() {
- BubbleBarLocation bubbleBarLocation = mControllerCallbacks.getBubbleBarLocationIfVisible();
- if (bubbleBarLocation == null) {
- announceForAccessibility(mContext.getString(R.string.taskbar_a11y_hidden_title));
- } else {
- announceForAccessibility(
- mContext.getString(R.string.taskbar_a11y_hidden_with_bubbles_title));
- }
- }
-
protected void announceAccessibilityChanges() {
- this.performAccessibilityAction(
- isVisibleToUser() ? AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS
- : AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
-
+ // Only announce taskbar window shown. Window disappearing is generally not announce.
+ // This also aligns with talkback guidelines and unnecessary announcement to users.
+ if (isVisibleToUser()) {
+ announceTaskbarShown();
+ }
ActivityContext.lookupContext(getContext()).getDragLayer()
.sendAccessibilityEvent(TYPE_WINDOW_CONTENT_CHANGED);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index aff879e..4e029e3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -19,8 +19,7 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_VISIBLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
@@ -92,10 +91,9 @@
private static final long MASK_HIDE_BUBBLE_BAR = SYSUI_STATE_BOUNCER_SHOWING
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
- | SYSUI_STATE_IME_SHOWING
+ | SYSUI_STATE_IME_VISIBLE
| SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
- | SYSUI_STATE_QUICK_SETTINGS_EXPANDED
- | SYSUI_STATE_IME_SWITCHER_BUTTON_SHOWING;
+ | SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
private static final long MASK_HIDE_HANDLE_VIEW = SYSUI_STATE_BOUNCER_SHOWING
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
@@ -239,7 +237,7 @@
boolean sysuiLocked = (flags & MASK_SYSUI_LOCKED) != 0;
mBubbleStashController.setSysuiLocked(sysuiLocked);
- mIsImeVisible = (flags & SYSUI_STATE_IME_SHOWING) != 0;
+ mIsImeVisible = (flags & SYSUI_STATE_IME_VISIBLE) != 0;
if (mIsImeVisible) {
mBubbleBarViewController.onImeVisible();
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index a85e5e0..36a4865 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -142,7 +142,7 @@
int shadowSize = context.getResources().getDimensionPixelSize(
R.dimen.blur_size_thin_outline);
mShadowFilter = new BlurMaskFilter(shadowSize, BlurMaskFilter.Blur.OUTER);
- mShapePath = IconShape.INSTANCE.get(context).getShapeOverridePath(mNormalizedIconSize);
+ mShapePath = IconShape.INSTANCE.get(context).getShape().getPath(mNormalizedIconSize);
}
@Override
@@ -214,7 +214,7 @@
boolean animate = shouldAnimateIconChange(info);
Drawable oldIcon = getIcon();
int oldPlateColor = mPlateColor.currentColor;
- applyFromWorkspaceItem(info, null);
+ applyFromWorkspaceItem(info);
setContentDescription(
mIsPinned ? info.contentDescription :
diff --git a/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt b/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
index 2e2d7cc..d097dba 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
@@ -192,4 +192,6 @@
override fun getApplicationInfoHash(appInfo: ApplicationInfo): String =
(appInfo.sourceDir?.hashCode() ?: 0).toString() + " " + appInfo.longVersionCode
+
+ override fun getRoundIconRes(appInfo: ApplicationInfo) = appInfo.roundIconRes
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 49a5ac5..cbfcda7 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -86,6 +86,8 @@
import com.android.quickstep.util.WorkspaceRevealAnim;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
+import com.android.window.flags.Flags;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
/**
* Handles quick switching to a recent task from the home screen. To give as much flexibility to
@@ -184,6 +186,12 @@
mIsTrackpadSwipe = isTrackpadFourFingerSwipe(ev);
return mIsTrackpadSwipe;
}
+ if (DesktopModeStatus.canEnterDesktopMode(mLauncher.getContext())
+ //TODO(b/345296916): replace with dev option once in teamfood
+ && Flags.enableQuickswitchDesktopSplitBugfix()
+ && mRecentsView.getNonDesktopTaskViewCount() < 1) {
+ return false;
+ }
return true;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index d673720..f582324 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -149,9 +149,9 @@
mOverviewPanel.setFullscreenProgress(progress);
if (progress > UPDATE_SYSUI_FLAGS_THRESHOLD) {
int sysuiFlags = 0;
- TaskView tv = mOverviewPanel.getFirstTaskView();
- if (tv != null) {
- sysuiFlags = tv.getTaskContainers().getFirst().getSysUiStatusNavFlags();
+ TaskView firstTaskView = mOverviewPanel.getFirstTaskView();
+ if (firstTaskView != null) {
+ sysuiFlags = firstTaskView.getSysUiStatusNavFlags();
}
mLauncher.getSystemUiController().updateUiState(UI_STATE_FULLSCREEN_TASK, sysuiFlags);
} else {
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 8b76ce9..5f8b89a 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -120,6 +120,7 @@
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StatefulContainer;
import com.android.launcher3.taskbar.TaskbarThresholdUtils;
@@ -932,7 +933,7 @@
TaskView runningTask = mRecentsView.getRunningTaskView();
TaskView centermostTask = mRecentsView.getTaskViewNearestToCenterOfScreen();
int centermostTaskFlags = centermostTask == null ? 0
- : centermostTask.getTaskContainers().getFirst().getSysUiStatusNavFlags();
+ : centermostTask.getSysUiStatusNavFlags();
boolean swipeUpThresholdPassed = windowProgress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD;
boolean quickswitchThresholdPassed = centermostTask != runningTask;
@@ -955,7 +956,7 @@
@Override
public void onRecentsAnimationStart(RecentsAnimationController controller,
- RecentsAnimationTargets targets, TransitionInfo transitionInfo) {
+ RecentsAnimationTargets targets, @Nullable TransitionInfo transitionInfo) {
super.onRecentsAnimationStart(controller, targets, transitionInfo);
if (targets.hasDesktopTasks(mContext)) {
mRemoteTargetHandles = mTargetGluer.assignTargetsForDesktop(targets, transitionInfo);
@@ -1527,8 +1528,9 @@
.withInputType(mGestureState.isTrackpadGesture()
? SysUiStatsLog.LAUNCHER_UICHANGED__INPUT_TYPE__TRACKPAD
: SysUiStatsLog.LAUNCHER_UICHANGED__INPUT_TYPE__TOUCH);
- if (targetTask != null) {
- logger.withItemInfo(targetTask.getFirstItemInfo());
+ ItemInfo itemInfo;
+ if (targetTask != null && (itemInfo = targetTask.getFirstItemInfo()) != null) {
+ logger.withItemInfo(itemInfo);
}
int pageIndex = endTarget == LAST_TASK || mRecentsView == null
@@ -2369,9 +2371,6 @@
ActiveGestureLog.CompoundString nextTaskLog =
ActiveGestureLog.CompoundString.newEmptyString();
for (TaskContainer container : nextTask.getTaskContainers()) {
- if (container == null) {
- continue;
- }
nextTaskLog.append("[id: %d, pkg: %s] | ",
container.getTask().key.id,
container.getTask().key.getPackageName());
diff --git a/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt b/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
index d60dab6..914855b 100644
--- a/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
+++ b/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
@@ -26,6 +26,7 @@
import com.android.quickstep.views.RecentsViewContainer
import com.android.quickstep.views.TaskContainer
import com.android.systemui.shared.system.InteractionJankMonitorWrapper
+import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
@@ -70,16 +71,31 @@
container: RecentsViewContainer,
taskContainer: TaskContainer,
): List<DesktopSystemShortcut>? {
- return if (!DesktopModeStatus.canEnterDesktopMode(container.asContext())) null
- else if (!taskContainer.task.isDockable) null
- else
- listOf(
- DesktopSystemShortcut(
- container,
- taskContainer,
- abstractFloatingViewHelper,
+ val context = container.asContext()
+ val taskKey = taskContainer.task.key
+ val desktopModeCompatPolicy = DesktopModeCompatPolicy(context)
+ return when {
+ !DesktopModeStatus.canEnterDesktopMode(context) -> null
+
+ desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
+ taskKey.baseActivity?.packageName,
+ taskKey.numActivities,
+ taskKey.isTopActivityNoDisplay,
+ taskKey.isActivityStackTransparent,
+ ) -> null
+
+ !taskContainer.task.isDockable -> null
+
+ else -> {
+ listOf(
+ DesktopSystemShortcut(
+ container,
+ taskContainer,
+ abstractFloatingViewHelper,
+ )
)
- )
+ }
+ }
}
override fun showForGroupedTask() = true
diff --git a/quickstep/src/com/android/quickstep/ExternalDisplaySystemShortcut.kt b/quickstep/src/com/android/quickstep/ExternalDisplaySystemShortcut.kt
index 46c4f36..f97cf9c 100644
--- a/quickstep/src/com/android/quickstep/ExternalDisplaySystemShortcut.kt
+++ b/quickstep/src/com/android/quickstep/ExternalDisplaySystemShortcut.kt
@@ -25,6 +25,7 @@
import com.android.quickstep.views.RecentsViewContainer
import com.android.quickstep.views.TaskContainer
import com.android.window.flags.Flags
+import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
/** A menu item that allows the user to move the current app into external display. */
@@ -66,18 +67,31 @@
container: RecentsViewContainer,
taskContainer: TaskContainer,
): List<ExternalDisplaySystemShortcut>? {
- return if (
- DesktopModeStatus.canEnterDesktopMode(container.asContext()) &&
- Flags.moveToExternalDisplayShortcut()
- )
- listOf(
- ExternalDisplaySystemShortcut(
- container,
- abstractFloatingViewHelper,
- taskContainer,
+ val context = container.asContext()
+ val taskKey = taskContainer.task.key
+ val desktopModeCompatPolicy = DesktopModeCompatPolicy(context)
+ return when {
+ !DesktopModeStatus.canEnterDesktopMode(context) -> null
+
+ !Flags.moveToExternalDisplayShortcut() -> null
+
+ desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
+ taskKey.baseActivity?.packageName,
+ taskKey.numActivities,
+ taskKey.isTopActivityNoDisplay,
+ taskKey.isActivityStackTransparent,
+ ) -> null
+
+ else -> {
+ listOf(
+ ExternalDisplaySystemShortcut(
+ container,
+ abstractFloatingViewHelper,
+ taskContainer,
+ )
)
- )
- else null
+ }
+ }
}
override fun showForGroupedTask() = true
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index c60d3e8..e1e9c99 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -56,6 +56,7 @@
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.animation.TransitionAnimator;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.InputConsumerController;
import java.util.Collections;
@@ -300,7 +301,9 @@
// Disable if swiping to PIP
return null;
}
- if (sourceTaskView == null || sourceTaskView.getFirstTask().key.getComponent() == null) {
+ Task firstTask;
+ if (sourceTaskView == null || ((firstTask = sourceTaskView.getFirstTask()) == null)
+ || firstTask.key.getComponent() == null) {
// Disable if it's an invalid task
return null;
}
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
index 66f307c..94d115b 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
@@ -28,7 +28,9 @@
import androidx.annotation.UiThread
import androidx.annotation.VisibleForTesting
import com.android.internal.jank.Cuj
+import com.android.launcher3.Flags.enableFallbackOverviewInWindow
import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
+import com.android.launcher3.Flags.enableLauncherOverviewInWindow
import com.android.launcher3.Flags.enableOverviewCommandHelperTimeout
import com.android.launcher3.PagedView
import com.android.launcher3.logger.LauncherAtom
@@ -344,9 +346,12 @@
return false
}
- val activity = containerInterface.getCreatedContainer()
- if (activity != null) {
- InteractionJankMonitorWrapper.begin(activity.rootView, Cuj.CUJ_LAUNCHER_QUICK_SWITCH)
+ val recentsInWindowFlagSet =
+ enableFallbackOverviewInWindow() || enableLauncherOverviewInWindow()
+ if (!recentsInWindowFlagSet) {
+ containerInterface.getCreatedContainer()?.rootView?.let { view ->
+ InteractionJankMonitorWrapper.begin(view, Cuj.CUJ_LAUNCHER_QUICK_SWITCH)
+ }
}
val gestureState =
@@ -370,9 +375,15 @@
override fun onRecentsAnimationStart(
controller: RecentsAnimationController,
targets: RecentsAnimationTargets,
- transitionInfo: TransitionInfo,
+ transitionInfo: TransitionInfo?,
) {
Log.d(TAG, "recents animation started: $command")
+ if (recentsInWindowFlagSet) {
+ containerInterface.getCreatedContainer()?.rootView?.let { view ->
+ InteractionJankMonitorWrapper.begin(view, Cuj.CUJ_LAUNCHER_QUICK_SWITCH)
+ }
+ }
+
updateRecentsViewFocus(command)
logShowOverviewFrom(command.type)
containerInterface.runOnInitBackgroundStateUI {
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 01ced75..bb72408 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -18,6 +18,7 @@
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
+import static com.android.launcher3.Flags.enableSeparateExternalDisplayTasks;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.util.SplitScreenUtils.convertShellSplitBoundsToLauncher;
import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_FREEFORM;
@@ -47,12 +48,18 @@
import com.android.wm.shell.shared.GroupedTaskInfo;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import kotlin.collections.ArraysKt;
+import kotlin.collections.CollectionsKt;
+import kotlin.collections.MapsKt;
+
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -350,10 +357,9 @@
// TYPE_FREEFORM tasks is only created when desktop mode can be entered,
// leftover TYPE_FREEFORM tasks created when flag was on should be ignored.
if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
- GroupTask desktopTask = createDesktopTask(rawTask.getBaseGroupedTask());
- if (desktopTask != null) {
- allTasks.add(desktopTask);
- }
+ List<DesktopTask> desktopTasks = createDesktopTasks(
+ rawTask.getBaseGroupedTask());
+ allTasks.addAll(desktopTasks);
}
continue;
}
@@ -421,20 +427,35 @@
return allTasks;
}
- private @Nullable DesktopTask createDesktopTask(GroupedTaskInfo recentTaskInfo) {
- ArrayList<Task> tasks = new ArrayList<>(recentTaskInfo.getTaskInfoList().size());
- int[] minimizedTaskIds = recentTaskInfo.getMinimizedTaskIds();
- for (TaskInfo taskInfo : recentTaskInfo.getTaskInfoList()) {
- Task.TaskKey key = new Task.TaskKey(taskInfo);
- Task task = Task.from(key, taskInfo, false);
- task.positionInParent = taskInfo.positionInParent;
- task.appBounds = taskInfo.configuration.windowConfiguration.getAppBounds();
- task.isVisible = taskInfo.isVisible;
- task.isMinimized =
- Arrays.stream(minimizedTaskIds).anyMatch(taskId -> taskId == taskInfo.taskId);
- tasks.add(task);
+ private Task createTask(TaskInfo taskInfo, Set<Integer> minimizedTaskIds) {
+ Task.TaskKey key = new Task.TaskKey(taskInfo);
+ Task task = Task.from(key, taskInfo, false);
+ task.positionInParent = taskInfo.positionInParent;
+ task.appBounds = taskInfo.configuration.windowConfiguration.getAppBounds();
+ task.isVisible = taskInfo.isVisible;
+ task.isMinimized = minimizedTaskIds.contains(taskInfo.taskId);
+ return task;
+ }
+
+ private List<DesktopTask> createDesktopTasks(GroupedTaskInfo recentTaskInfo) {
+ int[] minimizedTaskIdArray = recentTaskInfo.getMinimizedTaskIds();
+ Set<Integer> minimizedTaskIds = minimizedTaskIdArray != null
+ ? CollectionsKt.toSet(ArraysKt.asIterable(minimizedTaskIdArray))
+ : Collections.emptySet();
+ if (enableSeparateExternalDisplayTasks()) {
+ Map<Integer, List<Task>> perDisplayTasks = new HashMap<>();
+ for (TaskInfo taskInfo : recentTaskInfo.getTaskInfoList()) {
+ Task task = createTask(taskInfo, minimizedTaskIds);
+ List<Task> tasks = perDisplayTasks.computeIfAbsent(taskInfo.displayId,
+ k -> new ArrayList<>());
+ tasks.add(task);
+ }
+ return MapsKt.map(perDisplayTasks, it -> new DesktopTask(it.getValue()));
+ } else {
+ List<Task> tasks = CollectionsKt.map(recentTaskInfo.getTaskInfoList(),
+ it -> createTask(it, minimizedTaskIds));
+ return List.of(new DesktopTask(tasks));
}
- return new DesktopTask(tasks);
}
public void dump(String prefix, PrintWriter writer) {
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index 87bf81c..c6b858b 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -22,6 +22,7 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import android.annotation.Nullable;
import android.graphics.Rect;
import android.os.Bundle;
import android.util.ArraySet;
@@ -103,7 +104,7 @@
RemoteAnimationTarget[] appTargets,
RemoteAnimationTarget[] wallpaperTargets,
Rect homeContentInsets, Rect minimizedHomeBounds, Bundle extras,
- TransitionInfo transitionInfo) {
+ @Nullable TransitionInfo transitionInfo) {
long appCount = Arrays.stream(appTargets)
.filter(app -> app.mode == MODE_CLOSING)
.count();
@@ -207,7 +208,7 @@
*/
public interface RecentsAnimationListener {
default void onRecentsAnimationStart(RecentsAnimationController controller,
- RecentsAnimationTargets targets, TransitionInfo transitionInfo) {}
+ RecentsAnimationTargets targets, @Nullable TransitionInfo transitionInfo) {}
/**
* Callback from the system when the recents animation is canceled. {@param thumbnailData}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 0c89a80..e1e962a 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -37,7 +37,7 @@
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;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_VISIBLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
@@ -586,7 +586,7 @@
/** Returns whether IME is rendering nav buttons, and IME is currently showing. */
public boolean isImeRenderingNavButtons() {
return mCanImeRenderGesturalNavButtons && mMode == NO_BUTTON
- && ((mSystemUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0);
+ && ((mSystemUiStateFlags & SYSUI_STATE_IME_VISIBLE) != 0);
}
/**
diff --git a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
index ef63b9b..f96bbcb 100644
--- a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
+++ b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
@@ -217,7 +217,7 @@
* transform params per app in {@code targets.apps} list.
*/
public RemoteTargetHandle[] assignTargetsForDesktop(
- RemoteAnimationTargets targets, TransitionInfo transitionInfo) {
+ RemoteAnimationTargets targets, @Nullable TransitionInfo transitionInfo) {
resizeRemoteTargetHandles(targets);
for (int i = 0; i < mRemoteTargetHandles.length; i++) {
diff --git a/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java
index 5264643..d2a491d 100644
--- a/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java
@@ -22,35 +22,35 @@
import android.content.Context;
import android.view.MotionEvent;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.util.DaggerSingletonObject;
+import com.android.launcher3.util.DaggerSingletonTracker;
import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.SafeCloseable;
+import com.android.quickstep.dagger.QuickstepBaseAppComponent;
+import javax.inject.Inject;
+
+@LauncherAppSingleton
public class SimpleOrientationTouchTransformer implements
- DisplayController.DisplayInfoChangeListener, SafeCloseable {
+ DisplayController.DisplayInfoChangeListener {
- public static final MainThreadInitializedObject<SimpleOrientationTouchTransformer> INSTANCE =
- new MainThreadInitializedObject<>(SimpleOrientationTouchTransformer::new);
+ public static final DaggerSingletonObject<SimpleOrientationTouchTransformer> INSTANCE =
+ new DaggerSingletonObject<>(
+ QuickstepBaseAppComponent::getSimpleOrientationTouchTransformer);
- 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;
+ @Inject
+ public SimpleOrientationTouchTransformer(@ApplicationContext Context context,
+ DisplayController displayController,
+ DaggerSingletonTracker tracker) {
displayController.addChangeListener(this);
- onDisplayInfoChanged(context, displayController.getInfo(), CHANGE_ALL);
- }
+ tracker.addCloseable(() -> displayController.removeChangeListener(this));
- @Override
- public void close() {
- DisplayController.INSTANCE.get(mContext).removeChangeListener(this);
+ onDisplayInfoChanged(context, displayController.getInfo(), CHANGE_ALL);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.kt b/quickstep/src/com/android/quickstep/SystemUiProxy.kt
index 87953c7..d5382ad 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.kt
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.kt
@@ -126,7 +126,7 @@
private val systemUiProxyDeathRecipient =
IBinder.DeathRecipient { Executors.MAIN_EXECUTOR.execute { clearProxy() } }
- // Save the listeners passed into the proxy since OverviewProxyService may not have been bound
+ // Save the listeners passed into the proxy since LauncherProxyService may not have been bound
// yet, and we'll need to set/register these listeners with SysUI when they do. Note that it is
// up to the caller to clear the listeners to prevent leaks as these can be held indefinitely
// in case SysUI needs to rebind.
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 1fd7211..63b8aaf 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -156,7 +156,7 @@
mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() {
@Override
public void onRecentsAnimationStart(RecentsAnimationController controller,
- RecentsAnimationTargets targets, TransitionInfo transitionInfo) {
+ RecentsAnimationTargets targets, @Nullable TransitionInfo transitionInfo) {
if (enableHandleDelayedGestureCallbacks() && mRecentsAnimationStartPending) {
ActiveGestureProtoLogProxy.logStartRecentsAnimationCallback(
"onRecentsAnimationStart");
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index ff9c9f6..a594e49 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -120,7 +120,8 @@
TaskShortcutFactory.WELLBEING,
TaskShortcutFactory.SAVE_APP_PAIR,
TaskShortcutFactory.SCREENSHOT,
- TaskShortcutFactory.MODAL
+ TaskShortcutFactory.MODAL,
+ TaskShortcutFactory.CLOSE,
};
/**
@@ -233,7 +234,7 @@
RecentsView overviewPanel = mTaskContainer.getTaskView().getRecentsView();
// Task has already been dismissed
if (overviewPanel == null) return;
- overviewPanel.initiateSplitSelect(mTaskContainer.getTaskView());
+ overviewPanel.initiateSplitSelect(mTaskContainer);
}
protected void saveAppPair() {
@@ -369,7 +370,7 @@
@Override
public void onClick(View view) {
- saveScreenshot(mTaskContainer.getTaskView().getFirstTask());
+ saveScreenshot(mTaskContainer.getTask());
dismissTaskMenuView();
}
}
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index ab5e830..2dbd5e9 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -21,6 +21,7 @@
import static android.view.Surface.ROTATION_0;
import static com.android.launcher3.Flags.enableRefactorTaskThumbnail;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_CLOSE_APP_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
@@ -48,6 +49,7 @@
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.popup.SystemShortcut.AppInfo;
import com.android.launcher3.util.InstantAppResolver;
+import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
@@ -128,20 +130,28 @@
};
class SplitSelectSystemShortcut extends SystemShortcut {
- private final TaskView mTaskView;
+ private final TaskContainer mTaskContainer;
private final SplitPositionOption mSplitPositionOption;
- public SplitSelectSystemShortcut(RecentsViewContainer container, TaskView taskView,
+ public SplitSelectSystemShortcut(RecentsViewContainer container,
+ TaskContainer taskContainer, TaskView taskView,
SplitPositionOption option) {
super(option.iconResId, option.textResId, container, taskView.getFirstItemInfo(),
taskView);
- mTaskView = taskView;
+ mTaskContainer = taskContainer;
mSplitPositionOption = option;
}
@Override
public void onClick(View view) {
- mTaskView.initiateSplitSelect(mSplitPositionOption);
+ RecentsView recentsView = mTaskContainer.getTaskView().getRecentsView();
+ if (recentsView != null) {
+ recentsView.initiateSplitSelect(
+ mTaskContainer,
+ mSplitPositionOption.stagePosition,
+ SplitConfigurationOptions.getLogEventForPosition(
+ mSplitPositionOption.stagePosition));
+ }
}
}
@@ -152,7 +162,6 @@
class SaveAppPairSystemShortcut extends SystemShortcut<RecentsViewContainer> {
private final GroupedTaskView mTaskView;
-
public SaveAppPairSystemShortcut(RecentsViewContainer container, GroupedTaskView taskView,
int iconResId) {
super(iconResId, R.string.save_app_pair, container, taskView.getFirstItemInfo(),
@@ -202,14 +211,14 @@
}
private void startActivity() {
- final Task.TaskKey taskKey = mTaskView.getFirstTask().key;
- final int taskId = taskKey.id;
final ActivityOptions options = makeLaunchOptions(mTarget);
- if (options != null) {
- options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
+ if (options == null) {
+ return;
}
- if (options != null
- && ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId,
+ final Task.TaskKey taskKey = mTaskContainer.getTask().key;
+ final int taskId = taskKey.id;
+ options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
+ if (ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId,
options)) {
final Runnable animStartedListener = () -> {
// Hide the task view and wait for the window to be resized
@@ -252,8 +261,8 @@
overridePendingAppTransitionMultiThumbFuture(
future, animStartedListener, mHandler, true /* scaleUp */,
taskKey.displayId);
- mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getFirstItemInfo())
- .log(mLauncherEvent);
+ mTarget.getStatsLogManager().logger().withItemInfo(mTaskContainer.getItemInfo())
+ .log(mLauncherEvent);
}
}
@@ -289,6 +298,29 @@
}
}
+ class CloseSystemShortcut extends SystemShortcut {
+ private final TaskContainer mTaskContainer;
+
+ public CloseSystemShortcut(int iconResId, int textResId, RecentsViewContainer container,
+ TaskContainer taskContainer) {
+ super(iconResId, textResId, container, taskContainer.getTaskView().getFirstItemInfo(),
+ taskContainer.getTaskView());
+ mTaskContainer = taskContainer;
+ }
+
+ @Override
+ public void onClick(View view) {
+ TaskView taskView = mTaskContainer.getTaskView();
+ RecentsView<?, ?> recentsView = taskView.getRecentsView();
+ if (recentsView != null) {
+ dismissTaskMenuView();
+ recentsView.dismissTask(taskView, true, true);
+ mTarget.getStatsLogManager().logger().withItemInfo(mTaskContainer.getItemInfo())
+ .log(LAUNCHER_SYSTEM_SHORTCUT_CLOSE_APP_TAP);
+ }
+ }
+ }
+
/**
* Does NOT add split options in the following scenarios:
* * 1. Taskbar is not present AND aren't at least 2 tasks in overview to show split options for
@@ -327,7 +359,8 @@
return orientationHandler.getSplitPositionOptions(deviceProfile)
.stream()
.map((Function<SplitPositionOption, SystemShortcut>) option ->
- new SplitSelectSystemShortcut(container, taskView, option))
+ new SplitSelectSystemShortcut(container, taskContainer, taskView,
+ option))
.collect(Collectors.toList());
}
};
@@ -420,24 +453,24 @@
private static final String TAG = "PinSystemShortcut";
- private final TaskView mTaskView;
+ private final TaskContainer mTaskContainer;
public PinSystemShortcut(RecentsViewContainer target,
TaskContainer taskContainer) {
super(R.drawable.ic_pin, R.string.recent_task_option_pin, target,
taskContainer.getItemInfo(), taskContainer.getTaskView());
- mTaskView = taskContainer.getTaskView();
+ mTaskContainer = taskContainer;
}
@Override
public void onClick(View view) {
- if (mTaskView.launchAsStaticTile() != null) {
+ if (mTaskContainer.getTaskView().launchAsStaticTile() != null) {
SystemUiProxy.INSTANCE.get(mTarget.asContext()).startScreenPinning(
- mTaskView.getFirstTask().key.id);
+ mTaskContainer.getTask().key.id);
}
dismissTaskMenuView();
- mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getFirstItemInfo())
- .log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP);
+ mTarget.getStatsLogManager().logger().withItemInfo(mTaskContainer.getItemInfo())
+ .log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP);
}
}
@@ -508,4 +541,24 @@
return createSingletonShortcutList(modalStateSystemShortcut);
}
};
+
+ TaskShortcutFactory CLOSE = new TaskShortcutFactory() {
+ @Override
+ public List<SystemShortcut> getShortcuts(RecentsViewContainer container,
+ TaskContainer taskContainer) {
+ return Collections.singletonList(new CloseSystemShortcut(
+ R.drawable.ic_close_option,
+ R.string.recent_task_option_close, container, taskContainer));
+ }
+
+ @Override
+ public boolean showForGroupedTask() {
+ return true;
+ }
+
+ @Override
+ public boolean showForDesktopTask() {
+ return true;
+ }
+ };
}
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 3133907..e47223b 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -123,8 +123,9 @@
int userId = itemInfo.user.getIdentifier();
if (componentName != null) {
for (TaskView taskView : recentsView.getTaskViews()) {
- if (recentsView.isTaskViewVisible(taskView)) {
- Task.TaskKey key = taskView.getFirstTask().key;
+ Task firstTask = taskView.getFirstTask();
+ if (firstTask != null && recentsView.isTaskViewVisible(taskView)) {
+ Task.TaskKey key = firstTask.key;
if (componentName.equals(key.getComponent()) && userId == key.userId) {
return taskView;
}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 15f320d..51e59ff 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -100,7 +100,7 @@
import com.android.quickstep.util.ContextualSearchInvoker;
import com.android.quickstep.util.ContextualSearchStateManager;
import com.android.quickstep.views.RecentsViewContainer;
-import com.android.systemui.shared.recents.IOverviewProxy;
+import com.android.systemui.shared.recents.ILauncherProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.statusbar.phone.BarTransitions;
import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver;
@@ -141,9 +141,9 @@
private final TISBinder mTISBinder = new TISBinder(this);
/**
- * Local IOverviewProxy implementation with some methods for local components
+ * Local ILauncherProxy implementation with some methods for local components
*/
- public static class TISBinder extends IOverviewProxy.Stub {
+ public static class TISBinder extends ILauncherProxy.Stub {
private final WeakReference<TouchInteractionService> mTis;
@@ -301,10 +301,10 @@
@BinderThread
@Override
- public void onDisplayReady(int displayId) {
+ public void onDisplayAddSystemDecorations(int displayId) {
MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(
- tis -> executeForTaskbarManager(
- taskbarManager -> taskbarManager.onDisplayReady(displayId))));
+ tis -> executeForTaskbarManager(taskbarManager ->
+ taskbarManager.onDisplayAddSystemDecorations(displayId))));
}
@BinderThread
@@ -317,6 +317,17 @@
@BinderThread
@Override
+ public void onDisplayRemoveSystemDecorations(int displayId) {
+ // TODO(b/391786915): Replace all
+ // `executeForTouchInteractionService(executeForTaskbarManager())` with just
+ // `executeForTaskbarManager` directly (since `tis` is unused).
+ MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(
+ tis -> executeForTaskbarManager(taskbarManager -> taskbarManager
+ .onDisplayRemoveSystemDecorations(displayId))));
+ }
+
+ @BinderThread
+ @Override
public void updateWallpaperVisibility(int displayId, boolean visible) {
MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(
tis -> executeForTaskbarManager(
@@ -412,7 +423,7 @@
try {
reply.sendResult(null);
} catch (RemoteException e) {
- Log.w(TAG, "onUnbind: Failed to reply to OverviewProxyService", e);
+ Log.w(TAG, "onUnbind: Failed to reply to LauncherProxyService", e);
}
});
}
diff --git a/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java b/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
index fe25f32..adc45ae 100644
--- a/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
+++ b/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
@@ -23,9 +23,11 @@
import com.android.quickstep.OverviewComponentObserver;
import com.android.quickstep.RecentsAnimationDeviceState;
import com.android.quickstep.RotationTouchHelper;
+import com.android.quickstep.SimpleOrientationTouchTransformer;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TopTaskTracker;
import com.android.quickstep.fallback.window.RecentsDisplayModel;
+import com.android.quickstep.logging.SettingsChangeLogger;
import com.android.quickstep.util.AsyncClockEventDelegate;
import com.android.quickstep.util.ContextualSearchStateManager;
@@ -58,4 +60,9 @@
ContextualSearchStateManager getContextualSearchStateManager();
RecentsAnimationDeviceState getRecentsAnimationDeviceState();
+
+ SettingsChangeLogger getSettingsChangeLogger();
+
+ SimpleOrientationTouchTransformer getSimpleOrientationTouchTransformer();
+
}
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index fff7e9b..8d010e2 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -52,6 +52,7 @@
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.RecentsViewContainer;
+import com.android.quickstep.views.TaskContainer;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
@@ -239,10 +240,10 @@
}
@Override
- public void initiateSplitSelect(TaskView taskView,
+ public void initiateSplitSelect(TaskContainer taskContainer,
@SplitConfigurationOptions.StagePosition int stagePosition,
StatsLogManager.EventEnum splitEvent) {
- super.initiateSplitSelect(taskView, stagePosition, splitEvent);
+ super.initiateSplitSelect(taskContainer, stagePosition, splitEvent);
mContainer.getStateManager().goToState(OVERVIEW_SPLIT_SELECT);
}
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
index cda6c1b..3082dc6 100644
--- a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
@@ -41,9 +41,11 @@
import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory
import com.android.launcher3.statemanager.StatefulContainer
import com.android.launcher3.taskbar.TaskbarUIController
+import com.android.launcher3.testing.TestLogging
import com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL
import com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_SPLIT_SELECT_ORDINAL
import com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL
+import com.android.launcher3.testing.shared.TestProtocol.SEQUENCE_MAIN
import com.android.launcher3.util.ContextTracker
import com.android.launcher3.util.DisplayController
import com.android.launcher3.util.RunnableList
@@ -237,6 +239,7 @@
private val onBackInvokedCallback: () -> Unit = {
// If we are in live tile mode, launch the live task, otherwise return home
recentsView?.runningTaskView?.launchWithAnimation() ?: startHome()
+ TestLogging.recordEvent(SEQUENCE_MAIN, "onBackInvoked")
}
private fun cleanupRecentsWindow() {
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowSwipeHandler.java b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowSwipeHandler.java
index 973fb2f..5adc960 100644
--- a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowSwipeHandler.java
@@ -125,7 +125,7 @@
@Override
public void onRecentsAnimationStart(RecentsAnimationController controller,
- RecentsAnimationTargets targets, TransitionInfo transitionInfo) {
+ RecentsAnimationTargets targets, @Nullable TransitionInfo transitionInfo) {
super.onRecentsAnimationStart(controller, targets, transitionInfo);
initTransformParams();
}
diff --git a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
index 946ca2a..0cc349d 100644
--- a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
+++ b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
@@ -44,16 +44,18 @@
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppSingleton;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
import com.android.launcher3.model.DeviceGridState;
+import com.android.launcher3.util.DaggerSingletonObject;
+import com.android.launcher3.util.DaggerSingletonTracker;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.Info;
-import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.NavigationMode;
-import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.SettingsCache;
+import com.android.quickstep.dagger.QuickstepBaseAppComponent;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -61,18 +63,20 @@
import java.io.IOException;
import java.util.Optional;
+import javax.inject.Inject;
+
/**
* Utility class to log launcher settings changes
*/
+@LauncherAppSingleton
public class SettingsChangeLogger implements
- DisplayController.DisplayInfoChangeListener, OnSharedPreferenceChangeListener,
- SafeCloseable {
+ DisplayController.DisplayInfoChangeListener, OnSharedPreferenceChangeListener {
/**
* Singleton instance
*/
- public static MainThreadInitializedObject<SettingsChangeLogger> INSTANCE =
- new MainThreadInitializedObject<>(SettingsChangeLogger::new);
+ public static DaggerSingletonObject<SettingsChangeLogger> INSTANCE =
+ new DaggerSingletonObject<>(QuickstepBaseAppComponent::getSettingsChangeLogger);
private static final String TAG = "SettingsChangeLogger";
private static final String BOOLEAN_PREF = "SwitchPreference";
@@ -85,26 +89,43 @@
private StatsLogManager.LauncherEvent mNotificationDotsEvent;
private StatsLogManager.LauncherEvent mHomeScreenSuggestionEvent;
- SettingsChangeLogger(@ApplicationContext Context context) {
- this(context, StatsLogManager.newInstance(context));
+ private final SettingsCache.OnChangeListener mListener = this::onNotificationDotsChanged;
+
+ @Inject
+ SettingsChangeLogger(@ApplicationContext Context context,
+ DaggerSingletonTracker tracker,
+ DisplayController displayController,
+ SettingsCache settingsCache) {
+ this(context, StatsLogManager.newInstance(context), tracker, displayController,
+ settingsCache);
}
@VisibleForTesting
- SettingsChangeLogger(Context context, StatsLogManager statsLogManager) {
+ SettingsChangeLogger(@ApplicationContext Context context,
+ StatsLogManager statsLogManager,
+ DaggerSingletonTracker tracker,
+ DisplayController displayController,
+ SettingsCache settingsCache) {
mContext = context;
mStatsLogManager = statsLogManager;
mLoggablePrefs = loadPrefKeys(context);
- DisplayController.INSTANCE.get(context).addChangeListener(this);
- mNavMode = DisplayController.getNavigationMode(context);
+ displayController.addChangeListener(this);
+ mNavMode = displayController.getInfo().getNavigationMode();
+ tracker.addCloseable(() -> displayController.removeChangeListener(this));
getPrefs(context).registerOnSharedPreferenceChangeListener(this);
getDevicePrefs(context).registerOnSharedPreferenceChangeListener(this);
+ tracker.addCloseable(() -> {
+ getPrefs(mContext).unregisterOnSharedPreferenceChangeListener(this);
+ getDevicePrefs(mContext).unregisterOnSharedPreferenceChangeListener(this);
+ });
- SettingsCache settingsCache = SettingsCache.INSTANCE.get(context);
- settingsCache.register(NOTIFICATION_BADGING_URI,
- this::onNotificationDotsChanged);
+ settingsCache.register(NOTIFICATION_BADGING_URI, mListener);
onNotificationDotsChanged(settingsCache.getValue(NOTIFICATION_BADGING_URI));
+ tracker.addCloseable(() -> {
+ settingsCache.unregister(NOTIFICATION_BADGING_URI, mListener);
+ });
}
private static ArrayMap<String, LoggablePref> loadPrefKeys(Context context) {
@@ -207,12 +228,6 @@
return mLoggablePrefs;
}
- @Override
- public void close() {
- getPrefs(mContext).unregisterOnSharedPreferenceChangeListener(this);
- getDevicePrefs(mContext).unregisterOnSharedPreferenceChangeListener(this);
- }
-
@VisibleForTesting
static class LoggablePref {
public boolean defaultValue;
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 9bfe71f..b2a30ca 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -31,15 +31,15 @@
import static com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers.ContainerCase.DEVICE_SEARCH_RESULT_CONTAINER;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORKSPACE_SNAPSHOT;
import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DISPLAY_ROTATION__ROTATION_0;
-import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DISPLAY_ROTATION__ROTATION_90;
import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DISPLAY_ROTATION__ROTATION_180;
import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DISPLAY_ROTATION__ROTATION_270;
+import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DISPLAY_ROTATION__ROTATION_90;
import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__ALLAPPS;
import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__BACKGROUND;
import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__HOME;
import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__OVERVIEW;
-import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__RECENTS_ORIENTATION_HANDLER__PORTRAIT;
import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__RECENTS_ORIENTATION_HANDLER__LANDSCAPE;
+import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__RECENTS_ORIENTATION_HANDLER__PORTRAIT;
import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__RECENTS_ORIENTATION_HANDLER__SEASCAPE;
import android.content.Context;
@@ -69,6 +69,7 @@
import com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.model.data.CollectionInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.Executors;
@@ -386,7 +387,8 @@
// and then write to StatsLog.
app.getModel().enqueueModelUpdateTask((taskController, dataModel, apps) ->
write(event, applyOverwrites(mItemInfo.buildProto(
- dataModel.collections.get(mItemInfo.container), mContext))));
+ (CollectionInfo) dataModel.itemsIdMap.get(mItemInfo.container),
+ mContext))));
})) {
// Write log on the model thread so that logs do not go out of order
// (for eg: drop comes after drag)
diff --git a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
index 2f95413..002a4e8 100644
--- a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
@@ -17,7 +17,6 @@
package com.android.quickstep.recents.data
import android.graphics.drawable.Drawable
-import android.graphics.drawable.ShapeDrawable
import android.util.Log
import com.android.launcher3.util.coroutines.DispatcherProvider
import com.android.quickstep.recents.data.TaskVisualsChangedDelegate.TaskIconChangedCallback
@@ -52,37 +51,29 @@
override fun getAllTaskData(forceRefresh: Boolean): Flow<List<Task>> {
if (forceRefresh) {
recentsModel.getTasks { newTaskList ->
- val oldTaskMap = tasks.value
val recentTasks =
newTaskList
.flatMap { groupTask -> groupTask.tasks }
.associateBy { it.key.id }
.also { newTaskMap ->
// Clean tasks that are not in the latest group tasks list.
- val tasksNoLongerVisible = oldTaskMap.keys.subtract(newTaskMap.keys)
+ val tasksNoLongerVisible = tasks.value.keys.subtract(newTaskMap.keys)
removeTasks(tasksNoLongerVisible)
-
- // Use pre-loaded thumbnail data and icon from the previous list.
- // This reduces the Thumbnail loading time in the Overview and prevent
- // empty thumbnail and icon.
- val cache =
- taskRequests.keys
- .mapNotNull { key ->
- val task = oldTaskMap[key] ?: return@mapNotNull null
- key to Pair(task.thumbnail, task.icon)
- }
- .toMap()
-
- newTaskMap.values.forEach { task ->
- task.thumbnail = task.thumbnail ?: cache[task.key.id]?.first
- task.icon = task.icon ?: cache[task.key.id]?.second
- }
}
- tasks.value = MapForStateFlow(recentTasks)
Log.d(
TAG,
- "getAllTaskData: oldTasks ${oldTaskMap.keys}, newTasks: ${recentTasks.keys}",
+ "getAllTaskData: oldTasks ${tasks.value.keys}, newTasks: ${recentTasks.keys}",
)
+ tasks.value = MapForStateFlow(recentTasks)
+
+ // Request data for completed tasks to prevent stale data.
+ // This will prevent thumbnail and icon from being replaced and
+ // null due to race condition.
+ taskRequests.values.forEach { (taskKey, job) ->
+ if (job.isCompleted) {
+ requestTaskData(taskKey.id)
+ }
+ }
}
}
return tasks.map { it.values.toList() }
@@ -202,13 +193,11 @@
private suspend fun getIconFromDataSource(task: Task) =
withContext(dispatcherProvider.background) {
val iconCacheEntry = taskIconDataSource.getIcon(task)
- val icon = iconCacheEntry.icon.constantState?.newDrawable()?.mutate() ?: EMPTY_DRAWABLE
- IconData(icon, iconCacheEntry.contentDescription, iconCacheEntry.title)
+ IconData(iconCacheEntry.icon, iconCacheEntry.contentDescription, iconCacheEntry.title)
}
companion object {
private const val TAG = "TasksRepository"
- private val EMPTY_DRAWABLE = ShapeDrawable()
}
/** Helper class to support StateFlow emissions when using a Map with a MutableStateFlow. */
diff --git a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
index 358537c..02f48e6 100644
--- a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
+++ b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
@@ -26,9 +26,10 @@
import com.android.quickstep.recents.data.TaskVisualsChangedDelegate
import com.android.quickstep.recents.data.TaskVisualsChangedDelegateImpl
import com.android.quickstep.recents.data.TasksRepository
+import com.android.quickstep.recents.domain.usecase.GetSysUiStatusNavFlagsUseCase
+import com.android.quickstep.recents.domain.usecase.GetTaskUseCase
import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
import com.android.quickstep.recents.usecase.GetThumbnailUseCase
-import com.android.quickstep.recents.usecase.SysUiStatusNavFlagsUseCase
import com.android.quickstep.recents.viewmodel.RecentsViewData
import com.android.quickstep.task.thumbnail.SplashAlphaUseCase
import com.android.quickstep.task.thumbnail.TaskThumbnailViewData
@@ -177,12 +178,8 @@
TaskThumbnailViewData::class.java -> TaskThumbnailViewData()
TaskThumbnailViewModel::class.java ->
TaskThumbnailViewModelImpl(
- recentsViewData = inject(),
- taskContainerData = inject(scopeId),
dispatcherProvider = inject(),
getThumbnailPositionUseCase = inject(),
- tasksRepository = inject(),
- deviceProfileRepository = inject(),
splashAlphaUseCase = inject(scopeId),
)
TaskOverlayViewModel::class.java -> {
@@ -195,9 +192,9 @@
dispatcherProvider = inject(),
)
}
+ GetTaskUseCase::class.java -> GetTaskUseCase(repository = inject())
GetThumbnailUseCase::class.java -> GetThumbnailUseCase(taskRepository = inject())
- SysUiStatusNavFlagsUseCase::class.java ->
- SysUiStatusNavFlagsUseCase(taskRepository = inject())
+ GetSysUiStatusNavFlagsUseCase::class.java -> GetSysUiStatusNavFlagsUseCase()
GetThumbnailPositionUseCase::class.java ->
GetThumbnailPositionUseCase(
deviceProfileRepository = inject(),
diff --git a/quickstep/src/com/android/quickstep/recents/domain/model/TaskModel.kt b/quickstep/src/com/android/quickstep/recents/domain/model/TaskModel.kt
index 3823100..bf29b1d 100644
--- a/quickstep/src/com/android/quickstep/recents/domain/model/TaskModel.kt
+++ b/quickstep/src/com/android/quickstep/recents/domain/model/TaskModel.kt
@@ -38,7 +38,7 @@
*/
data class TaskModel(
val id: TaskId,
- val title: String,
+ val title: String?,
val titleDescription: String?,
val icon: Drawable?,
val thumbnail: ThumbnailData?,
diff --git a/quickstep/src/com/android/quickstep/recents/domain/usecase/GetSysUiStatusNavFlagsUseCase.kt b/quickstep/src/com/android/quickstep/recents/domain/usecase/GetSysUiStatusNavFlagsUseCase.kt
new file mode 100644
index 0000000..da0e2d1
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/domain/usecase/GetSysUiStatusNavFlagsUseCase.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.domain.usecase
+
+import android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
+import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
+import com.android.launcher3.util.SystemUiController.FLAG_DARK_NAV
+import com.android.launcher3.util.SystemUiController.FLAG_DARK_STATUS
+import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_NAV
+import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_STATUS
+import com.android.systemui.shared.recents.model.ThumbnailData
+
+/** UseCase to calculate flags for status bar and navigation bar */
+class GetSysUiStatusNavFlagsUseCase {
+ operator fun invoke(thumbnailData: ThumbnailData?): Int {
+ if (thumbnailData == null) return 0
+ val thumbnailAppearance = thumbnailData.appearance
+ var flags = 0
+ flags =
+ flags or
+ if (thumbnailAppearance and APPEARANCE_LIGHT_STATUS_BARS != 0) FLAG_LIGHT_STATUS
+ else FLAG_DARK_STATUS
+ flags =
+ flags or
+ if (thumbnailAppearance and APPEARANCE_LIGHT_NAVIGATION_BARS != 0) FLAG_LIGHT_NAV
+ else FLAG_DARK_NAV
+ return flags
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapper.kt b/quickstep/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapper.kt
new file mode 100644
index 0000000..fb62268
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapper.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.ui.mapper
+
+import com.android.quickstep.recents.ui.viewmodel.TaskData
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.SnapshotSplash
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.ThumbnailHeader
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
+
+object TaskUiStateMapper {
+
+ /**
+ * Converts a [TaskData] object into a [TaskThumbnailUiState] for display in the UI.
+ *
+ * This function handles different types of [TaskData] and determines the appropriate UI state
+ * based on the data and provided flags.
+ *
+ * @param taskData The [TaskData] to convert. Can be null or a specific subclass.
+ * @param isLiveTile A flag indicating whether the task data represents live tile.
+ * @param hasHeader A flag indicating whether the UI should display a header.
+ * @return A [TaskThumbnailUiState] representing the UI state for the given task data.
+ */
+ fun toTaskThumbnailUiState(
+ taskData: TaskData?,
+ isLiveTile: Boolean,
+ hasHeader: Boolean,
+ ): TaskThumbnailUiState =
+ when {
+ taskData !is TaskData.Data -> Uninitialized
+ isLiveTile -> createLiveTileState(taskData, hasHeader)
+ isBackgroundOnly(taskData) -> BackgroundOnly(taskData.backgroundColor)
+ isSnapshotSplash(taskData) ->
+ SnapshotSplash(createSnapshotState(taskData, hasHeader), taskData.icon)
+ else -> Uninitialized
+ }
+
+ private fun createSnapshotState(taskData: TaskData.Data, hasHeader: Boolean): Snapshot =
+ if (canHeaderBeCreated(taskData, hasHeader)) {
+ Snapshot.WithHeader(
+ taskData.thumbnailData?.thumbnail!!,
+ taskData.thumbnailData.rotation,
+ taskData.backgroundColor,
+ ThumbnailHeader(taskData.icon!!, taskData.titleDescription!!),
+ )
+ } else {
+ Snapshot.WithoutHeader(
+ taskData.thumbnailData?.thumbnail!!,
+ taskData.thumbnailData.rotation,
+ taskData.backgroundColor,
+ )
+ }
+
+ private fun isBackgroundOnly(taskData: TaskData.Data) =
+ taskData.isLocked || taskData.thumbnailData == null
+
+ private fun isSnapshotSplash(taskData: TaskData.Data) =
+ taskData.thumbnailData?.thumbnail != null && !taskData.isLocked
+
+ private fun canHeaderBeCreated(taskData: TaskData.Data, hasHeader: Boolean) =
+ hasHeader && taskData.icon != null && taskData.titleDescription != null
+
+ private fun createLiveTileState(taskData: TaskData.Data, hasHeader: Boolean) =
+ if (canHeaderBeCreated(taskData, hasHeader)) {
+ // TODO(http://b/353965691): figure out what to do when `icon` or `titleDescription` is
+ // null.
+ LiveTile.WithHeader(ThumbnailHeader(taskData.icon!!, taskData.titleDescription!!))
+ } else LiveTile.WithoutHeader
+}
diff --git a/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskTileUiState.kt b/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskTileUiState.kt
index 5f98479..118a931 100644
--- a/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskTileUiState.kt
+++ b/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskTileUiState.kt
@@ -29,29 +29,39 @@
* @property tasks The list of [TaskTileUiState] objects representing the individual tasks.
* @property isLiveTile Indicates whether this data is intended for a live tile. If `true`, the
* running app will be displayed instead of the thumbnail.
+ * @property sysUiStatusNavFlags Flags for status bar and navigation bar
*/
-data class TaskTileUiState(val tasks: List<TaskData>, val isLiveTile: Boolean)
+data class TaskTileUiState(
+ val tasks: List<TaskData>,
+ val isLiveTile: Boolean,
+ val hasHeader: Boolean,
+ val sysUiStatusNavFlags: Int,
+)
-sealed interface TaskData {
+sealed class TaskData {
+ abstract val taskId: Int
+
/** When no data was found for the TaskId provided */
- data class NoData(val taskId: Int) : TaskData
+ data class NoData(override val taskId: Int) : TaskData()
/**
* This class provides UI information related to a Task (App) to be displayed within a TaskView.
*
* @property taskId Identifier of the task
* @property title App title
+ * @property titleDescription App content description
* @property icon App icon
* @property thumbnailData Information related to the last snapshot retrieved from the app
* @property backgroundColor The background color of the task.
* @property isLocked Indicates whether the task is locked or not.
*/
data class Data(
- val taskId: Int,
- val title: String,
+ override val taskId: Int,
+ val title: String?,
+ val titleDescription: String?,
val icon: Drawable?,
val thumbnailData: ThumbnailData?,
val backgroundColor: Int,
val isLocked: Boolean,
- ) : TaskData
+ ) : TaskData()
}
diff --git a/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModel.kt b/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModel.kt
index 2e51a8a..4936e30 100644
--- a/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModel.kt
+++ b/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModel.kt
@@ -16,12 +16,16 @@
package com.android.quickstep.recents.ui.viewmodel
+import android.annotation.ColorInt
import android.util.Log
+import androidx.core.graphics.ColorUtils
import com.android.launcher3.util.coroutines.DispatcherProvider
import com.android.quickstep.recents.domain.model.TaskId
import com.android.quickstep.recents.domain.model.TaskModel
+import com.android.quickstep.recents.domain.usecase.GetSysUiStatusNavFlagsUseCase
import com.android.quickstep.recents.domain.usecase.GetTaskUseCase
import com.android.quickstep.recents.viewmodel.RecentsViewData
+import com.android.quickstep.views.TaskViewType
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -37,8 +41,10 @@
*/
@OptIn(ExperimentalCoroutinesApi::class)
class TaskViewModel(
+ private val taskViewType: TaskViewType,
recentsViewData: RecentsViewData,
private val getTaskUseCase: GetTaskUseCase,
+ private val getSysUiStatusNavFlagsUseCase: GetSysUiStatusNavFlagsUseCase,
dispatcherProvider: DispatcherProvider,
) {
private var taskIds = MutableStateFlow(emptySet<Int>())
@@ -53,16 +59,20 @@
}
.distinctUntilChanged()
+ private val taskData =
+ taskIds.flatMapLatest { ids ->
+ // Combine Tasks requests
+ combine(
+ ids.map { id -> getTaskUseCase(id).map { taskModel -> id to taskModel } },
+ ::mapToTaskData,
+ )
+ }
+
+ val tintAmount: Flow<Float> = recentsViewData.tintAmount
+
val state: Flow<TaskTileUiState> =
- taskIds
- .flatMapLatest { ids ->
- // Combine Tasks requests
- combine(
- ids.map { id -> getTaskUseCase(id).map { taskModel -> id to taskModel } },
- ::mapToUiState,
- )
- }
- .combine(isLiveTile) { tasks, isLiveTile -> TaskTileUiState(tasks, isLiveTile) }
+ combine(taskData, isLiveTile) { tasks, isLiveTile -> mapToTaskTile(tasks, isLiveTile) }
+ .distinctUntilChanged()
.flowOn(dispatcherProvider.background)
fun bind(vararg taskId: TaskId) {
@@ -70,21 +80,34 @@
taskIds.value = taskId.toSet()
}
- private fun mapToUiState(result: Array<Pair<TaskId, TaskModel?>>): List<TaskData> =
- result.map { mapToUiState(it.first, it.second) }
+ private fun mapToTaskTile(tasks: List<TaskData>, isLiveTile: Boolean): TaskTileUiState {
+ val firstThumbnailData = (tasks.firstOrNull() as? TaskData.Data)?.thumbnailData
+ return TaskTileUiState(
+ tasks = tasks,
+ isLiveTile = isLiveTile,
+ hasHeader = taskViewType == TaskViewType.DESKTOP,
+ sysUiStatusNavFlags = getSysUiStatusNavFlagsUseCase(firstThumbnailData),
+ )
+ }
- private fun mapToUiState(taskId: TaskId, result: TaskModel?): TaskData =
+ private fun mapToTaskData(result: Array<Pair<TaskId, TaskModel?>>): List<TaskData> =
+ result.map { mapToTaskData(it.first, it.second) }
+
+ private fun mapToTaskData(taskId: TaskId, result: TaskModel?): TaskData =
result?.let {
TaskData.Data(
taskId = taskId,
title = result.title,
+ titleDescription = result.titleDescription,
icon = result.icon,
thumbnailData = result.thumbnail,
- backgroundColor = result.backgroundColor,
+ backgroundColor = result.backgroundColor.removeAlpha(),
isLocked = result.isLocked,
)
} ?: TaskData.NoData(taskId)
+ @ColorInt private fun Int.removeAlpha(): Int = ColorUtils.setAlphaComponent(this, 0xff)
+
private companion object {
const val TAG = "TaskViewModel"
}
diff --git a/quickstep/src/com/android/quickstep/recents/usecase/SysUiStatusNavFlagsUseCase.kt b/quickstep/src/com/android/quickstep/recents/usecase/SysUiStatusNavFlagsUseCase.kt
deleted file mode 100644
index 5be5f4a..0000000
--- a/quickstep/src/com/android/quickstep/recents/usecase/SysUiStatusNavFlagsUseCase.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quickstep.recents.usecase
-
-import android.view.WindowInsetsController
-import com.android.launcher3.util.SystemUiController.FLAG_DARK_NAV
-import com.android.launcher3.util.SystemUiController.FLAG_DARK_STATUS
-import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_NAV
-import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_STATUS
-import com.android.quickstep.recents.data.RecentTasksRepository
-
-/** UseCase to calculate flags for status bar and navigation bar */
-class SysUiStatusNavFlagsUseCase(private val taskRepository: RecentTasksRepository) {
- fun getSysUiStatusNavFlags(taskId: Int): Int {
- val thumbnailData = taskRepository.getCurrentThumbnailById(taskId) ?: return 0
-
- val thumbnailAppearance = thumbnailData.appearance
- var flags = 0
- flags =
- flags or
- if (
- thumbnailAppearance and WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS != 0
- )
- FLAG_LIGHT_STATUS
- else FLAG_DARK_STATUS
- flags =
- flags or
- if (
- thumbnailAppearance and
- WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS != 0
- )
- FLAG_LIGHT_NAV
- else FLAG_DARK_NAV
- return flags
- }
-}
diff --git a/quickstep/src/com/android/quickstep/recents/viewmodel/TaskContainerViewModel.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/TaskContainerViewModel.kt
index 168c1e0..723df55 100644
--- a/quickstep/src/com/android/quickstep/recents/viewmodel/TaskContainerViewModel.kt
+++ b/quickstep/src/com/android/quickstep/recents/viewmodel/TaskContainerViewModel.kt
@@ -16,23 +16,11 @@
package com.android.quickstep.recents.viewmodel
-import android.graphics.Bitmap
-import com.android.quickstep.recents.usecase.GetThumbnailUseCase
-import com.android.quickstep.recents.usecase.SysUiStatusNavFlagsUseCase
import com.android.quickstep.task.thumbnail.SplashAlphaUseCase
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.runBlocking
-class TaskContainerViewModel(
- private val sysUiStatusNavFlagsUseCase: SysUiStatusNavFlagsUseCase,
- private val getThumbnailUseCase: GetThumbnailUseCase,
- private val splashAlphaUseCase: SplashAlphaUseCase,
-) {
- fun getThumbnail(taskId: Int): Bitmap? = getThumbnailUseCase.run(taskId)
-
- fun getSysUiStatusNavFlags(taskId: Int) =
- sysUiStatusNavFlagsUseCase.getSysUiStatusNavFlags(taskId)
-
+class TaskContainerViewModel(private val splashAlphaUseCase: SplashAlphaUseCase) {
fun shouldShowThumbnailSplash(taskId: Int): Boolean =
(runBlocking { splashAlphaUseCase.execute(taskId).firstOrNull() } ?: 0f) > 0f
}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
index 02baa39..34b0206 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
@@ -20,6 +20,7 @@
import android.graphics.Color
import android.graphics.Outline
import android.graphics.Rect
+import android.graphics.drawable.ShapeDrawable
import android.util.AttributeSet
import android.util.Log
import android.view.LayoutInflater
@@ -29,7 +30,9 @@
import androidx.annotation.ColorInt
import androidx.core.view.isInvisible
import com.android.launcher3.Flags.enableDesktopExplodedView
+import com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA
import com.android.launcher3.R
+import com.android.launcher3.util.MultiPropertyFactory
import com.android.launcher3.util.ViewPool
import com.android.launcher3.util.coroutines.DispatcherProvider
import com.android.quickstep.recents.di.RecentsDependencies
@@ -70,11 +73,13 @@
private val thumbnailView: FixedSizeImageView by lazy { findViewById(R.id.task_thumbnail) }
private val splashBackground: View by lazy { findViewById(R.id.splash_background) }
private val splashIcon: FixedSizeImageView by lazy { findViewById(R.id.splash_icon) }
+ private val dimAlpha: MultiPropertyFactory<View> by lazy {
+ MultiPropertyFactory(scrimView, VIEW_ALPHA, ScrimViewAlpha.entries.size, ::maxOf)
+ }
private var taskThumbnailViewHeader: TaskThumbnailViewHeader? = null
private var uiState: TaskThumbnailUiState = Uninitialized
-
private val bounds = Rect()
var cornerRadius: Float = 0f
@@ -108,26 +113,6 @@
viewData = RecentsDependencies.get(this)
updateViewDataValues()
viewModel = RecentsDependencies.get(this)
- viewModel.uiState
- .dropWhile { it == Uninitialized }
- .flowOn(dispatcherProvider.background)
- .onEach { viewModelUiState ->
- Log.d(TAG, "viewModelUiState changed from: $uiState to: $viewModelUiState")
- uiState = viewModelUiState
- resetViews()
- when (viewModelUiState) {
- is Uninitialized -> {}
- is LiveTile -> drawLiveWindow(viewModelUiState)
- is SnapshotSplash -> drawSnapshotSplash(viewModelUiState)
- is BackgroundOnly -> drawBackground(viewModelUiState.backgroundColor)
- }
- }
- .launchIn(viewAttachedScope)
- viewModel.dimProgress
- .dropWhile { it == 0f }
- .flowOn(dispatcherProvider.background)
- .onEach { dimProgress -> scrimView.alpha = dimProgress }
- .launchIn(viewAttachedScope)
viewModel.splashAlpha
.dropWhile { it == 0f }
.flowOn(dispatcherProvider.background)
@@ -166,6 +151,27 @@
}
}
+ fun setState(state: TaskThumbnailUiState) {
+ Log.d(TAG, "viewModelUiState changed from: $uiState to: $state")
+ if (uiState == state) return
+ uiState = state
+ resetViews()
+ when (state) {
+ is Uninitialized -> {}
+ is LiveTile -> drawLiveWindow(state)
+ is SnapshotSplash -> drawSnapshotSplash(state)
+ is BackgroundOnly -> drawBackground(state.backgroundColor)
+ }
+ }
+
+ fun updateTintAmount(tintAmount: Float) {
+ dimAlpha[ScrimViewAlpha.TintAmount.ordinal].value = tintAmount
+ }
+
+ fun updateMenuOpenProgress(progress: Float) {
+ dimAlpha[ScrimViewAlpha.MenuProgress.ordinal].value = progress * MAX_SCRIM_ALPHA
+ }
+
private fun updateViewDataValues() {
viewData.width.value = width
viewData.height.value = height
@@ -219,7 +225,8 @@
drawSnapshot(snapshotSplash.snapshot)
splashBackground.setBackgroundColor(snapshotSplash.snapshot.backgroundColor)
- splashIcon.setImageDrawable(snapshotSplash.splash)
+ val icon = snapshotSplash.splash?.constantState?.newDrawable()?.mutate() ?: ShapeDrawable()
+ splashIcon.setImageDrawable(icon)
}
private fun drawSnapshot(snapshot: Snapshot) {
@@ -238,10 +245,6 @@
thumbnailView.imageMatrix = viewModel.getThumbnailPositionState(width, height, isLayoutRtl)
}
- private companion object {
- const val TAG = "TaskThumbnailView"
- }
-
private fun maybeCreateHeader() {
if (enableDesktopExplodedView() && taskThumbnailViewHeader == null) {
taskThumbnailViewHeader =
@@ -251,4 +254,14 @@
addView(taskThumbnailViewHeader)
}
}
+
+ private companion object {
+ const val TAG = "TaskThumbnailView"
+ private const val MAX_SCRIM_ALPHA = 0.4f
+
+ enum class ScrimViewAlpha {
+ MenuProgress,
+ TintAmount,
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskContainerData.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskContainerData.kt
index 5f2de94..279ce39 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskContainerData.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskContainerData.kt
@@ -19,7 +19,5 @@
import kotlinx.coroutines.flow.MutableStateFlow
class TaskContainerData {
- val taskMenuOpenProgress = MutableStateFlow(0f)
-
val thumbnailSplashProgress = MutableStateFlow(0f)
}
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
index a048a1d..c89bf01 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
@@ -17,20 +17,13 @@
package com.android.quickstep.task.viewmodel
import android.graphics.Matrix
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState
import kotlinx.coroutines.flow.Flow
/** ViewModel for representing TaskThumbnails */
interface TaskThumbnailViewModel {
- /** Provides the level of dimming that the View should have */
- val dimProgress: Flow<Float>
-
/** Provides the alpha of the splash icon */
val splashAlpha: Flow<Float>
- /** Provides the UiState by which the task thumbnail can be represented */
- val uiState: Flow<TaskThumbnailUiState>
-
/** Attaches this ViewModel to a specific task id for it to provide data from. */
fun bind(taskId: Int)
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
index a154c3c..635d08b 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
@@ -16,100 +16,34 @@
package com.android.quickstep.task.viewmodel
-import android.annotation.ColorInt
import android.app.ActivityTaskManager.INVALID_TASK_ID
-import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.graphics.Matrix
import android.util.Log
-import androidx.core.graphics.ColorUtils
-import com.android.launcher3.Flags.enableDesktopExplodedView
import com.android.launcher3.util.coroutines.DispatcherProvider
-import com.android.quickstep.recents.data.RecentTasksRepository
-import com.android.quickstep.recents.data.RecentsDeviceProfileRepository
import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
import com.android.quickstep.recents.usecase.ThumbnailPositionState
-import com.android.quickstep.recents.viewmodel.RecentsViewData
import com.android.quickstep.task.thumbnail.SplashAlphaUseCase
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.SnapshotSplash
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.ThumbnailHeader
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
-import com.android.systemui.shared.recents.model.Task
-import kotlin.math.max
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
@OptIn(ExperimentalCoroutinesApi::class)
class TaskThumbnailViewModelImpl(
- recentsViewData: RecentsViewData,
- taskContainerData: TaskContainerData,
dispatcherProvider: DispatcherProvider,
- private val tasksRepository: RecentTasksRepository,
- private val deviceProfileRepository: RecentsDeviceProfileRepository,
private val getThumbnailPositionUseCase: GetThumbnailPositionUseCase,
private val splashAlphaUseCase: SplashAlphaUseCase,
) : TaskThumbnailViewModel {
- private val task = MutableStateFlow<Flow<Task?>>(flowOf(null))
private val splashProgress = MutableStateFlow(flowOf(0f))
private var taskId: Int = INVALID_TASK_ID
- override val dimProgress: Flow<Float> =
- combine(taskContainerData.taskMenuOpenProgress, recentsViewData.tintAmount) {
- taskMenuOpenProgress,
- tintAmount ->
- max(taskMenuOpenProgress * MAX_SCRIM_ALPHA, tintAmount)
- }
- .flowOn(dispatcherProvider.background)
-
override val splashAlpha =
splashProgress.flatMapLatest { it }.flowOn(dispatcherProvider.background)
- private val isLiveTile =
- combine(
- task.flatMapLatest { it }.map { it?.key?.id }.distinctUntilChanged(),
- recentsViewData.runningTaskIds,
- recentsViewData.runningTaskShowScreenshot,
- ) { taskId, runningTaskIds, runningTaskShowScreenshot ->
- runningTaskIds.contains(taskId) && !runningTaskShowScreenshot
- }
- .distinctUntilChanged()
-
- override val uiState: Flow<TaskThumbnailUiState> =
- combine(task.flatMapLatest { it }, isLiveTile) { taskVal, isRunning ->
- // TODO(b/369339561) This log is firing a lot. Reduce emissions from TasksRepository
- // then re-enable this log.
- // Log.d(
- // TAG,
- // "Received task and / or live tile update. taskVal: $taskVal"
- // + " isRunning: $isRunning.",
- // )
- when {
- taskVal == null -> Uninitialized
- isRunning -> createLiveTileState(taskVal)
- isBackgroundOnly(taskVal) ->
- BackgroundOnly(taskVal.colorBackground.removeAlpha())
- isSnapshotSplashState(taskVal) ->
- SnapshotSplash(createSnapshotState(taskVal), taskVal.icon)
- else -> Uninitialized
- }
- }
- .distinctUntilChanged()
- .flowOn(dispatcherProvider.background)
-
override fun bind(taskId: Int) {
Log.d(TAG, "bind taskId: $taskId")
this.taskId = taskId
- task.value = tasksRepository.getTaskDataById(taskId)
splashProgress.value = splashAlphaUseCase.execute(taskId)
}
@@ -122,64 +56,7 @@
is ThumbnailPositionState.MissingThumbnail -> Matrix.IDENTITY_MATRIX
}
- private fun isBackgroundOnly(task: Task): Boolean = task.isLocked || task.thumbnail == null
-
- private fun isSnapshotSplashState(task: Task): Boolean {
- val thumbnailPresent = task.thumbnail?.thumbnail != null
- val taskLocked = task.isLocked
-
- return thumbnailPresent && !taskLocked
- }
-
- private fun createSnapshotState(task: Task): Snapshot {
- val thumbnailData = task.thumbnail
- val bitmap = thumbnailData?.thumbnail!!
- var thumbnailHeader = maybeCreateHeader(task)
- return if (thumbnailHeader != null)
- Snapshot.WithHeader(
- bitmap,
- thumbnailData.rotation,
- task.colorBackground.removeAlpha(),
- thumbnailHeader,
- )
- else
- Snapshot.WithoutHeader(
- bitmap,
- thumbnailData.rotation,
- task.colorBackground.removeAlpha(),
- )
- }
-
- private fun shouldHaveThumbnailHeader(task: Task): Boolean {
- return deviceProfileRepository.getRecentsDeviceProfile().canEnterDesktopMode &&
- enableDesktopExplodedView() &&
- task.key.windowingMode == WINDOWING_MODE_FREEFORM
- }
-
- private fun maybeCreateHeader(task: Task): ThumbnailHeader? {
- // Header is only needed when this task is a desktop task and Overivew exploded view is
- // enabled.
- if (!shouldHaveThumbnailHeader(task)) {
- return null
- }
-
- // TODO(http://b/353965691): figure out what to do when `icon` or `titleDescription` is
- // null.
- val icon = task.icon ?: return null
- val titleDescription = task.titleDescription ?: return null
- return ThumbnailHeader(icon, titleDescription)
- }
-
- private fun createLiveTileState(task: Task): LiveTile {
- val thumbnailHeader = maybeCreateHeader(task)
- return if (thumbnailHeader != null) LiveTile.WithHeader(thumbnailHeader)
- else LiveTile.WithoutHeader
- }
-
- @ColorInt private fun Int.removeAlpha(): Int = ColorUtils.setAlphaComponent(this, 0xff)
-
private companion object {
- const val MAX_SCRIM_ALPHA = 0.4f
const val TAG = "TaskThumbnailViewModel"
}
}
diff --git a/quickstep/src/com/android/quickstep/util/ActiveTrackpadList.kt b/quickstep/src/com/android/quickstep/util/ActiveTrackpadList.kt
index 63bd03d..a1ff0ce 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveTrackpadList.kt
+++ b/quickstep/src/com/android/quickstep/util/ActiveTrackpadList.kt
@@ -30,7 +30,7 @@
init {
inputManager.registerInputDeviceListener(this, Executors.UI_HELPER_EXECUTOR.handler)
- inputManager.inputDeviceIds.forEach { deviceId -> onInputDeviceAdded(deviceId) }
+ inputManager.inputDeviceIds.filter(this::isTrackpadDevice).forEach(this::add)
}
override fun onInputDeviceAdded(deviceId: Int) {
diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java
index 6b8650f..f20d7a5 100644
--- a/quickstep/src/com/android/quickstep/util/AppPairsController.java
+++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java
@@ -128,26 +128,25 @@
& ItemInfoWithIcon.FLAG_NOT_PINNABLE) != 0));
if (!taskView.containsMultipleTasks()
|| hasUnpinnableApp
- || !(taskView instanceof GroupedTaskView)) {
+ || !(taskView instanceof GroupedTaskView groupedTaskView)) {
return false;
}
- GroupedTaskView gtv = (GroupedTaskView) taskView;
- List<TaskContainer> containers = gtv.getTaskContainers();
- ComponentKey taskKey1 = TaskUtils.getLaunchComponentKeyForTask(
- containers.get(0).getTask().key);
- ComponentKey taskKey2 = TaskUtils.getLaunchComponentKeyForTask(
- containers.get(1).getTask().key);
- AppInfo app1 = resolveAppInfoByComponent(taskKey1);
- AppInfo app2 = resolveAppInfoByComponent(taskKey2);
+ ComponentKey leftTopComponentKey = TaskUtils.getLaunchComponentKeyForTask(
+ groupedTaskView.getLeftTopTaskContainer().getTask().key);
+ ComponentKey rightBottomComponentKey = TaskUtils.getLaunchComponentKeyForTask(
+ groupedTaskView.getRightBottomTaskContainer().getTask().key);
+ AppInfo leftTopAppInfo = resolveAppInfoByComponent(leftTopComponentKey);
+ AppInfo rightBottomAppInfo = resolveAppInfoByComponent(rightBottomComponentKey);
- if (app1 == null || app2 == null) {
+ if (leftTopAppInfo == null || rightBottomAppInfo == null) {
// Disallow saving app pairs for apps that don't have a front-door in Launcher
return false;
}
- if (PackageManagerHelper.isSameAppForMultiInstance(app1, app2)) {
- if (!app1.supportsMultiInstance() || !app2.supportsMultiInstance()) {
+ if (PackageManagerHelper.isSameAppForMultiInstance(leftTopAppInfo, rightBottomAppInfo)) {
+ if (!leftTopAppInfo.supportsMultiInstance()
+ || !rightBottomAppInfo.supportsMultiInstance()) {
return false;
}
}
@@ -183,9 +182,8 @@
return;
}
- List<TaskContainer> containers = gtv.getTaskContainers();
List<TaskViewItemInfo> recentsInfos =
- containers.stream().map(TaskContainer::getItemInfo).toList();
+ gtv.getTaskContainers().stream().map(TaskContainer::getItemInfo).toList();
List<WorkspaceItemInfo> apps =
recentsInfos.stream().map(this::resolveAppPairWorkspaceInfo).toList();
diff --git a/quickstep/src/com/android/quickstep/util/ExternalDisplays.kt b/quickstep/src/com/android/quickstep/util/ExternalDisplays.kt
new file mode 100644
index 0000000..e1a8578
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/ExternalDisplays.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util
+
+import android.view.Display.DEFAULT_DISPLAY
+import com.android.systemui.shared.recents.model.Task
+
+/** Whether this displayId belongs to an external display */
+val Int.isExternalDisplay
+ get() = this != DEFAULT_DISPLAY
+
+/** Returns displayId of this [Task], default to [DEFAULT_DISPLAY] */
+val Task?.displayId
+ get() = this?.key?.displayId ?: DEFAULT_DISPLAY
+
+/** Returns if this task belongs tto [DEFAULT_DISPLAY] */
+val Task?.isExternalDisplay
+ get() = displayId.isExternalDisplay
diff --git a/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt b/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
index 1dab18a..e353160 100644
--- a/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
+++ b/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
@@ -21,7 +21,9 @@
import android.graphics.drawable.shapes.RoundRectShape
import android.util.AttributeSet
import android.widget.ImageButton
+import com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X
import com.android.launcher3.R
+import com.android.launcher3.util.MultiPropertyFactory
/**
* Button for supporting multiple desktop sessions. The button will be next to the first TaskView
@@ -30,6 +32,29 @@
class AddDesktopButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
ImageButton(context, attrs) {
+ private enum class TranslationX {
+ GRID,
+ OFFSET,
+ }
+
+ private val multiTranslationX =
+ MultiPropertyFactory(this, VIEW_TRANSLATE_X, TranslationX.entries.size) { a: Float, b: Float
+ ->
+ a + b
+ }
+
+ var gridTranslationX
+ get() = multiTranslationX[TranslationX.GRID.ordinal].value
+ set(value) {
+ multiTranslationX[TranslationX.GRID.ordinal].value = value
+ }
+
+ var offsetTranslationX
+ get() = multiTranslationX[TranslationX.OFFSET.ordinal].value
+ set(value) {
+ multiTranslationX[TranslationX.OFFSET.ordinal].value = value
+ }
+
override fun onFinishInflate() {
super.onFinishInflate()
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
index 38ffe50..229c8f5 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
@@ -55,6 +55,12 @@
private val MINIMUM_RATIO_TO_SHOW_ICON = 0.2f
+ val leftTopTaskContainer: TaskContainer
+ get() = taskContainers[0]
+
+ val rightBottomTaskContainer: TaskContainer
+ get() = taskContainers[1]
+
// TODO(b/336612373): Support new TTV for GroupedTaskView
var splitBoundsConfig: SplitConfigurationOptions.SplitBounds? = null
private set
@@ -72,8 +78,8 @@
val splitBoundsConfig = splitBoundsConfig ?: return
val inSplitSelection = getThisTaskCurrentlyInSplitSelection() != INVALID_TASK_ID
pagedOrientationHandler.measureGroupedTaskViewThumbnailBounds(
- taskContainers[0].snapshotView,
- taskContainers[1].snapshotView,
+ leftTopTaskContainer.snapshotView,
+ rightBottomTaskContainer.snapshotView,
widthSize,
heightSize,
splitBoundsConfig,
@@ -165,10 +171,10 @@
val iconMargins = (iconViewMarginStart + iconViewBackgroundMarginStart) * 2
// setMaxWidth() needs to be called before mIconView.setIconOrientation which is
// called in the super below.
- (taskContainers[0].iconView as IconAppChipView).setMaxWidth(
+ (leftTopTaskContainer.iconView as IconAppChipView).setMaxWidth(
groupedTaskViewSizes.first.x - iconMargins
)
- (taskContainers[1].iconView as IconAppChipView).setMaxWidth(
+ (rightBottomTaskContainer.iconView as IconAppChipView).setMaxWidth(
groupedTaskViewSizes.second.x - iconMargins
)
}
@@ -189,16 +195,12 @@
if (deviceProfile.isLeftRightSplit) splitBoundsConfig.leftTaskPercent
else splitBoundsConfig.topTaskPercent
val bottomRightTaskPercent = 1 - topLeftTaskPercent
- taskContainers[0]
- .iconView
- .setFlexSplitAlpha(
- if (topLeftTaskPercent < MINIMUM_RATIO_TO_SHOW_ICON) 0f else 1f
- )
- taskContainers[1]
- .iconView
- .setFlexSplitAlpha(
- if (bottomRightTaskPercent < MINIMUM_RATIO_TO_SHOW_ICON) 0f else 1f
- )
+ leftTopTaskContainer.iconView.setFlexSplitAlpha(
+ if (topLeftTaskPercent < MINIMUM_RATIO_TO_SHOW_ICON) 0f else 1f
+ )
+ rightBottomTaskContainer.iconView.setFlexSplitAlpha(
+ if (bottomRightTaskPercent < MINIMUM_RATIO_TO_SHOW_ICON) 0f else 1f
+ )
}
if (enableOverviewIconMenu()) {
@@ -210,8 +212,8 @@
layoutParams.height,
)
pagedOrientationHandler.setSplitIconParams(
- taskContainers[0].iconView.asView(),
- taskContainers[1].iconView.asView(),
+ leftTopTaskContainer.iconView.asView(),
+ rightBottomTaskContainer.iconView.asView(),
taskIconHeight,
groupedTaskViewSizes.first.x,
groupedTaskViewSizes.first.y,
@@ -224,11 +226,11 @@
)
} else {
pagedOrientationHandler.setSplitIconParams(
- taskContainers[0].iconView.asView(),
- taskContainers[1].iconView.asView(),
+ leftTopTaskContainer.iconView.asView(),
+ rightBottomTaskContainer.iconView.asView(),
taskIconHeight,
- taskContainers[0].snapshotView.measuredWidth,
- taskContainers[0].snapshotView.measuredHeight,
+ leftTopTaskContainer.snapshotView.measuredWidth,
+ leftTopTaskContainer.snapshotView.measuredHeight,
measuredHeight,
measuredWidth,
isRtl,
@@ -288,8 +290,8 @@
recentsView?.let {
it.splitSelectController.launchExistingSplitPair(
if (launchingExistingTaskView) this else null,
- taskContainers[0].task.key.id,
- taskContainers[1].task.key.id,
+ leftTopTaskContainer.task.key.id,
+ rightBottomTaskContainer.task.key.id,
STAGE_POSITION_TOP_OR_LEFT,
callback,
isQuickSwitch,
@@ -319,14 +321,14 @@
// checks below aren't reliable since both of those views may be gone/transformed
val initSplitTaskId = getThisTaskCurrentlyInSplitSelection()
if (initSplitTaskId != INVALID_TASK_ID) {
- return if (initSplitTaskId == taskContainers[0].task.key.id) 1 else 0
+ return if (initSplitTaskId == leftTopTaskContainer.task.key.id) 1 else 0
}
}
// Check which of the two apps was selected
if (
- taskContainers[1].iconView.asView().containsPoint(lastTouchDownPosition) ||
- taskContainers[1].snapshotView.containsPoint(lastTouchDownPosition)
+ rightBottomTaskContainer.iconView.asView().containsPoint(lastTouchDownPosition) ||
+ rightBottomTaskContainer.snapshotView.containsPoint(lastTouchDownPosition)
) {
return 1
}
diff --git a/quickstep/src/com/android/quickstep/views/IconView.kt b/quickstep/src/com/android/quickstep/views/IconView.kt
index 2e6c4bf..6da52d6 100644
--- a/quickstep/src/com/android/quickstep/views/IconView.kt
+++ b/quickstep/src/com/android/quickstep/views/IconView.kt
@@ -45,11 +45,11 @@
private var msdlPlayerWrapper: MSDLPlayerWrapper? = null
constructor(context: Context) : super(context) {
- msdlPlayerWrapper = MSDLPlayerWrapper.INSTANCE.get(context)
+ setUpHaptics()
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
- msdlPlayerWrapper = MSDLPlayerWrapper.INSTANCE.get(context)
+ setUpHaptics()
}
constructor(
@@ -57,11 +57,15 @@
attrs: AttributeSet?,
defStyleAttr: Int,
) : super(context, attrs, defStyleAttr) {
- msdlPlayerWrapper = MSDLPlayerWrapper.INSTANCE.get(context)
+ setUpHaptics()
}
init {
multiValueAlpha.setUpdateVisibility(true)
+ }
+
+ private fun setUpHaptics() {
+ msdlPlayerWrapper = MSDLPlayerWrapper.INSTANCE.get(context)
// Haptics are handled by the MSDLPlayerWrapper
isHapticFeedbackEnabled = !Flags.msdlFeedback() || msdlPlayerWrapper == null
}
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 9be462c..c6bd677 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -36,7 +36,6 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.desktop.DesktopRecentsTransitionController;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.statehandlers.DepthController;
@@ -127,9 +126,11 @@
// If Launcher needs to return to split select state, do it now, after the icon has updated.
if (mContainer.hasPendingSplitSelectInfo()) {
PendingSplitSelectInfo recoveryData = mContainer.getPendingSplitSelectInfo();
- if (recoveryData.getStagedTaskId() == taskId) {
+ TaskContainer taskContainer;
+ if (recoveryData != null && recoveryData.getStagedTaskId() == taskId && (taskContainer =
+ mUtils.getTaskContainerById(taskId)) != null) {
initiateSplitSelect(
- getTaskViewByTaskId(recoveryData.getStagedTaskId()),
+ taskContainer,
recoveryData.getStagePosition(), recoveryData.getSource()
);
mContainer.finishSplitSelectRecovery();
@@ -240,10 +241,10 @@
}
@Override
- public void initiateSplitSelect(TaskView taskView,
+ public void initiateSplitSelect(TaskContainer taskContainer,
@SplitConfigurationOptions.StagePosition int stagePosition,
StatsLogManager.EventEnum splitEvent) {
- super.initiateSplitSelect(taskView, stagePosition, splitEvent);
+ super.initiateSplitSelect(taskContainer, stagePosition, splitEvent);
getStateManager().goToState(LauncherState.OVERVIEW_SPLIT_SELECT);
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index c6db576..29d2689 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -40,6 +40,7 @@
import static com.android.launcher3.Flags.enableGridOnlyOverview;
import static com.android.launcher3.Flags.enableLargeDesktopWindowingTile;
import static com.android.launcher3.Flags.enableRefactorTaskThumbnail;
+import static com.android.launcher3.Flags.enableSeparateExternalDisplayTasks;
import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR;
@@ -653,13 +654,13 @@
return;
}
- TaskView taskView = getTaskViewByTaskId(taskId);
- if (taskView == null) {
- Log.d(TAG, "onTaskRemoved: " + taskId + ", no associated TaskView");
+ TaskContainer taskContainer = mUtils.getTaskContainerById(taskId);
+ if (taskContainer == null) {
+ Log.d(TAG, "onTaskRemoved: " + taskId + ", no associated Task");
return;
}
Log.d(TAG, "onTaskRemoved: " + taskId);
- Task.TaskKey taskKey = taskView.getFirstTask().key;
+ Task.TaskKey taskKey = taskContainer.getTask().key;
UI_HELPER_EXECUTOR.execute(new CancellableTask<>(
() -> PackageManagerWrapper.getInstance()
.getActivityInfo(taskKey.getComponent(), taskKey.userId) == null,
@@ -848,7 +849,7 @@
private final RecentsViewModel mRecentsViewModel;
private final RecentsViewModelHelper mHelper;
- private final RecentsViewUtils mUtils = new RecentsViewUtils(this);
+ protected final RecentsViewUtils mUtils = new RecentsViewUtils(this);
private final Matrix mTmpMatrix = new Matrix();
@@ -909,6 +910,7 @@
if (DesktopModeStatus.enableMultipleDesktops(mContext)) {
mAddDesktopButton = (AddDesktopButton) LayoutInflater.from(context).inflate(
R.layout.overview_add_desktop_button, this, false);
+ mAddDesktopButton.setOnClickListener(this::createDesk);
}
mTaskViewPool = new ViewPool<>(context, this, R.layout.task, 20 /* max size */,
@@ -1117,7 +1119,7 @@
TaskView taskView = getTaskViewByTaskId(taskId);
if (taskView != null) {
for (TaskContainer container : taskView.getTaskContainers()) {
- if (container == null || taskId != container.getTask().key.id) {
+ if (taskId != container.getTask().key.id) {
continue;
}
container.getThumbnailViewDeprecated().setThumbnail(container.getTask(),
@@ -1132,9 +1134,10 @@
@Override
public void onTaskIconChanged(@NonNull String pkg, @NonNull UserHandle user) {
for (TaskView taskView : getTaskViews()) {
- Task task = taskView.getFirstTask();
- if (pkg.equals(task.key.getPackageName()) && task.key.userId == user.getIdentifier()) {
- task.icon = null;
+ Task firstTask = taskView.getFirstTask();
+ if (firstTask != null && pkg.equals(firstTask.key.getPackageName())
+ && firstTask.key.userId == user.getIdentifier()) {
+ firstTask.icon = null;
if (taskView.getTaskContainers().stream().anyMatch(
container -> container.getIconView().getDrawable() != null)) {
taskView.onTaskListVisibilityChanged(true /* visible */);
@@ -1829,7 +1832,7 @@
return;
}
- int runningTaskExpectedIndex = getRunningTaskExpectedIndex(runningTaskView);
+ int runningTaskExpectedIndex = mUtils.getRunningTaskExpectedIndex(runningTaskView);
if (mCurrentPage == runningTaskExpectedIndex) {
return;
}
@@ -1849,28 +1852,6 @@
updateTaskSize();
}
- private int getRunningTaskExpectedIndex(TaskView runningTaskView) {
- int firstTaskViewIndex = indexOfChild(getFirstTaskView());
- if (mContainer.getDeviceProfile().isTablet) {
- if (runningTaskView instanceof DesktopTaskView) {
- return firstTaskViewIndex; // Desktop running task is always in front.
- } else if (enableLargeDesktopWindowingTile()) {
- // Other running task is behind desktop tasks.
- return getDesktopTaskViewCount() + firstTaskViewIndex;
- } else {
- return firstTaskViewIndex;
- }
- } else {
- int currentIndex = indexOfChild(runningTaskView);
- if (currentIndex != -1) {
- return currentIndex; // Keep the position if running task already in layout.
- } else {
- // New running task are added to the front to begin with.
- return firstTaskViewIndex;
- }
- }
- }
-
@Override
protected void onScrollerAnimationAborted() {
ActiveGestureProtoLogProxy.logOnScrollerAnimationAborted();
@@ -1958,6 +1939,9 @@
if (enableLargeDesktopWindowingTile()) {
taskGroups = mUtils.sortDesktopTasksToFront(taskGroups);
}
+ if (enableSeparateExternalDisplayTasks()) {
+ taskGroups = mUtils.sortExternalDisplayTasksToFront(taskGroups);
+ }
if (mAddDesktopButton != null) {
// Add `mAddDesktopButton` as the first child.
@@ -2141,6 +2125,11 @@
return mUtils.getDesktopTaskViewCount();
}
+ /** Counts {@link TaskView}s that are not {@link DesktopTaskView} instances. */
+ public int getNonDesktopTaskViewCount() {
+ return mUtils.getNonDesktopTaskViewCount();
+ }
+
/**
* Returns the number of tasks in the top row of the overview grid.
*/
@@ -3037,7 +3026,7 @@
if (mAddDesktopButton != null && wasEmpty) {
addView(mAddDesktopButton);
}
- addView(taskView, getRunningTaskExpectedIndex(taskView));
+ addView(taskView, mUtils.getRunningTaskExpectedIndex(taskView));
runningTaskViewId = taskView.getTaskViewId();
if (wasEmpty) {
addView(mClearAllButton);
@@ -3471,7 +3460,7 @@
// `mAddDesktopButton`, shift `mAddDesktopButton` to accommodate.
translationX += largeTaskWidthAndSpacing;
}
- mAddDesktopButton.setTranslationX(translationX);
+ mAddDesktopButton.setGridTranslationX(translationX);
}
final TaskView runningTask = getRunningTaskView();
@@ -4100,6 +4089,7 @@
} else {
removeTaskInternal(dismissedTaskView);
}
+ // TODO(b/391918297): Logging when the TaskView does not have tasks as well.
mContainer.getStatsLogManager().logger()
.withItemInfo(dismissedTaskView.getFirstItemInfo())
.log(LAUNCHER_TASK_DISMISS_SWIPE_UP);
@@ -4609,6 +4599,12 @@
}
}
+ private void createDesk(View view) {
+ SystemUiProxy.INSTANCE
+ .get(getContext())
+ .createDesktop(mContainer.getDisplay().getDisplayId());
+ }
+
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (isHandlingTouch() || event.getAction() != KeyEvent.ACTION_DOWN) {
@@ -4977,8 +4973,8 @@
} else if (child instanceof ClearAllButton) {
getPagedOrientationHandler().getPrimaryViewTranslate().set(child,
totalTranslationX);
- } else {
- // TODO(b/389209581): Handle the page offsets update of the 'mAddDesktopButton'.
+ } else if (child instanceof AddDesktopButton addDesktopButton) {
+ addDesktopButton.setOffsetTranslationX(totalTranslationX);
}
if (mEnableDrawingLiveTile && i == getRunningTaskIndex()) {
runActionOnRemoteHandles(
@@ -5181,18 +5177,20 @@
* Primarily used by overview actions to initiate split from focused task, logs the source
* of split invocation as such.
*/
- public void initiateSplitSelect(TaskView taskView) {
+ public void initiateSplitSelect(TaskContainer taskContainer) {
int defaultSplitPosition = getPagedOrientationHandler()
.getDefaultSplitPosition(mContainer.getDeviceProfile());
- initiateSplitSelect(taskView, defaultSplitPosition, LAUNCHER_OVERVIEW_ACTIONS_SPLIT);
+ initiateSplitSelect(taskContainer, defaultSplitPosition, LAUNCHER_OVERVIEW_ACTIONS_SPLIT);
}
/** TODO(b/266477929): Consolidate this call w/ the one below */
- public void initiateSplitSelect(TaskView taskView, @StagePosition int stagePosition,
+ public void initiateSplitSelect(TaskContainer taskContainer,
+ @StagePosition int stagePosition,
StatsLogManager.EventEnum splitEvent) {
+ TaskView taskView = taskContainer.getTaskView();
mSplitHiddenTaskView = taskView;
mSplitSelectStateController.setInitialTaskSelect(null /*intent*/, stagePosition,
- taskView.getFirstItemInfo(), splitEvent, taskView.getFirstTask().key.id);
+ taskContainer.getItemInfo(), splitEvent, taskContainer.getTask().key.id);
mSplitSelectStateController.setAnimateCurrentTaskDismissal(
true /*animateCurrentTaskDismissal*/);
mSplitHiddenTaskViewIndex = indexOfChild(taskView);
@@ -5283,15 +5281,16 @@
boolean isInitiatingTaskViewSplitPair =
mSplitSelectStateController.isDismissingFromSplitPair();
if (isInitiatingSplitFromTaskView && isInitiatingTaskViewSplitPair
- && mSplitHiddenTaskView instanceof GroupedTaskView) {
+ && mSplitHiddenTaskView instanceof GroupedTaskView groupedTaskView) {
// Splitting from Overview for split pair task
createInitialSplitSelectAnimation(builder);
// Animate pair thumbnail into full thumbnail
- boolean primaryTaskSelected = mSplitHiddenTaskView.getTaskIds()[0]
+ boolean primaryTaskSelected = groupedTaskView.getLeftTopTaskContainer().getTask().key.id
== mSplitSelectStateController.getInitialTaskId();
- TaskContainer taskContainer = mSplitHiddenTaskView
- .getTaskContainers().get(primaryTaskSelected ? 1 : 0);
+ TaskContainer taskContainer =
+ primaryTaskSelected ? groupedTaskView.getRightBottomTaskContainer()
+ : groupedTaskView.getLeftTopTaskContainer();
mSplitSelectStateController.getSplitAnimationController()
.addInitialSplitFromPair(taskContainer, builder,
mContainer.getDeviceProfile(),
@@ -5719,7 +5718,7 @@
updateGridProperties();
updateScrollSynchronously();
- int targetSysUiFlags = taskView.getTaskContainers().getFirst().getSysUiStatusNavFlags();
+ int targetSysUiFlags = taskView.getSysUiStatusNavFlags();
final boolean[] passedOverviewThreshold = new boolean[]{false};
AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(taskView);
anim.play(new AnimatedFloat(v -> {
@@ -5777,8 +5776,12 @@
} else {
taskView.launchWithoutAnimation(this::onTaskLaunchAnimationEnd);
}
- mContainer.getStatsLogManager().logger().withItemInfo(taskView.getFirstItemInfo())
- .log(LAUNCHER_TASK_LAUNCH_SWIPE_DOWN);
+ // TODO(b/391918297): Logging when there is no associated task.
+ ItemInfo firstItemInfo = taskView.getFirstItemInfo();
+ if (firstItemInfo != null) {
+ mContainer.getStatsLogManager().logger().withItemInfo(firstItemInfo)
+ .log(LAUNCHER_TASK_LAUNCH_SWIPE_DOWN);
+ }
} else {
onTaskLaunchAnimationEnd(false);
}
@@ -6161,7 +6164,7 @@
if (addDesktopButtonIndex != -1 && addDesktopButtonIndex < outPageScrolls.length) {
outPageScrolls[addDesktopButtonIndex] =
newPageScrolls[addDesktopButtonIndex] + Math.round(
- mAddDesktopButton.getTranslationX());
+ mAddDesktopButton.getGridTranslationX());
}
int lastTaskScroll = getLastTaskScroll(clearAllScroll, clearAllWidth);
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
index dcb954a..94e8c03 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
@@ -20,8 +20,10 @@
import android.view.View
import androidx.core.view.children
import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
+import com.android.launcher3.Flags.enableSeparateExternalDisplayTasks
import com.android.launcher3.util.IntArray
import com.android.quickstep.util.GroupTask
+import com.android.quickstep.util.isExternalDisplay
import com.android.quickstep.views.RecentsView.RUNNING_TASK_ATTACH_ALPHA
import com.android.systemui.shared.recents.model.ThumbnailData
import java.util.function.BiConsumer
@@ -52,6 +54,12 @@
return otherTasks + desktopTasks
}
+ fun sortExternalDisplayTasksToFront(tasks: List<GroupTask>): List<GroupTask> {
+ val (externalDisplayTasks, otherTasks) =
+ tasks.partition { it.tasks.firstOrNull().isExternalDisplay }
+ return otherTasks + externalDisplayTasks
+ }
+
class TaskViewsIterable(val recentsView: RecentsView<*, *>) : Iterable<TaskView> {
/** Iterates TaskViews when its index inside the RecentsView is needed. */
fun forEachWithIndexInParent(consumer: BiConsumer<Int, TaskView>) {
@@ -67,6 +75,9 @@
/** Counts [TaskView]s that are [DesktopTaskView] instances. */
fun getDesktopTaskViewCount(): Int = taskViews.count { it is DesktopTaskView }
+ /** Counts [TaskView]s that are not [DesktopTaskView] instances. */
+ fun getNonDesktopTaskViewCount(): Int = taskViews.count { it !is DesktopTaskView }
+
/** Returns a list of all large TaskView Ids from [TaskView]s */
fun getLargeTaskViewIds(): List<Int> = taskViews.filter { it.isLargeTile }.map { it.taskViewId }
@@ -96,9 +107,47 @@
fun getExpectedCurrentTask(runningTaskView: TaskView?, focusedTaskView: TaskView?): TaskView? =
runningTaskView
?: focusedTaskView
- ?: taskViews.firstOrNull { it !is DesktopTaskView }
+ ?: taskViews.firstOrNull {
+ it !is DesktopTaskView &&
+ !(enableSeparateExternalDisplayTasks() && it.isExternalDisplay)
+ }
?: taskViews.lastOrNull()
+ private fun getDeviceProfile() = (recentsView.mContainer as RecentsViewContainer).deviceProfile
+
+ fun getRunningTaskExpectedIndex(runningTaskView: TaskView): Int {
+ val firstTaskViewIndex = recentsView.indexOfChild(getFirstTaskView())
+ return if (getDeviceProfile().isTablet) {
+ var index = firstTaskViewIndex
+ if (enableLargeDesktopWindowingTile() && runningTaskView !is DesktopTaskView) {
+ // For fullsreen tasks, skip over Desktop tasks in its section
+ index +=
+ if (enableSeparateExternalDisplayTasks()) {
+ if (runningTaskView.isExternalDisplay) {
+ taskViews.count { it is DesktopTaskView && it.isExternalDisplay }
+ } else {
+ taskViews.count { it is DesktopTaskView && !it.isExternalDisplay }
+ }
+ } else {
+ getDesktopTaskViewCount()
+ }
+ }
+ if (enableSeparateExternalDisplayTasks() && !runningTaskView.isExternalDisplay) {
+ // For main display section, skip over external display tasks
+ index += taskViews.count { it.isExternalDisplay }
+ }
+ index
+ } else {
+ val currentIndex: Int = recentsView.indexOfChild(runningTaskView)
+ return if (currentIndex != -1) {
+ currentIndex // Keep the position if running task already in layout.
+ } else {
+ // New running task are added to the front to begin with.
+ firstTaskViewIndex
+ }
+ }
+ }
+
/** Returns the first TaskView if it exists, or null otherwise. */
fun getFirstTaskView(): TaskView? = taskViews.firstOrNull()
@@ -175,6 +224,9 @@
/** Returns true if there are at least one TaskView has been added to the RecentsView. */
fun hasTaskViews() = taskViews.any()
+ fun getTaskContainerById(taskId: Int) =
+ taskViews.firstNotNullOfOrNull { it.getTaskContainerById(taskId) }
+
private fun getRowRect(firstView: View?, lastView: View?, outRowRect: Rect) {
outRowRect.setEmpty()
firstView?.let {
@@ -204,7 +256,7 @@
outTopRowRect: Rect,
outBottomRowRect: Rect,
) {
- if (!(recentsView.mContainer as RecentsViewContainer).deviceProfile.isTablet) {
+ if (!getDeviceProfile().isTablet) {
getRowRect(getFirstTaskView(), getLastTaskView(), outTaskViewRowRect)
return
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
index 7ac0946..a9e84ef 100644
--- a/quickstep/src/com/android/quickstep/views/TaskContainer.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
@@ -28,6 +28,8 @@
import com.android.quickstep.recents.di.get
import com.android.quickstep.recents.di.getScope
import com.android.quickstep.recents.di.inject
+import com.android.quickstep.recents.ui.mapper.TaskUiStateMapper
+import com.android.quickstep.recents.ui.viewmodel.TaskData
import com.android.quickstep.recents.viewmodel.TaskContainerViewModel
import com.android.quickstep.task.thumbnail.TaskThumbnailView
import com.android.quickstep.task.viewmodel.TaskContainerData
@@ -61,11 +63,7 @@
// TODO(b/335649589): Ideally create and obtain this from DI.
private val taskContainerViewModel: TaskContainerViewModel by lazy {
- TaskContainerViewModel(
- sysUiStatusNavFlagsUseCase = RecentsDependencies.get(),
- getThumbnailUseCase = RecentsDependencies.get(),
- splashAlphaUseCase = RecentsDependencies.get(),
- )
+ TaskContainerViewModel(splashAlphaUseCase = RecentsDependencies.get())
}
init {
@@ -84,13 +82,9 @@
}
}
- val splitAnimationThumbnail: Bitmap?
- get() =
- if (enableRefactorTaskThumbnail()) {
- taskContainerViewModel.getThumbnail(task.key.id)
- } else {
- thumbnailViewDeprecated.thumbnail
- }
+ var splitAnimationThumbnail: Bitmap? = null
+ get() = if (enableRefactorTaskThumbnail()) field else thumbnailViewDeprecated.thumbnail
+ private set
val thumbnailView: TaskThumbnailView
get() {
@@ -110,12 +104,6 @@
taskContainerViewModel.shouldShowThumbnailSplash(task.key.id)
else thumbnailViewDeprecated.shouldShowSplashView()
- val sysUiStatusNavFlags: Int
- get() =
- if (enableRefactorTaskThumbnail())
- taskContainerViewModel.getSysUiStatusNavFlags(task.key.id)
- else thumbnailViewDeprecated.sysUiStatusNavFlags
-
/** Builds proto for logging */
val itemInfo: TaskViewItemInfo
get() = TaskViewItemInfo(this)
@@ -146,7 +134,7 @@
thumbnailView.destroyScopes()
}
- fun bindThumbnailView() {
+ private fun bindThumbnailView() {
taskThumbnailViewModel.bind(task.key.id)
}
@@ -163,4 +151,18 @@
digitalWellBeingToast?.let { addAccessibleChildToList(it, outChildren) }
overlay.addChildForAccessibility(outChildren)
}
+
+ fun setState(state: TaskData?, liveTile: Boolean, hasHeader: Boolean) {
+ thumbnailView.setState(TaskUiStateMapper.toTaskThumbnailUiState(state, liveTile, hasHeader))
+ splitAnimationThumbnail =
+ if (state is TaskData.Data) state.thumbnailData?.thumbnail else null
+ }
+
+ fun updateTintAmount(tintAmount: Float) {
+ thumbnailView.updateTintAmount(tintAmount)
+ }
+
+ fun updateMenuOpenProgress(progress: Float) {
+ thumbnailView.updateMenuOpenProgress(progress)
+ }
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index 63bc509..0b3eb75 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -368,8 +368,7 @@
mRevealAnimator.addUpdateListener(animation -> {
float animatedFraction = animation.getAnimatedFraction();
float openProgress = closing ? (1 - animatedFraction) : animatedFraction;
- mTaskContainer.getTaskContainerData()
- .getTaskMenuOpenProgress().setValue(openProgress);
+ mTaskContainer.updateMenuOpenProgress(openProgress);
});
} else {
openCloseAnimatorBuilder.with(ObjectAnimator.ofFloat(
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index e7a395f..ce0efb6 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -33,11 +33,9 @@
import android.view.Display
import android.view.MotionEvent
import android.view.View
-import android.view.View.OnClickListener
import android.view.ViewGroup
import android.view.ViewStub
import android.view.accessibility.AccessibilityNodeInfo
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
import android.widget.FrameLayout
import android.widget.Toast
import androidx.annotation.IntDef
@@ -50,6 +48,7 @@
import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
import com.android.launcher3.Flags.enableOverviewIconMenu
import com.android.launcher3.Flags.enableRefactorTaskThumbnail
+import com.android.launcher3.Flags.enableSeparateExternalDisplayTasks
import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.anim.AnimatedFloat
@@ -63,13 +62,12 @@
import com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE
import com.android.launcher3.util.MultiValueAlpha
import com.android.launcher3.util.RunnableList
-import com.android.launcher3.util.SplitConfigurationOptions
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
-import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption
import com.android.launcher3.util.SplitConfigurationOptions.StagePosition
import com.android.launcher3.util.TraceHelper
import com.android.launcher3.util.TransformingTouchDelegate
import com.android.launcher3.util.ViewPool
+import com.android.launcher3.util.coroutines.DispatcherProvider
import com.android.launcher3.util.rects.set
import com.android.quickstep.FullscreenDrawParams
import com.android.quickstep.RecentsModel
@@ -77,6 +75,11 @@
import com.android.quickstep.TaskOverlayFactory
import com.android.quickstep.TaskViewUtils
import com.android.quickstep.orientation.RecentsPagedOrientationHandler
+import com.android.quickstep.recents.di.RecentsDependencies
+import com.android.quickstep.recents.di.get
+import com.android.quickstep.recents.di.inject
+import com.android.quickstep.recents.ui.viewmodel.TaskTileUiState
+import com.android.quickstep.recents.ui.viewmodel.TaskViewModel
import com.android.quickstep.util.ActiveGestureErrorDetector
import com.android.quickstep.util.ActiveGestureLog
import com.android.quickstep.util.BorderAnimator
@@ -84,10 +87,20 @@
import com.android.quickstep.util.RecentsOrientedState
import com.android.quickstep.util.TaskCornerRadius
import com.android.quickstep.util.TaskRemovedDuringLaunchListener
+import com.android.quickstep.util.displayId
+import com.android.quickstep.util.isExternalDisplay
import com.android.quickstep.views.RecentsView.UNBOUND_TASK_VIEW_ID
import com.android.systemui.shared.recents.model.Task
import com.android.systemui.shared.recents.model.ThumbnailData
import com.android.systemui.shared.system.ActivityManagerWrapper
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
/** A task in the Recents view. */
open class TaskView
@@ -128,10 +141,17 @@
val isRunningTask: Boolean
get() = this === recentsView?.runningTaskView
+ val displayId: Int
+ get() = taskContainers.firstOrNull()?.task.displayId
+
+ val isExternalDisplay: Boolean
+ get() = displayId.isExternalDisplay
+
val isLargeTile: Boolean
get() =
this == recentsView?.focusedTaskView ||
- (enableLargeDesktopWindowingTile() && type == TaskViewType.DESKTOP)
+ (enableLargeDesktopWindowingTile() && type == TaskViewType.DESKTOP) ||
+ (enableSeparateExternalDisplayTasks() && isExternalDisplay)
val recentsView: RecentsView<*, *>?
get() = parent as? RecentsView<*, *>
@@ -139,14 +159,15 @@
val pagedOrientationHandler: RecentsPagedOrientationHandler
get() = orientedState.orientationHandler
- @get:Deprecated("Use [taskContainers] instead.")
- val firstTask: Task
- /** Returns the first task bound to this TaskView. */
- get() = taskContainers[0].task
+ val firstTaskContainer: TaskContainer?
+ get() = taskContainers.firstOrNull()
- @get:Deprecated("Use [taskContainers] instead.")
- val firstItemInfo: ItemInfo
- get() = taskContainers[0].itemInfo
+ val firstTask: Task?
+ /** Returns the first task bound to this TaskView. */
+ get() = firstTaskContainer?.task
+
+ val firstItemInfo: ItemInfo?
+ get() = firstTaskContainer?.itemInfo
protected val container: RecentsViewContainer =
RecentsViewContainer.containerFromContext(context)
@@ -231,6 +252,11 @@
var taskViewId = UNBOUND_TASK_VIEW_ID
var isEndQuickSwitchCuj = false
+ var sysUiStatusNavFlags: Int = 0
+ get() =
+ if (enableRefactorTaskThumbnail()) field
+ else taskContainers.first().thumbnailViewDeprecated.sysUiStatusNavFlags
+ private set
// Various animation progress variables.
// progress: 0 = show icon and no insets; 1 = don't show icon and show full insets.
@@ -448,6 +474,11 @@
private val settledProgressDismiss =
settledProgressPropertyFactory.get(SETTLED_PROGRESS_INDEX_DISMISS)
+ private var viewModel: TaskViewModel? = null
+ private val dispatcherProvider: DispatcherProvider by RecentsDependencies.inject()
+ private val coroutineScope by lazy { CoroutineScope(SupervisorJob() + dispatcherProvider.main) }
+ private val coroutineJobs = mutableListOf<Job>()
+
/**
* Returns an animator of [settledProgressDismiss] that transition in with a built-in
* interpolator.
@@ -601,6 +632,8 @@
override fun onRecycle() {
resetPersistentViewTransforms()
+
+ viewModel = null
attachAlpha = 1f
splitAlpha = 1f
// Clear any references to the thumbnail (it will be re-read either from the cache or the
@@ -635,13 +668,6 @@
val shouldPopulateAccessibilityMenu =
modalness == 0f && recentsView?.isSplitSelectionActive == false
if (shouldPopulateAccessibilityMenu) {
- addAction(
- AccessibilityAction(
- R.id.action_close,
- context.getText(R.string.accessibility_close),
- )
- )
-
taskContainers.forEach {
TraceHelper.allowIpcs("TV.a11yInfo") {
TaskOverlayFactory.getEnabledShortcuts(this@TaskView, it).forEach { shortcut
@@ -672,11 +698,6 @@
override fun performAccessibilityAction(action: Int, arguments: Bundle?): Boolean {
// TODO(b/343708271): Add support for multiple tasks per action.
- if (action == R.id.action_close) {
- recentsView?.dismissTask(this, true /*animateTaskView*/, true /*removeTask*/)
- return true
- }
-
taskContainers.forEach {
if (it.digitalWellBeingToast?.handleAccessibilityAction(action) == true) {
return true
@@ -715,6 +736,52 @@
?.inflate()
}
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ if (enableRefactorTaskThumbnail()) {
+ // The TaskView lifecycle is starts the ViewModel during onBind, and cleans it in
+ // onRecycle. So it should be initialized at this point. TaskView Lifecycle:
+ // `bind` -> `onBind` -> onAttachedToWindow() -> onDetachFromWindow -> onRecycle
+ coroutineJobs +=
+ viewModel!!.tintAmount.onEach(::updateTintAmount).launchIn(coroutineScope)
+
+ coroutineJobs +=
+ coroutineScope.launch { viewModel!!.state.collectLatest(::updateTaskViewState) }
+ }
+ }
+
+ private fun updateTintAmount(amount: Float) {
+ taskContainers.forEach { it.updateTintAmount(amount) }
+ }
+
+ private fun updateTaskViewState(state: TaskTileUiState) {
+ sysUiStatusNavFlags = state.sysUiStatusNavFlags
+
+ // Updating containers
+ val mapOfTasks = state.tasks.associateBy { it.taskId }
+ taskContainers.forEach { container ->
+ container.setState(
+ state = mapOfTasks[container.task.key.id],
+ liveTile = state.isLiveTile,
+ hasHeader = type == TaskViewType.DESKTOP,
+ )
+ }
+ }
+
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ if (enableRefactorTaskThumbnail()) {
+ // The jobs are being cancelled in the background thread. So we make a copy of the list
+ // to prevent cleaning a new job that might be added to this list during onAttach
+ // or another moment in the lifecycle.
+ val coroutineJobsToCancel = coroutineJobs.toList()
+ coroutineJobs.clear()
+ coroutineScope.launch(dispatcherProvider.background) {
+ coroutineJobsToCancel.forEach { it.cancel("TaskView detaching from window") }
+ }
+ }
+ }
+
/** Updates this task view to the given {@param task}. */
open fun bind(
task: Task,
@@ -738,6 +805,18 @@
}
open fun onBind(orientedState: RecentsOrientedState) {
+ if (enableRefactorTaskThumbnail()) {
+ viewModel =
+ TaskViewModel(
+ taskViewType = type,
+ recentsViewData = RecentsDependencies.get(),
+ getTaskUseCase = RecentsDependencies.get(),
+ getSysUiStatusNavFlagsUseCase = RecentsDependencies.get(),
+ dispatcherProvider = RecentsDependencies.get(),
+ )
+ .apply { bind(*taskIds) }
+ }
+
taskContainers.forEach {
it.bind()
if (enableRefactorTaskThumbnail()) {
@@ -848,7 +927,7 @@
protected open fun updateThumbnailSize() {
// TODO(b/271468547), we should default to setting translations only on the snapshot instead
// of a hybrid of both margins and translations
- taskContainers[0].snapshotView.updateLayoutParams<LayoutParams> {
+ firstTaskContainer?.snapshotView?.updateLayoutParams<LayoutParams> {
topMargin = container.deviceProfile.overviewTaskThumbnailTopMarginPx
}
taskContainers.forEach { it.digitalWellBeingToast?.setupLayout() }
@@ -1012,10 +1091,13 @@
}
}
Log.d("b/310064698", "${taskIds.contentToString()} - onClick - callbackList: $callbackList")
- container.statsLogManager
- .logger()
- .withItemInfo(firstItemInfo)
- .log(LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP)
+ // TODO(b/391918297): Logging when there is no associated task.
+ firstItemInfo?.let {
+ container.statsLogManager
+ .logger()
+ .withItemInfo(it)
+ .log(LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP)
+ }
}
/** Launch of the current task (both live and inactive tasks) with an animation. */
@@ -1118,6 +1200,7 @@
* @return CompletionStage to indicate the animation completion or null if the launch failed.
*/
open fun launchAsStaticTile(): RunnableList? {
+ val firstTaskContainer = firstTaskContainer ?: return null
TestLogging.recordEvent(
TestProtocol.SEQUENCE_MAIN,
"startActivityFromRecentsAsync",
@@ -1125,11 +1208,11 @@
)
val opts =
container.getActivityLaunchOptions(this, null).apply {
- options.launchDisplayId = display?.displayId ?: Display.DEFAULT_DISPLAY
+ options.launchDisplayId = displayId
}
if (
ActivityManagerWrapper.getInstance()
- .startActivityFromRecents(taskContainers[0].task.key, opts.options)
+ .startActivityFromRecents(firstTaskContainer.task.key, opts.options)
) {
Log.d(
TAG,
@@ -1168,18 +1251,18 @@
isQuickSwitch: Boolean = false,
callback: (launched: Boolean) -> Unit,
) {
+ val firstTaskContainer = firstTaskContainer ?: return
TestLogging.recordEvent(
TestProtocol.SEQUENCE_MAIN,
"startActivityFromRecentsAsync",
taskIds.contentToString(),
)
- val firstContainer = taskContainers[0]
val failureListener = TaskRemovedDuringLaunchListener(context.applicationContext)
if (isQuickSwitch) {
// We only listen for failures to launch in quickswitch because the during this
// gesture launcher is in the background state, vs other launches which are in
// the actual overview state
- failureListener.register(container, firstContainer.task.key.id) {
+ failureListener.register(container, firstTaskContainer.task.key.id) {
notifyTaskLaunchFailed("launchWithoutAnimation")
recentsView?.let {
// Disable animations for now, as it is an edge case and the app usually
@@ -1211,12 +1294,12 @@
if (isQuickSwitch) {
setFreezeRecentTasksReordering()
}
- disableStartingWindow = firstContainer.shouldShowSplashView
+ disableStartingWindow = firstTaskContainer.shouldShowSplashView
}
Executors.UI_HELPER_EXECUTOR.execute {
if (
!ActivityManagerWrapper.getInstance()
- .startActivityFromRecents(firstContainer.task.key, opts)
+ .startActivityFromRecents(firstTaskContainer.task.key, opts)
) {
// If the call to start activity failed, then post the result immediately,
// otherwise, wait for the animation start callback from the activity options
@@ -1243,14 +1326,6 @@
Toast.makeText(context, R.string.activity_not_available, Toast.LENGTH_SHORT).show()
}
- fun initiateSplitSelect(splitPositionOption: SplitPositionOption) {
- recentsView?.initiateSplitSelect(
- this,
- splitPositionOption.stagePosition,
- SplitConfigurationOptions.getLogEventForPosition(splitPositionOption.stagePosition),
- )
- }
-
/**
* Returns `true` if user is already in split select mode and this tap was to choose the second
* app. `false` otherwise
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/FakeTaskThumbnailViewModel.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/FakeTaskThumbnailViewModel.kt
index 47d2bfc..3e0c186 100644
--- a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/FakeTaskThumbnailViewModel.kt
+++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/FakeTaskThumbnailViewModel.kt
@@ -17,14 +17,11 @@
package com.android.quickstep.task.thumbnail
import android.graphics.Matrix
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
import kotlinx.coroutines.flow.MutableStateFlow
class FakeTaskThumbnailViewModel : TaskThumbnailViewModel {
- override val dimProgress = MutableStateFlow(0f)
override val splashAlpha = MutableStateFlow(0f)
- override val uiState = MutableStateFlow<TaskThumbnailUiState>(Uninitialized)
override fun bind(taskId: Int) {
// no-op
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
index a76f83c..356080a 100644
--- a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
+++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
@@ -60,36 +60,61 @@
screenshotRule.screenshotTest("taskThumbnailView_uninitialized") { activity ->
activity.actionBar?.hide()
val taskThumbnailView = createTaskThumbnailView(activity)
- taskThumbnailViewModel.uiState.value = BackgroundOnly(Color.YELLOW)
- taskThumbnailViewModel.uiState.value = Uninitialized
+ taskThumbnailView.setState(Uninitialized)
taskThumbnailView
}
}
@Test
fun taskThumbnailView_recyclesToUninitialized() {
- screenshotRule.screenshotTest(
- "taskThumbnailView_uninitialized",
- viewProvider = { activity ->
- activity.actionBar?.hide()
- val taskThumbnailView = createTaskThumbnailView(activity)
- taskThumbnailViewModel.uiState.value = BackgroundOnly(Color.YELLOW)
- taskThumbnailView
- },
- checkView = { _, taskThumbnailView ->
- // Call onRecycle() after View is attached (end of block above)
- (taskThumbnailView as TaskThumbnailView).onRecycle()
- false
- },
- )
+ screenshotRule.screenshotTest("taskThumbnailView_uninitialized") { activity ->
+ activity.actionBar?.hide()
+ val taskThumbnailView = createTaskThumbnailView(activity)
+ taskThumbnailView.setState(BackgroundOnly(Color.YELLOW))
+ taskThumbnailView.onRecycle()
+ taskThumbnailView
+ }
}
@Test
fun taskThumbnailView_backgroundOnly() {
screenshotRule.screenshotTest("taskThumbnailView_backgroundOnly") { activity ->
activity.actionBar?.hide()
- taskThumbnailViewModel.uiState.value = BackgroundOnly(Color.YELLOW)
- createTaskThumbnailView(activity)
+ createTaskThumbnailView(activity).apply { setState(BackgroundOnly(Color.YELLOW)) }
+ }
+ }
+
+ @Test
+ fun taskThumbnailView_dimmed_tintAmount() {
+ screenshotRule.screenshotTest("taskThumbnailView_dimmed_40") { activity ->
+ activity.actionBar?.hide()
+ createTaskThumbnailView(activity).apply {
+ setState(BackgroundOnly(Color.YELLOW))
+ updateTintAmount(.4f)
+ }
+ }
+ }
+
+ @Test
+ fun taskThumbnailView_dimmed_menuOpen() {
+ screenshotRule.screenshotTest("taskThumbnailView_dimmed_40") { activity ->
+ activity.actionBar?.hide()
+ createTaskThumbnailView(activity).apply {
+ setState(BackgroundOnly(Color.YELLOW))
+ updateMenuOpenProgress(1f)
+ }
+ }
+ }
+
+ @Test
+ fun taskThumbnailView_dimmed_tintAmountAndMenuOpen() {
+ screenshotRule.screenshotTest("taskThumbnailView_dimmed_80") { activity ->
+ activity.actionBar?.hide()
+ createTaskThumbnailView(activity).apply {
+ setState(BackgroundOnly(Color.YELLOW))
+ updateTintAmount(.8f)
+ updateMenuOpenProgress(1f)
+ }
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/AppEventProducerTest.java b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/AppEventProducerTest.java
index d4dd580..06a939a 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/AppEventProducerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/AppEventProducerTest.java
@@ -35,9 +35,12 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherAppSingleton;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.util.AllModulesForTest;
import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
import com.android.launcher3.util.UserIconInfo;
import com.android.systemui.shared.system.SysUiStatsLog;
@@ -51,6 +54,9 @@
import java.util.Arrays;
+import dagger.BindsInstance;
+import dagger.Component;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
public class AppEventProducerTest {
@@ -72,7 +78,9 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = new SandboxContext(getApplicationContext());
- mContext.putObject(UserCache.INSTANCE, mUserCache);
+ mContext.initDaggerComponent(
+ DaggerAppEventProducerTest_TestComponent.builder().bindUserCache(mUserCache)
+ );
mAppEventProducer = new AppEventProducer(mContext, null);
}
@@ -129,4 +137,15 @@
.build());
return itemBuilder.build();
}
+
+ @LauncherAppSingleton
+ @Component(modules = { AllModulesForTest.class })
+ interface TestComponent extends LauncherAppComponent {
+ @Component.Builder
+ interface Builder extends LauncherAppComponent.Builder {
+ @BindsInstance
+ AppEventProducerTest.TestComponent.Builder bindUserCache(UserCache userCache);
+ @Override LauncherAppComponent build();
+ }
+ }
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
index 5cee434..be71640 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
@@ -21,15 +21,19 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.Flags.enableRefactorTaskThumbnail
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
import com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_NOT_PINNABLE
import com.android.launcher3.model.data.TaskViewItemInfo.Companion.createTaskViewAtom
import com.android.launcher3.pm.UserCache
+import com.android.launcher3.util.AllModulesForTest
import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.launcher3.util.TransformingTouchDelegate
import com.android.launcher3.util.UserIconInfo
import com.android.quickstep.TaskOverlayFactory
import com.android.quickstep.TaskOverlayFactory.TaskOverlay
+import com.android.quickstep.TestComponent
import com.android.quickstep.recents.di.RecentsDependencies
import com.android.quickstep.task.thumbnail.TaskThumbnailView
import com.android.quickstep.views.RecentsView
@@ -41,6 +45,8 @@
import com.android.systemui.shared.recents.model.Task
import com.android.systemui.shared.recents.model.Task.TaskKey
import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -66,7 +72,9 @@
whenever(recentsView.indexOfChild(taskView)).thenReturn(TASK_VIEW_INDEX)
whenever(userInfo.isPrivate).thenReturn(false)
whenever(userCache.getUserInfo(any())).thenReturn(userInfo)
- context.putObject(UserCache.INSTANCE, userCache)
+ context.initDaggerComponent(
+ DaggerTaskViewItemInfoTest_TestComponent.builder().bindUserCache(userCache)
+ )
RecentsDependencies.initialize(context)
}
@@ -176,6 +184,17 @@
)
}
+ @LauncherAppSingleton
+ @Component(modules = [AllModulesForTest::class])
+ interface TestComponent : LauncherAppComponent {
+ @Component.Builder
+ interface Builder : LauncherAppComponent.Builder {
+ @BindsInstance fun bindUserCache(userCache: UserCache): Builder
+
+ override fun build(): TestComponent
+ }
+ }
+
companion object {
const val PACKAGE = "package"
const val CLASS = "class"
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt
index 588c22c..021e1e4 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt
@@ -53,7 +53,7 @@
import com.android.launcher3.util.LauncherMultivalentJUnit
import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED
-import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_VISIBLE
import com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_BAR
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.TruthJUnit.assume
@@ -542,7 +542,7 @@
assume().that(activityContext.isHardwareKeyboard).isFalse()
getInstrumentation().runOnMainSync {
- stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, false)
+ stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_VISIBLE, false)
animatorTestRule.advanceTimeBy(TASKBAR_STASH_DURATION_FOR_IME)
}
assertThat(viewController.areIconsVisible()).isFalse()
@@ -555,7 +555,7 @@
assume().that(activityContext.isHardwareKeyboard).isFalse()
getInstrumentation().runOnMainSync {
- stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, true)
+ stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_VISIBLE, true)
animatorTestRule.advanceTimeBy(0)
}
@@ -574,7 +574,7 @@
// Start with IME shown.
getInstrumentation().runOnMainSync {
- stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, true)
+ stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_VISIBLE, true)
animatorTestRule.advanceTimeBy(0)
}
@@ -600,7 +600,7 @@
assume().that(activityContext.isHardwareKeyboard).isFalse()
getInstrumentation().runOnMainSync {
- stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, false)
+ stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_VISIBLE, false)
animatorTestRule.advanceTimeBy(TASKBAR_STASH_DURATION_FOR_IME)
}
assertThat(viewController.areIconsVisible()).isFalse()
@@ -613,7 +613,7 @@
assume().that(activityContext.isHardwareKeyboard).isFalse()
getInstrumentation().runOnMainSync {
- stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, false)
+ stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_VISIBLE, false)
animatorTestRule.advanceTimeBy(TASKBAR_STASH_DURATION_FOR_IME)
}
@@ -633,7 +633,7 @@
assume().that(activityContext.isHardwareKeyboard).isFalse()
getInstrumentation().runOnMainSync {
- stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, true)
+ stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_VISIBLE, true)
animatorTestRule.advanceTimeBy(0)
}
@@ -653,7 +653,7 @@
getInstrumentation().runOnMainSync {
stashController.updateStateForFlag(FLAG_IN_APP, true)
- stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, true)
+ stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_VISIBLE, true)
}
assertThat(stashController.isStashed).isFalse()
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java
index cad3b99..c399bdb 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java
@@ -16,6 +16,10 @@
package com.android.quickstep;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.launcher3.Flags.FLAG_ENABLE_SEPARATE_EXTERNAL_DISPLAY_TASKS;
+
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.TestCase.assertNull;
@@ -36,6 +40,9 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -50,6 +57,7 @@
import com.android.wm.shell.shared.split.SplitScreenConstants;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -65,6 +73,8 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
public class RecentTasksListTest {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock
private Context mContext;
@@ -158,11 +168,13 @@
}
@Test
+ @DisableFlags(FLAG_ENABLE_SEPARATE_EXTERNAL_DISPLAY_TASKS)
public void loadTasksInBackground_freeformTask_createsDesktopTask() throws Exception {
List<TaskInfo> tasks = Arrays.asList(
- createRecentTaskInfo(1 /* taskId */),
- createRecentTaskInfo(4 /* taskId */),
- createRecentTaskInfo(5 /* taskId */));
+ createRecentTaskInfo(1 /* taskId */, DEFAULT_DISPLAY),
+ createRecentTaskInfo(4 /* taskId */, DEFAULT_DISPLAY),
+ createRecentTaskInfo(5 /* taskId */, 1 /* displayId */),
+ createRecentTaskInfo(6 /* taskId */, 1 /* displayId */));
GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forFreeformTasks(
tasks, Collections.emptySet() /* minimizedTaskIds */);
when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
@@ -174,22 +186,57 @@
assertEquals(1, taskList.size());
assertEquals(TaskViewType.DESKTOP, taskList.get(0).taskViewType);
List<Task> actualFreeformTasks = taskList.get(0).getTasks();
- assertEquals(3, actualFreeformTasks.size());
+ assertEquals(4, actualFreeformTasks.size());
assertEquals(1, actualFreeformTasks.get(0).key.id);
assertFalse(actualFreeformTasks.get(0).isMinimized);
assertEquals(4, actualFreeformTasks.get(1).key.id);
assertFalse(actualFreeformTasks.get(1).isMinimized);
assertEquals(5, actualFreeformTasks.get(2).key.id);
assertFalse(actualFreeformTasks.get(2).isMinimized);
+ assertEquals(6, actualFreeformTasks.get(3).key.id);
+ assertFalse(actualFreeformTasks.get(3).isMinimized);
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_SEPARATE_EXTERNAL_DISPLAY_TASKS)
+ public void loadTasksInBackground_freeformTask_createsDesktopTaskPerDisplay() throws Exception {
+ List<TaskInfo> tasks = Arrays.asList(
+ createRecentTaskInfo(1 /* taskId */, DEFAULT_DISPLAY),
+ createRecentTaskInfo(4 /* taskId */, DEFAULT_DISPLAY),
+ createRecentTaskInfo(5 /* taskId */, 1 /* displayId */),
+ createRecentTaskInfo(6 /* taskId */, 1 /* displayId */));
+ GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forFreeformTasks(
+ tasks, Collections.emptySet() /* minimizedTaskIds */);
+ when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
+ .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
+
+ List<GroupTask> taskList = mRecentTasksList.loadTasksInBackground(
+ Integer.MAX_VALUE /* numTasks */, -1 /* requestId */, false /* loadKeysOnly */);
+
+ assertEquals(2, taskList.size());
+ assertEquals(TaskViewType.DESKTOP, taskList.get(0).taskViewType);
+ List<Task> actualFreeformTasksDefaultDisplay = taskList.get(0).getTasks();
+ assertEquals(2, actualFreeformTasksDefaultDisplay.size());
+ assertEquals(1, actualFreeformTasksDefaultDisplay.get(0).key.id);
+ assertFalse(actualFreeformTasksDefaultDisplay.get(0).isMinimized);
+ assertEquals(4, actualFreeformTasksDefaultDisplay.get(1).key.id);
+ assertFalse(actualFreeformTasksDefaultDisplay.get(1).isMinimized);
+
+ List<Task> actualFreeformTasksExternalDisplay = taskList.get(1).getTasks();
+ assertEquals(2, actualFreeformTasksExternalDisplay.size());
+ assertEquals(5, actualFreeformTasksExternalDisplay.get(0).key.id);
+ assertFalse(actualFreeformTasksExternalDisplay.get(0).isMinimized);
+ assertEquals(6, actualFreeformTasksExternalDisplay.get(1).key.id);
+ assertFalse(actualFreeformTasksExternalDisplay.get(1).isMinimized);
}
@Test
public void loadTasksInBackground_freeformTask_onlyMinimizedTasks_createDesktopTask()
throws Exception {
List<TaskInfo> tasks = Arrays.asList(
- createRecentTaskInfo(1 /* taskId */),
- createRecentTaskInfo(4 /* taskId */),
- createRecentTaskInfo(5 /* taskId */));
+ createRecentTaskInfo(1 /* taskId */, DEFAULT_DISPLAY),
+ createRecentTaskInfo(4 /* taskId */, DEFAULT_DISPLAY),
+ createRecentTaskInfo(5 /* taskId */, DEFAULT_DISPLAY));
Set<Integer> minimizedTaskIds =
Arrays.stream(new Integer[]{1, 4, 5}).collect(Collectors.toSet());
GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forFreeformTasks(tasks, minimizedTaskIds);
@@ -211,9 +258,10 @@
assertTrue(actualFreeformTasks.get(2).isMinimized);
}
- private TaskInfo createRecentTaskInfo(int taskId) {
+ private TaskInfo createRecentTaskInfo(int taskId, int displayId) {
RecentTaskInfo recentTaskInfo = new RecentTaskInfo();
recentTaskInfo.taskId = taskId;
+ recentTaskInfo.displayId = displayId;
return recentTaskInfo;
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.kt
index cf59f44..14570b5 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.kt
@@ -34,6 +34,9 @@
import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NOTIFICATION_DOT_ENABLED
import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_THEMED_ICON_DISABLED
import com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY
+import com.android.launcher3.util.DaggerSingletonTracker
+import com.android.launcher3.util.DisplayController
+import com.android.launcher3.util.SettingsCache
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
@@ -60,6 +63,9 @@
@Mock private lateinit var mStatsLogManager: StatsLogManager
@Mock private lateinit var mMockLogger: StatsLogManager.StatsLogger
+ @Mock private lateinit var mTracker: DaggerSingletonTracker
+ private var displayController: DisplayController = DisplayController.INSTANCE.get(mContext)
+ private var settingsCache: SettingsCache = SettingsCache.INSTANCE.get(mContext)
@Captor private lateinit var mEventCaptor: ArgumentCaptor<StatsLogManager.EventEnum>
@@ -82,7 +88,14 @@
// To match the default value of ALLOW_ROTATION
LauncherPrefs.get(mContext).put(item = ALLOW_ROTATION, value = false)
- mSystemUnderTest = SettingsChangeLogger(mContext, mStatsLogManager)
+ mSystemUnderTest =
+ SettingsChangeLogger(
+ mContext,
+ mStatsLogManager,
+ mTracker,
+ displayController,
+ settingsCache,
+ )
}
@After
@@ -93,7 +106,14 @@
@Test
fun loggingPrefs_correctDefaultValue() {
- val systemUnderTest = SettingsChangeLogger(mContext, mStatsLogManager)
+ val systemUnderTest =
+ SettingsChangeLogger(
+ mContext,
+ mStatsLogManager,
+ mTracker,
+ displayController,
+ settingsCache,
+ )
assertThat(systemUnderTest.loggingPrefs[ALLOW_ROTATION_PREFERENCE_KEY]!!.defaultValue)
.isFalse()
@@ -120,7 +140,8 @@
LauncherPrefs.get(mContext).put(item = ALLOW_ROTATION, value = true)
// This a new object so the values of mLoggablePrefs will be different
- SettingsChangeLogger(mContext, mStatsLogManager).logSnapshot(mInstanceId)
+ SettingsChangeLogger(mContext, mStatsLogManager, mTracker, displayController, settingsCache)
+ .logSnapshot(mInstanceId)
verify(mMockLogger, atLeastOnce()).log(mEventCaptor.capture())
val capturedEvents = mEventCaptor.allValues
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/GetSysUiStatusNavFlagsUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/GetSysUiStatusNavFlagsUseCaseTest.kt
new file mode 100644
index 0000000..d384256
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/GetSysUiStatusNavFlagsUseCaseTest.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.domain.usecase
+
+import android.view.WindowInsetsController.APPEARANCE_LIGHT_CAPTION_BARS
+import android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
+import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
+import com.android.launcher3.util.SystemUiController.FLAG_DARK_NAV
+import com.android.launcher3.util.SystemUiController.FLAG_DARK_STATUS
+import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_NAV
+import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_STATUS
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class GetSysUiStatusNavFlagsUseCaseTest {
+ private val sut: GetSysUiStatusNavFlagsUseCase = GetSysUiStatusNavFlagsUseCase()
+
+ @Test
+ fun onLightStatusBarAppearance_returns_LightTheme() {
+ val thumbnailData = ThumbnailData(appearance = APPEARANCE_LIGHT_STATUS_BARS)
+ val flag = sut.invoke(thumbnailData) // 6
+ flag.assertContainsFlag(FLAG_LIGHT_STATUS)
+ flag.assertContainsFlag(FLAG_DARK_NAV)
+ flag.assertDoesNotContainsFlag(FLAG_DARK_STATUS)
+ flag.assertDoesNotContainsFlag(FLAG_LIGHT_NAV)
+ }
+
+ @Test
+ fun onLightNavBarsAppearance_returns_LightTheme() {
+ val thumbnailData = ThumbnailData(appearance = APPEARANCE_LIGHT_NAVIGATION_BARS)
+ val flag = sut.invoke(thumbnailData)
+ flag.assertContainsFlag(FLAG_DARK_STATUS)
+ flag.assertContainsFlag(FLAG_LIGHT_NAV)
+ flag.assertDoesNotContainsFlag(FLAG_LIGHT_STATUS)
+ flag.assertDoesNotContainsFlag(FLAG_DARK_NAV)
+ }
+
+ @Test
+ fun onLightStatusBarAndNavBarAppearance_returns_LightTheme() {
+ val thumbnailData =
+ ThumbnailData(
+ appearance = APPEARANCE_LIGHT_STATUS_BARS or APPEARANCE_LIGHT_NAVIGATION_BARS
+ )
+ val flag = sut.invoke(thumbnailData)
+ flag.assertContainsFlag(FLAG_LIGHT_NAV)
+ flag.assertContainsFlag(FLAG_LIGHT_STATUS)
+ flag.assertDoesNotContainsFlag(FLAG_DARK_STATUS)
+ flag.assertDoesNotContainsFlag(FLAG_DARK_NAV)
+ }
+
+ @Test
+ fun onLightAppearance_returns_LightTheme() {
+ val thumbnailData =
+ ThumbnailData(
+ appearance =
+ APPEARANCE_LIGHT_CAPTION_BARS or
+ APPEARANCE_LIGHT_STATUS_BARS or
+ APPEARANCE_LIGHT_NAVIGATION_BARS
+ )
+ val flag = sut.invoke(thumbnailData)
+ flag.assertContainsFlag(FLAG_LIGHT_NAV)
+ flag.assertContainsFlag(FLAG_LIGHT_STATUS)
+ flag.assertDoesNotContainsFlag(FLAG_DARK_STATUS)
+ flag.assertDoesNotContainsFlag(FLAG_DARK_NAV)
+ }
+
+ @Test
+ fun onDarkAppearance_returns_DarkTheme() {
+ val thumbnailData = ThumbnailData(appearance = 0)
+ val flag = sut.invoke(thumbnailData)
+ flag.assertContainsFlag(FLAG_DARK_STATUS)
+ flag.assertContainsFlag(FLAG_DARK_NAV)
+ flag.assertDoesNotContainsFlag(FLAG_LIGHT_NAV)
+ flag.assertDoesNotContainsFlag(FLAG_LIGHT_STATUS)
+ }
+
+ @Test
+ fun onUnrelatedDarkAppearance_returns_DarkTheme() {
+ val thumbnailData = ThumbnailData(appearance = 1)
+ val flag = sut.invoke(thumbnailData)
+ flag.assertContainsFlag(FLAG_DARK_STATUS)
+ flag.assertContainsFlag(FLAG_DARK_NAV)
+ flag.assertDoesNotContainsFlag(FLAG_LIGHT_NAV)
+ flag.assertDoesNotContainsFlag(FLAG_LIGHT_STATUS)
+ }
+
+ @Test
+ fun whenThumbnailIsNull_returns_default() {
+ val flag = sut.invoke(null)
+ flag.assertDoesNotContainsFlag(FLAG_DARK_STATUS)
+ flag.assertDoesNotContainsFlag(FLAG_LIGHT_NAV)
+ flag.assertDoesNotContainsFlag(FLAG_LIGHT_STATUS)
+ flag.assertDoesNotContainsFlag(FLAG_DARK_NAV)
+ }
+
+ private fun Int.assertContainsFlag(flag: Int) {
+ assertThat(this and flag).isNotEqualTo(0)
+ }
+
+ private fun Int.assertDoesNotContainsFlag(flag: Int) {
+ assertThat(this and flag).isEqualTo(0)
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapperTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapperTest.kt
new file mode 100644
index 0000000..124045f
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapperTest.kt
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.ui.mapper
+
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.graphics.drawable.ShapeDrawable
+import android.platform.test.annotations.EnableFlags
+import android.view.Surface
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.Flags
+import com.android.quickstep.recents.ui.viewmodel.TaskData
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.ThumbnailHeader
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class TaskUiStateMapperTest {
+
+ @Test
+ fun taskData_isNull_returns_Uninitialized() {
+ val result =
+ TaskUiStateMapper.toTaskThumbnailUiState(
+ taskData = null,
+ isLiveTile = false,
+ hasHeader = false,
+ )
+ assertThat(result).isEqualTo(TaskThumbnailUiState.Uninitialized)
+ }
+
+ @Test
+ fun taskData_isLiveTile_returns_LiveTile() {
+ val inputs =
+ listOf(TASK_DATA, TASK_DATA.copy(thumbnailData = null), TASK_DATA.copy(isLocked = true))
+ inputs.forEach { input ->
+ val result =
+ TaskUiStateMapper.toTaskThumbnailUiState(
+ taskData = input,
+ isLiveTile = true,
+ hasHeader = false,
+ )
+ assertThat(result).isEqualTo(LiveTile.WithoutHeader)
+ }
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
+ @Test
+ fun taskData_isLiveTileWithHeader_returns_LiveTileWithHeader() {
+ val inputs =
+ listOf(
+ TASK_DATA,
+ TASK_DATA.copy(thumbnailData = null),
+ TASK_DATA.copy(isLocked = true),
+ TASK_DATA.copy(title = null),
+ )
+ val expected =
+ LiveTile.WithHeader(header = ThumbnailHeader(TASK_ICON, TASK_TITLE_DESCRIPTION))
+ inputs.forEach { taskData ->
+ val result =
+ TaskUiStateMapper.toTaskThumbnailUiState(
+ taskData = taskData,
+ isLiveTile = true,
+ hasHeader = true,
+ )
+ assertThat(result).isEqualTo(expected)
+ }
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
+ @Test
+ fun taskData_isLiveTileWithHeader_missingHeaderData_returns_LiveTileWithoutHeader() {
+ val inputs =
+ listOf(
+ TASK_DATA.copy(icon = null),
+ TASK_DATA.copy(titleDescription = null),
+ TASK_DATA.copy(icon = null, titleDescription = null),
+ )
+
+ inputs.forEach { taskData ->
+ val result =
+ TaskUiStateMapper.toTaskThumbnailUiState(
+ taskData = taskData,
+ isLiveTile = true,
+ hasHeader = true,
+ )
+ assertThat(result).isEqualTo(LiveTile.WithoutHeader)
+ }
+ }
+
+ @Test
+ fun taskData_isStaticTile_returns_SnapshotSplash() {
+ val result =
+ TaskUiStateMapper.toTaskThumbnailUiState(
+ taskData = TASK_DATA,
+ isLiveTile = false,
+ hasHeader = false,
+ )
+
+ val expected =
+ TaskThumbnailUiState.SnapshotSplash(
+ snapshot =
+ Snapshot.WithoutHeader(
+ backgroundColor = TASK_BACKGROUND_COLOR,
+ bitmap = TASK_THUMBNAIL,
+ thumbnailRotation = Surface.ROTATION_0,
+ ),
+ splash = TASK_ICON,
+ )
+
+ assertThat(result).isEqualTo(expected)
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
+ @Test
+ fun taskData_isStaticTile_withHeader_returns_SnapshotSplashWithHeader() {
+ val inputs = listOf(TASK_DATA, TASK_DATA.copy(title = null))
+ val expected =
+ TaskThumbnailUiState.SnapshotSplash(
+ snapshot =
+ Snapshot.WithHeader(
+ backgroundColor = TASK_BACKGROUND_COLOR,
+ bitmap = TASK_THUMBNAIL,
+ thumbnailRotation = Surface.ROTATION_0,
+ header = ThumbnailHeader(TASK_ICON, TASK_TITLE_DESCRIPTION),
+ ),
+ splash = TASK_ICON,
+ )
+ inputs.forEach { taskData ->
+ val result =
+ TaskUiStateMapper.toTaskThumbnailUiState(
+ taskData = taskData,
+ isLiveTile = false,
+ hasHeader = true,
+ )
+ assertThat(result).isEqualTo(expected)
+ }
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
+ @Test
+ fun taskData_isStaticTile_missingHeaderData_returns_SnapshotSplashWithoutHeader() {
+ val inputs =
+ listOf(
+ TASK_DATA.copy(titleDescription = null, icon = null),
+ TASK_DATA.copy(titleDescription = null),
+ TASK_DATA.copy(icon = null),
+ )
+ val expected =
+ Snapshot.WithoutHeader(
+ backgroundColor = TASK_BACKGROUND_COLOR,
+ thumbnailRotation = Surface.ROTATION_0,
+ bitmap = TASK_THUMBNAIL,
+ )
+ inputs.forEach { taskData ->
+ val result =
+ TaskUiStateMapper.toTaskThumbnailUiState(
+ taskData = taskData,
+ isLiveTile = false,
+ hasHeader = true,
+ )
+
+ assertThat(result).isInstanceOf(TaskThumbnailUiState.SnapshotSplash::class.java)
+ result as TaskThumbnailUiState.SnapshotSplash
+ assertThat(result.snapshot).isEqualTo(expected)
+ }
+ }
+
+ @Test
+ fun taskData_thumbnailIsNull_returns_BackgroundOnly() {
+ val result =
+ TaskUiStateMapper.toTaskThumbnailUiState(
+ taskData = TASK_DATA.copy(thumbnailData = null),
+ isLiveTile = false,
+ hasHeader = false,
+ )
+
+ val expected = TaskThumbnailUiState.BackgroundOnly(TASK_BACKGROUND_COLOR)
+ assertThat(result).isEqualTo(expected)
+ }
+
+ @Test
+ fun taskData_isLocked_returns_BackgroundOnly() {
+ val result =
+ TaskUiStateMapper.toTaskThumbnailUiState(
+ taskData = TASK_DATA.copy(isLocked = true),
+ isLiveTile = false,
+ hasHeader = false,
+ )
+
+ val expected = TaskThumbnailUiState.BackgroundOnly(TASK_BACKGROUND_COLOR)
+ assertThat(result).isEqualTo(expected)
+ }
+
+ private companion object {
+ const val TASK_TITLE_DESCRIPTION = "Title Description 1"
+ val TASK_ICON = ShapeDrawable()
+ val TASK_THUMBNAIL = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+ val TASK_THUMBNAIL_DATA =
+ ThumbnailData(thumbnail = TASK_THUMBNAIL, rotation = Surface.ROTATION_0)
+ val TASK_BACKGROUND_COLOR = Color.rgb(1, 2, 3)
+ val TASK_DATA =
+ TaskData.Data(
+ 1,
+ title = "Task 1",
+ titleDescription = TASK_TITLE_DESCRIPTION,
+ icon = TASK_ICON,
+ thumbnailData = TASK_THUMBNAIL_DATA,
+ backgroundColor = TASK_BACKGROUND_COLOR,
+ isLocked = false,
+ )
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModelTest.kt
index 54a27e9..c031150 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModelTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModelTest.kt
@@ -18,11 +18,18 @@
import android.graphics.Color
import android.graphics.drawable.ShapeDrawable
+import android.view.WindowInsetsController.APPEARANCE_LIGHT_CAPTION_BARS
+import android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
+import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_NAV
+import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_STATUS
import com.android.launcher3.util.TestDispatcherProvider
import com.android.quickstep.recents.domain.model.TaskModel
+import com.android.quickstep.recents.domain.usecase.GetSysUiStatusNavFlagsUseCase
import com.android.quickstep.recents.domain.usecase.GetTaskUseCase
import com.android.quickstep.recents.viewmodel.RecentsViewData
+import com.android.quickstep.views.TaskViewType
import com.android.systemui.shared.recents.model.ThumbnailData
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -45,15 +52,18 @@
private val recentsViewData = RecentsViewData()
private val getTaskUseCase = mock<GetTaskUseCase>()
- private val sut =
- TaskViewModel(
- recentsViewData = recentsViewData,
- getTaskUseCase = getTaskUseCase,
- dispatcherProvider = TestDispatcherProvider(unconfinedTestDispatcher),
- )
+ private lateinit var sut: TaskViewModel
@Before
fun setUp() {
+ sut =
+ TaskViewModel(
+ taskViewType = TaskViewType.SINGLE,
+ recentsViewData = recentsViewData,
+ getTaskUseCase = getTaskUseCase,
+ getSysUiStatusNavFlagsUseCase = GetSysUiStatusNavFlagsUseCase(),
+ dispatcherProvider = TestDispatcherProvider(unconfinedTestDispatcher),
+ )
whenever(getTaskUseCase.invoke(TASK_MODEL_1.id)).thenReturn(flow { emit(TASK_MODEL_1) })
whenever(getTaskUseCase.invoke(TASK_MODEL_2.id)).thenReturn(flow { emit(TASK_MODEL_2) })
whenever(getTaskUseCase.invoke(TASK_MODEL_3.id)).thenReturn(flow { emit(TASK_MODEL_3) })
@@ -65,11 +75,41 @@
fun singleTaskRetrieved_when_validTaskId() =
testScope.runTest {
sut.bind(TASK_MODEL_1.id)
- val expectedResult = TaskTileUiState(listOf(TASK_MODEL_1.toUiState()), false)
+ val expectedResult =
+ TaskTileUiState(
+ tasks = listOf(TASK_MODEL_1.toUiState()),
+ isLiveTile = false,
+ hasHeader = false,
+ sysUiStatusNavFlags = FLAGS_APPEARANCE_LIGHT_THEME,
+ )
assertThat(sut.state.first()).isEqualTo(expectedResult)
}
@Test
+ fun hasHeader_when_taskViewTypeIsDesktop() =
+ testScope.runTest {
+ val expectedResults =
+ mapOf(
+ TaskViewType.SINGLE to false,
+ TaskViewType.GROUPED to false,
+ TaskViewType.DESKTOP to true,
+ )
+
+ expectedResults.forEach { (type, expectedResult) ->
+ sut =
+ TaskViewModel(
+ taskViewType = type,
+ recentsViewData = recentsViewData,
+ getTaskUseCase = getTaskUseCase,
+ getSysUiStatusNavFlagsUseCase = GetSysUiStatusNavFlagsUseCase(),
+ dispatcherProvider = TestDispatcherProvider(unconfinedTestDispatcher),
+ )
+ sut.bind(TASK_MODEL_1.id)
+ assertThat(sut.state.first().hasHeader).isEqualTo(expectedResult)
+ }
+ }
+
+ @Test
fun multipleTasksRetrieved_when_validTaskIds() =
testScope.runTest {
sut.bind(TASK_MODEL_1.id, TASK_MODEL_2.id, TASK_MODEL_3.id, INVALID_TASK_ID)
@@ -83,6 +123,8 @@
TaskData.NoData(INVALID_TASK_ID),
),
isLiveTile = false,
+ hasHeader = false,
+ sysUiStatusNavFlags = FLAGS_APPEARANCE_LIGHT_THEME,
)
assertThat(sut.state.first()).isEqualTo(expectedResult)
}
@@ -103,6 +145,8 @@
TASK_MODEL_3.toUiState(),
),
isLiveTile = true,
+ hasHeader = false,
+ sysUiStatusNavFlags = FLAGS_APPEARANCE_LIGHT_THEME,
)
assertThat(sut.state.first()).isEqualTo(expectedResult)
}
@@ -123,6 +167,8 @@
TASK_MODEL_3.toUiState(),
),
isLiveTile = false,
+ hasHeader = false,
+ sysUiStatusNavFlags = FLAGS_APPEARANCE_LIGHT_THEME,
)
assertThat(sut.state.first()).isEqualTo(expectedResult)
}
@@ -142,6 +188,8 @@
TASK_MODEL_3.toUiState(),
),
isLiveTile = false,
+ hasHeader = false,
+ sysUiStatusNavFlags = FLAGS_APPEARANCE_LIGHT_THEME,
)
assertThat(sut.state.first()).isEqualTo(expectedResult)
}
@@ -157,6 +205,8 @@
TaskTileUiState(
tasks = listOf(TASK_MODEL_1.toUiState(), TASK_MODEL_2.toUiState()),
isLiveTile = false,
+ hasHeader = false,
+ sysUiStatusNavFlags = FLAGS_APPEARANCE_LIGHT_THEME,
)
assertThat(sut.state.first()).isEqualTo(expectedResult)
}
@@ -166,7 +216,12 @@
testScope.runTest {
sut.bind(INVALID_TASK_ID)
val expectedResult =
- TaskTileUiState(listOf(TaskData.NoData(INVALID_TASK_ID)), isLiveTile = false)
+ TaskTileUiState(
+ listOf(TaskData.NoData(INVALID_TASK_ID)),
+ isLiveTile = false,
+ hasHeader = false,
+ sysUiStatusNavFlags = FLAGS_APPEARANCE_DEFAULT,
+ )
assertThat(sut.state.first()).isEqualTo(expectedResult)
}
@@ -174,21 +229,29 @@
TaskData.Data(
taskId = id,
title = title,
+ titleDescription = titleDescription,
icon = icon!!,
thumbnailData = thumbnail,
backgroundColor = backgroundColor,
isLocked = isLocked,
)
- companion object {
+ private companion object {
const val INVALID_TASK_ID = -1
+ const val FLAGS_APPEARANCE_LIGHT_THEME = FLAG_LIGHT_STATUS or FLAG_LIGHT_NAV
+ const val FLAGS_APPEARANCE_DEFAULT = 0
+ const val APPEARANCE_LIGHT_THEME =
+ APPEARANCE_LIGHT_CAPTION_BARS or
+ APPEARANCE_LIGHT_STATUS_BARS or
+ APPEARANCE_LIGHT_NAVIGATION_BARS
+
val TASK_MODEL_1 =
TaskModel(
1,
"Title 1",
"Content Description 1",
ShapeDrawable(),
- ThumbnailData(),
+ ThumbnailData(appearance = APPEARANCE_LIGHT_THEME),
Color.BLACK,
false,
)
@@ -198,7 +261,7 @@
"Title 2",
"Content Description 2",
ShapeDrawable(),
- ThumbnailData(),
+ ThumbnailData(appearance = APPEARANCE_LIGHT_THEME),
Color.RED,
true,
)
@@ -208,7 +271,7 @@
"Title 3",
"Content Description 3",
ShapeDrawable(),
- ThumbnailData(),
+ ThumbnailData(appearance = APPEARANCE_LIGHT_THEME),
Color.BLUE,
false,
)
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/SysUiStatusNavFlagsUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/SysUiStatusNavFlagsUseCaseTest.kt
deleted file mode 100644
index 92f2efd..0000000
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/SysUiStatusNavFlagsUseCaseTest.kt
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quickstep.recents.usecase
-
-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.systemui.shared.recents.model.Task
-import com.android.systemui.shared.recents.model.ThumbnailData
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.whenever
-
-/** Test for [SysUiStatusNavFlagsUseCase] */
-class SysUiStatusNavFlagsUseCaseTest {
- private lateinit var tasksRepository: FakeTasksRepository
- private lateinit var sysUiStatusNavFlagsUseCase: SysUiStatusNavFlagsUseCase
-
- @Before
- fun setup() {
- tasksRepository = FakeTasksRepository()
- sysUiStatusNavFlagsUseCase = SysUiStatusNavFlagsUseCase(tasksRepository)
- initTaskRepository()
- }
-
- @Test
- fun onLightAppearanceReturnExpectedFlags() {
- assertThat(sysUiStatusNavFlagsUseCase.getSysUiStatusNavFlags(FIRST_TASK_ID))
- .isEqualTo(FLAGS_APPEARANCE_LIGHT_THEME)
- }
-
- @Test
- fun onDarkAppearanceReturnExpectedFlags() {
- assertThat(sysUiStatusNavFlagsUseCase.getSysUiStatusNavFlags(SECOND_TASK_ID))
- .isEqualTo(FLAGS_APPEARANCE_DARK_THEME)
- }
-
- @Test
- fun whenThumbnailIsNullReturnDefault() {
- assertThat(sysUiStatusNavFlagsUseCase.getSysUiStatusNavFlags(UNKNOWN_TASK_ID))
- .isEqualTo(FLAGS_DEFAULT)
- }
-
- private fun initTaskRepository() {
- val firstTask =
- Task(Task.TaskKey(FIRST_TASK_ID, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
- colorBackground = Color.BLACK
- }
- val firstThumbnailData =
- ThumbnailData(
- thumbnail =
- mock<Bitmap>().apply {
- whenever(width).thenReturn(THUMBNAIL_WIDTH)
- whenever(height).thenReturn(THUMBNAIL_HEIGHT)
- },
- appearance = APPEARANCE_LIGHT_THEME,
- )
-
- val secondTask =
- Task(Task.TaskKey(SECOND_TASK_ID, 0, Intent(), ComponentName("", ""), 0, 2005)).apply {
- colorBackground = Color.BLACK
- }
- val secondThumbnailData =
- ThumbnailData(
- thumbnail =
- mock<Bitmap>().apply {
- whenever(width).thenReturn(THUMBNAIL_WIDTH)
- whenever(height).thenReturn(THUMBNAIL_HEIGHT)
- },
- appearance = APPEARANCE_DARK_THEME,
- )
-
- tasksRepository.seedTasks(listOf(firstTask, secondTask))
- tasksRepository.seedThumbnailData(
- mapOf(FIRST_TASK_ID to firstThumbnailData, SECOND_TASK_ID to secondThumbnailData)
- )
- tasksRepository.setVisibleTasks(setOf(FIRST_TASK_ID, SECOND_TASK_ID))
- }
-
- companion object {
- const val FIRST_TASK_ID = 0
- const val SECOND_TASK_ID = 100
- const val UNKNOWN_TASK_ID = 404
- const val THUMBNAIL_WIDTH = 100
- const val THUMBNAIL_HEIGHT = 200
- const val APPEARANCE_LIGHT_THEME = 24
- const val FLAGS_APPEARANCE_LIGHT_THEME = 5
- const val APPEARANCE_DARK_THEME = 0
- const val FLAGS_APPEARANCE_DARK_THEME = 10
- const val FLAGS_DEFAULT = 0
- }
-}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt
index a956c9c..aec586d 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt
@@ -16,39 +16,15 @@
package com.android.quickstep.task.thumbnail
-import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
-import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
-import android.content.ComponentName
-import android.content.Intent
-import android.graphics.Bitmap
-import android.graphics.Color
import android.graphics.Matrix
-import android.graphics.drawable.Drawable
-import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
-import android.view.Surface
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.launcher3.Flags
import com.android.launcher3.util.TestDispatcherProvider
-import com.android.quickstep.recents.data.FakeRecentsDeviceProfileRepository
-import com.android.quickstep.recents.data.FakeTasksRepository
-import com.android.quickstep.recents.data.RecentsDeviceProfile
import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
import com.android.quickstep.recents.usecase.ThumbnailPositionState.MatrixScaling
import com.android.quickstep.recents.usecase.ThumbnailPositionState.MissingThumbnail
-import com.android.quickstep.recents.viewmodel.RecentsViewData
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.SnapshotSplash
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.ThumbnailHeader
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
-import com.android.quickstep.task.viewmodel.TaskContainerData
import com.android.quickstep.task.viewmodel.TaskThumbnailViewModelImpl
-import com.android.systemui.shared.recents.model.Task
-import com.android.systemui.shared.recents.model.ThumbnailData
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
@@ -66,221 +42,18 @@
private val dispatcher = StandardTestDispatcher()
private val testScope = TestScope(dispatcher)
- private val recentsViewData = RecentsViewData()
- private val taskContainerData = TaskContainerData()
private val dispatcherProvider = TestDispatcherProvider(dispatcher)
- private val tasksRepository = FakeTasksRepository()
- private val deviceProfileRepository = FakeRecentsDeviceProfileRepository()
private val mGetThumbnailPositionUseCase = mock<GetThumbnailPositionUseCase>()
private val splashAlphaUseCase: SplashAlphaUseCase = mock()
private val systemUnderTest by lazy {
TaskThumbnailViewModelImpl(
- recentsViewData,
- taskContainerData,
dispatcherProvider,
- tasksRepository,
- deviceProfileRepository,
mGetThumbnailPositionUseCase,
splashAlphaUseCase,
)
}
- private val fullscreenTaskIdRange: IntRange = 0..5
- private val freeformTaskIdRange: IntRange = 6..10
-
- private val fullscreenTasks = fullscreenTaskIdRange.map(::createTaskWithId)
- private val freeformTasks = freeformTaskIdRange.map(::createFreeformTaskWithId)
- private val tasks = fullscreenTasks + freeformTasks
-
- @Test
- fun initialStateIsUninitialized() =
- testScope.runTest { assertThat(systemUnderTest.uiState.first()).isEqualTo(Uninitialized) }
-
- @Test
- fun bindRunningTask_thenStateIs_LiveTile() =
- testScope.runTest {
- val taskId = 1
- tasksRepository.seedTasks(tasks)
- tasksRepository.setVisibleTasks(setOf(taskId))
- recentsViewData.runningTaskIds.value = setOf(taskId)
- systemUnderTest.bind(taskId)
-
- assertThat(systemUnderTest.uiState.first()).isEqualTo(LiveTile.WithoutHeader)
- }
-
- @Test
- fun bindRunningTaskShouldShowScreenshot_thenStateIs_SnapshotSplash() =
- testScope.runTest {
- val taskId = 1
- val expectedThumbnailData = createThumbnailData()
- tasksRepository.seedThumbnailData(mapOf(taskId to expectedThumbnailData))
- val expectedIconData = mock<Drawable>()
- tasksRepository.seedIconData(taskId, "Task $taskId", "", expectedIconData)
- tasksRepository.seedTasks(tasks)
- tasksRepository.setVisibleTasks(setOf(taskId))
- recentsViewData.runningTaskIds.value = setOf(taskId)
- recentsViewData.runningTaskShowScreenshot.value = true
- systemUnderTest.bind(taskId)
-
- assertThat(systemUnderTest.uiState.first())
- .isEqualTo(
- SnapshotSplash(
- Snapshot.WithoutHeader(
- backgroundColor = Color.rgb(1, 1, 1),
- bitmap = expectedThumbnailData.thumbnail!!,
- thumbnailRotation = Surface.ROTATION_0,
- ),
- expectedIconData,
- )
- )
- }
-
- @Test
- fun bindRunningTaskThenStoppedTaskWithoutThumbnail_thenStateChangesToBackgroundOnly() =
- testScope.runTest {
- val runningTaskId = 1
- val stoppedTaskId = 2
- tasksRepository.seedTasks(tasks)
- tasksRepository.setVisibleTasks(setOf(runningTaskId, stoppedTaskId))
- recentsViewData.runningTaskIds.value = setOf(runningTaskId)
- systemUnderTest.bind(runningTaskId)
- assertThat(systemUnderTest.uiState.first()).isEqualTo(LiveTile.WithoutHeader)
-
- systemUnderTest.bind(stoppedTaskId)
- assertThat(systemUnderTest.uiState.first())
- .isEqualTo(BackgroundOnly(backgroundColor = Color.rgb(2, 2, 2)))
- }
-
- @Test
- fun bindStoppedTaskWithoutThumbnail_thenStateIs_BackgroundOnly_withAlphaRemoved() =
- testScope.runTest {
- val stoppedTaskId = 2
- tasksRepository.seedTasks(tasks)
- tasksRepository.setVisibleTasks(setOf(stoppedTaskId))
-
- systemUnderTest.bind(stoppedTaskId)
- assertThat(systemUnderTest.uiState.first())
- .isEqualTo(BackgroundOnly(backgroundColor = Color.rgb(2, 2, 2)))
- }
-
- @Test
- fun bindLockedTaskWithThumbnail_thenStateIs_BackgroundOnly() =
- testScope.runTest {
- val taskId = 2
- tasksRepository.seedThumbnailData(mapOf(taskId to createThumbnailData()))
- tasks[taskId].isLocked = true
- tasksRepository.seedTasks(tasks)
- tasksRepository.setVisibleTasks(setOf(taskId))
-
- systemUnderTest.bind(taskId)
- assertThat(systemUnderTest.uiState.first())
- .isEqualTo(BackgroundOnly(backgroundColor = Color.rgb(2, 2, 2)))
- }
-
- @Test
- fun bindStoppedTaskWithThumbnail_thenStateIs_SnapshotSplash_withAlphaRemoved() =
- testScope.runTest {
- val taskId = 2
- val expectedThumbnailData = createThumbnailData(rotation = Surface.ROTATION_270)
- tasksRepository.seedThumbnailData(mapOf(taskId to expectedThumbnailData))
- val expectedIconData = mock<Drawable>()
- tasksRepository.seedIconData(taskId, "Task $taskId", "", expectedIconData)
- tasksRepository.seedTasks(tasks)
- tasksRepository.setVisibleTasks(setOf(taskId))
-
- systemUnderTest.bind(taskId)
- assertThat(systemUnderTest.uiState.first())
- .isEqualTo(
- SnapshotSplash(
- Snapshot.WithoutHeader(
- backgroundColor = Color.rgb(2, 2, 2),
- bitmap = expectedThumbnailData.thumbnail!!,
- thumbnailRotation = Surface.ROTATION_270,
- ),
- expectedIconData,
- )
- )
- }
-
- @Test
- fun bindNonVisibleStoppedTask_whenMadeVisible_thenStateIsSnapshotSplash() =
- testScope.runTest {
- val taskId = 2
- val expectedThumbnailData = createThumbnailData()
- tasksRepository.seedThumbnailData(mapOf(taskId to expectedThumbnailData))
- val expectedIconData = mock<Drawable>()
- tasksRepository.seedIconData(taskId, "Task $taskId", "", expectedIconData)
- tasksRepository.seedTasks(tasks)
-
- systemUnderTest.bind(taskId)
- assertThat(systemUnderTest.uiState.first()).isEqualTo(Uninitialized)
-
- tasksRepository.setVisibleTasks(setOf(taskId))
- assertThat(systemUnderTest.uiState.first())
- .isEqualTo(
- SnapshotSplash(
- Snapshot.WithoutHeader(
- backgroundColor = Color.rgb(2, 2, 2),
- bitmap = expectedThumbnailData.thumbnail!!,
- thumbnailRotation = Surface.ROTATION_0,
- ),
- expectedIconData,
- )
- )
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
- fun bindRunningTask_inDesktop_thenStateIs_LiveTile_withHeader() =
- testScope.runTest {
- deviceProfileRepository.setRecentsDeviceProfile(
- RecentsDeviceProfile(isLargeScreen = true, canEnterDesktopMode = true)
- )
-
- val taskId = freeformTaskIdRange.first
- val expectedIconData = mock<Drawable>()
- tasksRepository.seedIconData(taskId, "Task $taskId", "Task $taskId", expectedIconData)
- tasksRepository.seedTasks(freeformTasks)
- tasksRepository.setVisibleTasks(setOf(taskId))
- recentsViewData.runningTaskIds.value = setOf(taskId)
- systemUnderTest.bind(taskId)
-
- assertThat(systemUnderTest.uiState.first())
- .isEqualTo(LiveTile.WithHeader(ThumbnailHeader(expectedIconData, "Task $taskId")))
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
- fun bindStoppedTaskWithThumbnail_inDesktop_thenStateIs_SnapshotSplash_withHeader() =
- testScope.runTest {
- deviceProfileRepository.setRecentsDeviceProfile(
- RecentsDeviceProfile(isLargeScreen = true, canEnterDesktopMode = true)
- )
-
- val taskId = freeformTaskIdRange.first
- val expectedThumbnailData = createThumbnailData(rotation = Surface.ROTATION_0)
- tasksRepository.seedThumbnailData(mapOf(taskId to expectedThumbnailData))
- val expectedIconData = mock<Drawable>()
- tasksRepository.seedIconData(taskId, "Task $taskId", "Task $taskId", expectedIconData)
- tasksRepository.seedTasks(freeformTasks)
- tasksRepository.setVisibleTasks(setOf(taskId))
-
- systemUnderTest.bind(taskId)
- assertThat(systemUnderTest.uiState.first())
- .isEqualTo(
- SnapshotSplash(
- Snapshot.WithHeader(
- backgroundColor = Color.rgb(taskId, taskId, taskId),
- bitmap = expectedThumbnailData.thumbnail!!,
- thumbnailRotation = Surface.ROTATION_0,
- header = ThumbnailHeader(expectedIconData, "Task $taskId"),
- ),
- expectedIconData,
- )
- )
- }
-
@Test
fun getSnapshotMatrix_MissingThumbnail() =
testScope.runTest {
@@ -313,75 +86,7 @@
.isEqualTo(MATRIX)
}
- @Test
- fun getForegroundScrimDimProgress_returnsForegroundMaxScrim() =
- testScope.runTest {
- recentsViewData.tintAmount.value = 0.32f
- taskContainerData.taskMenuOpenProgress.value = 0f
- assertThat(systemUnderTest.dimProgress.first()).isEqualTo(0.32f)
- }
-
- @Test
- fun getTaskMenuScrimDimProgress_returnsTaskMenuScrim() =
- testScope.runTest {
- recentsViewData.tintAmount.value = 0f
- taskContainerData.taskMenuOpenProgress.value = 1f
- assertThat(systemUnderTest.dimProgress.first()).isEqualTo(0.4f)
- }
-
- @Test
- fun getForegroundScrimDimProgress_returnsNoScrim() =
- testScope.runTest {
- recentsViewData.tintAmount.value = 0f
- taskContainerData.taskMenuOpenProgress.value = 0f
- assertThat(systemUnderTest.dimProgress.first()).isEqualTo(0f)
- }
-
- private fun createTaskWithId(taskId: Int) =
- Task(
- Task.TaskKey(
- taskId,
- WINDOWING_MODE_FULLSCREEN,
- Intent(),
- ComponentName("", ""),
- 0,
- 2000,
- )
- )
- .apply {
- colorBackground = Color.argb(taskId, taskId, taskId, taskId)
- titleDescription = "Task $taskId"
- icon = mock<Drawable>()
- }
-
- private fun createFreeformTaskWithId(taskId: Int) =
- Task(
- Task.TaskKey(
- taskId,
- WINDOWING_MODE_FREEFORM,
- Intent(),
- ComponentName("", ""),
- 0,
- 2000,
- )
- )
- .apply {
- colorBackground = Color.argb(taskId, taskId, taskId, taskId)
- titleDescription = "Task $taskId"
- icon = mock<Drawable>()
- }
-
- private fun createThumbnailData(rotation: Int = Surface.ROTATION_0): ThumbnailData {
- val bitmap = mock<Bitmap>()
- whenever(bitmap.width).thenReturn(THUMBNAIL_WIDTH)
- whenever(bitmap.height).thenReturn(THUMBNAIL_HEIGHT)
-
- return ThumbnailData(thumbnail = bitmap, rotation = rotation)
- }
-
- companion object {
- const val THUMBNAIL_WIDTH = 100
- const val THUMBNAIL_HEIGHT = 200
+ private companion object {
const val CANVAS_WIDTH = 300
const val CANVAS_HEIGHT = 600
val MATRIX =
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/ActiveTrackpadListTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/ActiveTrackpadListTest.kt
new file mode 100644
index 0000000..b4c236e
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/ActiveTrackpadListTest.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util
+
+import android.hardware.input.InputManager
+import android.view.InputDevice
+import android.view.InputDevice.SOURCE_MOUSE
+import android.view.InputDevice.SOURCE_TOUCHPAD
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.IntArray
+import com.android.launcher3.util.SandboxApplication
+import com.android.launcher3.util.TestUtil
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.whenever
+
+@RunWith(AndroidJUnit4::class)
+class ActiveTrackpadListTest {
+
+ @get:Rule val context = SandboxApplication()
+
+ private val inputDeviceIds = IntArray()
+ private lateinit var inputManager: InputManager
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ inputManager = context.spyService(InputManager::class.java)
+ doAnswer { inputDeviceIds.toArray() }.whenever(inputManager).inputDeviceIds
+
+ doReturn(null).whenever(inputManager).getInputDevice(eq(1))
+ doReturn(mockDevice(SOURCE_MOUSE or SOURCE_TOUCHPAD))
+ .whenever(inputManager)
+ .getInputDevice(eq(2))
+ doReturn(mockDevice(SOURCE_MOUSE or SOURCE_TOUCHPAD))
+ .whenever(inputManager)
+ .getInputDevice(eq(3))
+ doReturn(mockDevice(SOURCE_MOUSE)).whenever(inputManager).getInputDevice(eq(4))
+ }
+
+ @Test
+ fun `initialize correct devices`() {
+ inputDeviceIds.addAll(IntArray.wrap(1, 2, 3, 4))
+
+ val list = ActiveTrackpadList(context) {}
+ assertEquals(2, list.size())
+ assertTrue(list.contains(2))
+ assertTrue(list.contains(3))
+ }
+
+ @Test
+ fun `update callback not called in constructor`() {
+ inputDeviceIds.addAll(IntArray.wrap(2, 3))
+
+ var updateCalled = false
+ val list = ActiveTrackpadList(context) { updateCalled = true }
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+
+ assertEquals(2, list.size())
+ assertFalse(updateCalled)
+ }
+
+ @Test
+ fun `update called on add only once`() {
+ var updateCalled = false
+ val list = ActiveTrackpadList(context) { updateCalled = true }
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+
+ assertFalse(updateCalled)
+ assertEquals(0, list.size())
+
+ list.onInputDeviceAdded(1)
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+ assertFalse(updateCalled)
+ assertEquals(0, list.size())
+
+ list.onInputDeviceAdded(2)
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+ assertTrue(updateCalled)
+ assertEquals(1, list.size())
+
+ updateCalled = false
+ list.onInputDeviceAdded(3)
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+ assertFalse(updateCalled)
+ assertEquals(2, list.size())
+ }
+
+ @Test
+ fun `update called on remove only once`() {
+ var updateCalled = false
+ inputDeviceIds.addAll(IntArray.wrap(1, 2, 3, 4))
+ val list = ActiveTrackpadList(context) { updateCalled = true }
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+ assertEquals(2, list.size())
+
+ list.onInputDeviceRemoved(2)
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+ assertEquals(1, list.size())
+ assertFalse(updateCalled)
+
+ list.onInputDeviceRemoved(3)
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+ assertEquals(0, list.size())
+ assertTrue(updateCalled)
+ }
+
+ private fun mockDevice(sources: Int) =
+ mock(InputDevice::class.java).apply { doReturn(sources).whenever(this).sources }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
index f57c35f..c215ff2 100644
--- a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
+++ b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
@@ -20,7 +20,6 @@
import android.os.SystemProperties;
-import androidx.annotation.NonNull;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.Until;
@@ -33,7 +32,6 @@
import com.android.quickstep.fallback.RecentsState;
import com.android.quickstep.fallback.window.RecentsWindowManager;
import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.RecentsViewContainer;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
@@ -78,23 +76,12 @@
waitForRecentsWindowCondition(message, condition, TestUtil.DEFAULT_UI_TIMEOUT);
}
- protected <T> T getFromRecentsWindowManager(Function<RecentsWindowManager, T> f) {
+ protected <T> T getFromRecentsWindow(Function<RecentsWindowManager, T> f) {
if (!TestHelpers.isInLauncherProcess()) return null;
- return getOnUiThread(
- () -> f.apply(RecentsWindowManager.getRecentsWindowTracker().getCreatedContext()));
- }
-
- protected void executeOnRecentsWindow(Consumer<RecentsWindowManager> f) {
- getFromRecentsWindowManager(recentsWindow -> {
- f.accept(recentsWindow);
- return null;
- });
- }
-
- protected void executeOnRecentsViewContainerInTearDown(
- @NonNull Consumer<RecentsViewContainer> f) {
- executeOnRecentsWindow(container -> {
- if (container != null) f.accept(container);
+ return getOnUiThread(() -> {
+ RecentsWindowManager recentsWindowManager =
+ RecentsWindowManager.getRecentsWindowTracker().getCreatedContext();
+ return recentsWindowManager != null ? f.apply(recentsWindowManager) : null;
});
}
@@ -104,12 +91,12 @@
String message, Function<RecentsWindowManager, Boolean> condition, long timeout) {
verifyKeyguardInvisible();
if (!TestHelpers.isInLauncherProcess()) return;
- Wait.atMost(message, () -> getFromRecentsWindowManager(condition), mLauncher, timeout);
+ Wait.atMost(message, () -> getFromRecentsWindow(condition), mLauncher, timeout);
}
protected boolean isInRecentsWindowState(Supplier<RecentsState> state) {
if (!TestHelpers.isInLauncherProcess()) return true;
- return getFromRecentsWindowManager(
+ return getFromRecentsWindow(
recentsWindow -> recentsWindow.getStateManager().getState() == state.get());
}
diff --git a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
index c152ee1..76aab39 100644
--- a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
@@ -17,28 +17,34 @@
package com.android.quickstep
import android.content.ComponentName
+import android.content.Context
import android.content.Intent
+import android.content.pm.PackageManager
+import android.platform.test.annotations.EnableFlags
+import android.view.Display.DEFAULT_DISPLAY
import androidx.test.platform.app.InstrumentationRegistry
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.internal.R
import com.android.launcher3.AbstractFloatingView
import com.android.launcher3.AbstractFloatingViewHelper
import com.android.launcher3.Flags.enableRefactorTaskThumbnail
import com.android.launcher3.logging.StatsLogManager
import com.android.launcher3.logging.StatsLogManager.LauncherEvent
import com.android.launcher3.model.data.TaskViewItemInfo
-import com.android.launcher3.uioverrides.QuickstepLauncher
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.launcher3.util.TransformingTouchDelegate
import com.android.quickstep.TaskOverlayFactory.TaskOverlay
import com.android.quickstep.task.thumbnail.TaskThumbnailView
import com.android.quickstep.views.LauncherRecentsView
+import com.android.quickstep.views.RecentsViewContainer
import com.android.quickstep.views.TaskContainer
import com.android.quickstep.views.TaskThumbnailViewDeprecated
import com.android.quickstep.views.TaskView
import com.android.quickstep.views.TaskViewIcon
import com.android.systemui.shared.recents.model.Task
import com.android.systemui.shared.recents.model.Task.TaskKey
+import com.android.window.flags.Flags
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
import com.google.common.truth.Truth.assertThat
@@ -58,7 +64,7 @@
/** Test for [DesktopSystemShortcut] */
class DesktopSystemShortcutTest {
- private val launcher: QuickstepLauncher = mock()
+ private val launcher: RecentsViewContainer = mock()
private val statsLogManager: StatsLogManager = mock()
private val statsLogger: StatsLogManager.StatsLogger = mock()
private val recentsView: LauncherRecentsView = mock()
@@ -67,6 +73,7 @@
private val overlayFactory: TaskOverlayFactory = mock()
private val factory: TaskShortcutFactory =
DesktopSystemShortcut.createFactory(abstractFloatingViewHelper)
+ private val context: Context = spy(InstrumentationRegistry.getInstrumentation().targetContext)
private lateinit var mockitoSession: StaticMockitoSession
@@ -79,6 +86,7 @@
.startMocking()
whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true)
whenever(overlayFactory.createOverlay(any())).thenReturn(mock<TaskOverlay<*>>())
+ whenever(launcher.asContext()).thenReturn(context)
}
@After
@@ -97,6 +105,79 @@
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun createDesktopTaskShortcutFactory_transparentTask() {
+ val baseComponent = ComponentName("", /* class */ "")
+ val taskKey =
+ TaskKey(
+ /* id */ 1,
+ /* windowingMode */ 0,
+ Intent(),
+ baseComponent,
+ /* userId */ 0,
+ /* lastActiveTime */ 2000,
+ DEFAULT_DISPLAY,
+ baseComponent,
+ /* numActivities */ 1,
+ /* isTopActivityNoDisplay */ false,
+ /* isActivityStackTransparent */ true,
+ )
+ val taskContainer = createTaskContainer(Task(taskKey))
+ val shortcuts = factory.getShortcuts(launcher, taskContainer)
+ assertThat(shortcuts).isNull()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun createDesktopTaskShortcutFactory_systemUiTask() {
+ val sysUiPackageName: String = context.resources.getString(R.string.config_systemUi)
+ val baseComponent = ComponentName(sysUiPackageName, /* class */ "")
+ val taskKey =
+ TaskKey(
+ /* id */ 1,
+ /* windowingMode */ 0,
+ Intent(),
+ baseComponent,
+ /* userId */ 0,
+ /* lastActiveTime */ 2000,
+ DEFAULT_DISPLAY,
+ baseComponent,
+ /* numActivities */ 1,
+ /* isTopActivityNoDisplay */ false,
+ /* isActivityStackTransparent */ false,
+ )
+ val taskContainer = createTaskContainer(Task(taskKey))
+ val shortcuts = factory.getShortcuts(launcher, taskContainer)
+ assertThat(shortcuts).isNull()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun createDesktopTaskShortcutFactory_defaultHomeTask() {
+ val packageManager: PackageManager = mock()
+ val homeActivities = ComponentName("defaultHomePackage", /* class */ "")
+ whenever(context.packageManager).thenReturn(packageManager)
+ whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
+ val taskKey =
+ TaskKey(
+ /* id */ 1,
+ /* windowingMode */ 0,
+ Intent(),
+ homeActivities,
+ /* userId */ 0,
+ /* lastActiveTime */ 2000,
+ DEFAULT_DISPLAY,
+ homeActivities,
+ /* numActivities */ 1,
+ /* isTopActivityNoDisplay */ false,
+ /* isActivityStackTransparent */ false,
+ )
+ val taskContainer = createTaskContainer(Task(taskKey).apply { isDockable = true })
+ val shortcuts = factory.getShortcuts(launcher, taskContainer)
+ assertThat(shortcuts).isNull()
+ }
+
+ @Test
fun createDesktopTaskShortcutFactory_undockable() {
val unDockableTask = createTask().apply { isDockable = false }
val taskContainer = createTaskContainer(unDockableTask)
@@ -114,8 +195,7 @@
whenever(launcher.statsLogManager).thenReturn(statsLogManager)
whenever(statsLogManager.logger()).thenReturn(statsLogger)
whenever(statsLogger.withItemInfo(any())).thenReturn(statsLogger)
- whenever(taskView.context)
- .thenReturn(InstrumentationRegistry.getInstrumentation().targetContext)
+ whenever(taskView.context).thenReturn(context)
whenever(recentsView.moveTaskToDesktop(any(), any(), any())).thenAnswer {
val successCallback = it.getArgument<Runnable>(2)
successCallback.run()
@@ -145,7 +225,22 @@
}
private fun createTask() =
- Task(TaskKey(1, 0, Intent(), ComponentName("", ""), 0, 2000)).apply { isDockable = true }
+ Task(
+ TaskKey(
+ /* id */ 1,
+ /* windowingMode */ 0,
+ Intent(),
+ ComponentName("", ""),
+ /* userId */ 0,
+ /* lastActiveTime */ 2000,
+ DEFAULT_DISPLAY,
+ ComponentName("", ""),
+ /* numActivities */ 1,
+ /* isTopActivityNoDisplay */ false,
+ /* isActivityStackTransparent */ false,
+ )
+ )
+ .apply { isDockable = true }
private fun createTaskContainer(task: Task) =
TaskContainer(
diff --git a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
index f923142..c78fe1c 100644
--- a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
+++ b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
@@ -21,6 +21,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import android.app.PendingIntent;
@@ -93,7 +94,8 @@
final TaskView task = getOnceNotNull("No latest task", launcher -> getLatestTask(launcher));
return getFromLauncher(launcher -> {
- TaskContainer taskContainer = task.getTaskContainers().get(0);
+ TaskContainer taskContainer = task.getFirstTaskContainer();
+ assertNotNull(taskContainer);
assertTrue("Latest task is not Calculator", calculatorPackage.equals(
taskContainer.getTask().getTopComponent().getPackageName()));
return taskContainer.getDigitalWellBeingToast();
diff --git a/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt b/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt
index 9c2c13c..818841a 100644
--- a/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt
@@ -17,23 +17,28 @@
package com.android.quickstep
import android.content.ComponentName
+import android.content.Context
import android.content.Intent
+import android.content.pm.PackageManager
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.test.platform.app.InstrumentationRegistry
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.internal.R
import com.android.launcher3.AbstractFloatingView
import com.android.launcher3.AbstractFloatingViewHelper
import com.android.launcher3.Flags.enableRefactorTaskThumbnail
import com.android.launcher3.logging.StatsLogManager
import com.android.launcher3.logging.StatsLogManager.LauncherEvent
import com.android.launcher3.model.data.TaskViewItemInfo
-import com.android.launcher3.uioverrides.QuickstepLauncher
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.launcher3.util.TransformingTouchDelegate
import com.android.quickstep.TaskOverlayFactory.TaskOverlay
import com.android.quickstep.task.thumbnail.TaskThumbnailView
import com.android.quickstep.views.LauncherRecentsView
+import com.android.quickstep.views.RecentsViewContainer
import com.android.quickstep.views.TaskContainer
import com.android.quickstep.views.TaskThumbnailViewDeprecated
import com.android.quickstep.views.TaskView
@@ -62,7 +67,7 @@
@get:Rule val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT)
- private val launcher: QuickstepLauncher = mock()
+ private val launcher: RecentsViewContainer = mock()
private val statsLogManager: StatsLogManager = mock()
private val statsLogger: StatsLogManager.StatsLogger = mock()
private val recentsView: LauncherRecentsView = mock()
@@ -71,6 +76,7 @@
private val overlayFactory: TaskOverlayFactory = mock()
private val factory: TaskShortcutFactory =
ExternalDisplaySystemShortcut.createFactory(abstractFloatingViewHelper)
+ private val context: Context = spy(InstrumentationRegistry.getInstrumentation().targetContext)
private lateinit var mockitoSession: StaticMockitoSession
@@ -83,6 +89,7 @@
.startMocking()
whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true)
whenever(overlayFactory.createOverlay(any())).thenReturn(mock<TaskOverlay<*>>())
+ whenever(launcher.asContext()).thenReturn(context)
}
@After
@@ -102,6 +109,88 @@
}
@Test
+ @EnableFlags(
+ Flags.FLAG_MOVE_TO_EXTERNAL_DISPLAY_SHORTCUT,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+ )
+ fun createExternalDisplayTaskShortcut_transparentTask() {
+ val baseComponent = ComponentName("", /* class */ "")
+ val taskKey =
+ TaskKey(
+ /* id */ 1,
+ /* windowingMode */ 0,
+ Intent(),
+ baseComponent,
+ /* userId */ 0,
+ /* lastActiveTime */ 2000,
+ DEFAULT_DISPLAY,
+ baseComponent,
+ /* numActivities */ 1,
+ /* isTopActivityNoDisplay */ false,
+ /* isActivityStackTransparent */ true,
+ )
+ val taskContainer = createTaskContainer(Task(taskKey))
+ val shortcuts = factory.getShortcuts(launcher, taskContainer)
+ assertThat(shortcuts).isNull()
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_MOVE_TO_EXTERNAL_DISPLAY_SHORTCUT,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+ )
+ fun createExternalDisplayTaskShortcut_systemUiTask() {
+ val sysUiPackageName: String = context.resources.getString(R.string.config_systemUi)
+ val baseComponent = ComponentName(sysUiPackageName, /* class */ "")
+ val taskKey =
+ TaskKey(
+ /* id */ 1,
+ /* windowingMode */ 0,
+ Intent(),
+ baseComponent,
+ /* userId */ 0,
+ /* lastActiveTime */ 2000,
+ DEFAULT_DISPLAY,
+ baseComponent,
+ /* numActivities */ 1,
+ /* isTopActivityNoDisplay */ false,
+ /* isActivityStackTransparent */ false,
+ )
+ val taskContainer = createTaskContainer(Task(taskKey))
+ val shortcuts = factory.getShortcuts(launcher, taskContainer)
+ assertThat(shortcuts).isNull()
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_MOVE_TO_EXTERNAL_DISPLAY_SHORTCUT,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+ )
+ fun createExternalDisplayTaskShortcut_defaultHomeTask() {
+ val packageManager: PackageManager = mock()
+ val homeActivities = ComponentName("defaultHomePackage", /* class */ "")
+ whenever(context.packageManager).thenReturn(packageManager)
+ whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
+ val taskKey =
+ TaskKey(
+ /* id */ 1,
+ /* windowingMode */ 0,
+ Intent(),
+ homeActivities,
+ /* userId */ 0,
+ /* lastActiveTime */ 2000,
+ DEFAULT_DISPLAY,
+ homeActivities,
+ /* numActivities */ 1,
+ /* isTopActivityNoDisplay */ false,
+ /* isActivityStackTransparent */ false,
+ )
+ val taskContainer = createTaskContainer(Task(taskKey).apply { isDockable = true })
+ val shortcuts = factory.getShortcuts(launcher, taskContainer)
+ assertThat(shortcuts).isNull()
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_MOVE_TO_EXTERNAL_DISPLAY_SHORTCUT)
fun externalDisplaySystemShortcutClicked() {
val task = createTask()
@@ -134,7 +223,22 @@
verify(statsLogger).log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_EXTERNAL_DISPLAY_TAP)
}
- private fun createTask() = Task(TaskKey(1, 0, Intent(), ComponentName("", ""), 0, 2000))
+ private fun createTask() =
+ Task(
+ TaskKey(
+ /* id */ 1,
+ /* windowingMode */ 0,
+ Intent(),
+ ComponentName("", ""),
+ /* userId */ 0,
+ /* lastActiveTime */ 2000,
+ DEFAULT_DISPLAY,
+ ComponentName("", ""),
+ /* numActivities */ 1,
+ /* isTopActivityNoDisplay */ false,
+ /* isActivityStackTransparent */ false,
+ )
+ )
private fun createTaskContainer(task: Task) =
TaskContainer(
diff --git a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
index ff0ad53..a0ec635 100644
--- a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
+++ b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
@@ -43,6 +43,7 @@
import androidx.test.filters.SmallTest;
import com.android.launcher3.testing.shared.ResourceUtils;
+import com.android.launcher3.util.DaggerSingletonTracker;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.RotationUtils;
import com.android.launcher3.util.WindowBounds;
@@ -301,7 +302,8 @@
final DisplayController displayController = mock(DisplayController.class);
doReturn(mInfo).when(displayController).getInfo();
final SimpleOrientationTouchTransformer transformer =
- new SimpleOrientationTouchTransformer(getApplicationContext(), displayController);
+ new SimpleOrientationTouchTransformer(getApplicationContext(), displayController,
+ mock(DaggerSingletonTracker.class));
final MotionEvent move1 = generateMotionEvent(MotionEvent.ACTION_MOVE, 100, 10);
transformer.transform(move1, Surface.ROTATION_90);
// The position is transformed to 90 degree.
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 15038a4..e0560e2 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -55,7 +55,6 @@
import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
import com.android.quickstep.fallback.RecentsState;
import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.RecentsViewContainer;
import org.junit.After;
import org.junit.Before;
@@ -64,6 +63,7 @@
import org.junit.runner.RunWith;
import java.util.function.Consumer;
+import java.util.function.Function;
@LargeTest
@RunWith(AndroidJUnit4.class)
@@ -91,17 +91,16 @@
@Before
public void setUp() throws Exception {
super.setUp();
- executeOnRecentsViewContainer(container -> {
- RecentsView recentsView = container.getOverviewPanel();
- recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(true);
- });
+ runOnRecentsView(recentsView ->
+ recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(true));
}
@After
public void tearDown() {
- executeOnRecentsViewContainerInTearDown(container -> {
- RecentsView recentsView = container.getOverviewPanel();
- recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(false);
+ runOnRecentsView(recentsView -> {
+ if (recentsView != null) {
+ recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(false);
+ }
});
}
@@ -111,14 +110,6 @@
startTestActivity(2);
}
- private void startTestAppsWithCheck() throws Exception {
- startTestApps();
- executeOnLauncher(launcher -> assertTrue(
- "Launcher activity is the top activity; expecting another activity to be the top "
- + "one",
- isInLaunchedApp(launcher)));
- }
-
@Test
@NavigationModeSwitch
@PortraitLandscape
@@ -137,25 +128,24 @@
Overview overview = mLauncher.goHome().switchToOverview();
assertIsInState(
"Launcher internal state didn't switch to Overview", ExpectedState.OVERVIEW);
- executeOnRecentsViewContainer(container -> assertTrue(
- "Don't have at least 3 tasks", getTaskCount(container) >= 3));
+ runOnRecentsView(recentsView -> assertTrue("Don't have at least 3 tasks",
+ recentsView.getTaskViewCount() >= 3));
// Test flinging forward and backward.
- executeOnRecentsViewContainer(container -> assertEquals("Current task in Overview is not 0",
- 0, getCurrentOverviewPage(container)));
+ runOnRecentsView(recentsView -> assertEquals("Current task in Overview is not 0",
+ 0, recentsView.getCurrentPage()));
overview.flingForward();
assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW);
- final Integer currentTaskAfterFlingForward = getFromLauncher(
- launcher -> getCurrentOverviewPage(launcher));
- executeOnRecentsViewContainer(container -> assertTrue(
- "Current task in Overview is still 0", currentTaskAfterFlingForward > 0));
+ final Integer currentTaskAfterFlingForward =
+ getFromRecentsView(RecentsView::getCurrentPage);
+ runOnRecentsView(recentsView -> assertTrue("Current task in Overview is still 0",
+ currentTaskAfterFlingForward > 0));
overview.flingBackward();
assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW);
- executeOnRecentsViewContainer(container -> assertTrue(
- "Flinging back in Overview did nothing",
- getCurrentOverviewPage(container) < currentTaskAfterFlingForward));
+ runOnRecentsView(recentsView -> assertTrue("Flinging back in Overview did nothing",
+ recentsView.getCurrentPage() < currentTaskAfterFlingForward));
// Test opening a task.
OverviewTask task = mLauncher.goHome().switchToOverview().getCurrentTask();
@@ -164,29 +154,25 @@
assertTrue("Test activity didn't open from Overview", mDevice.wait(Until.hasObject(
By.pkg(getAppPackageName()).text("TestActivity2")),
TestUtil.DEFAULT_UI_TIMEOUT));
- executeOnLauncher(launcher -> assertTrue(
- "Launcher activity is the top activity; expecting another activity to be the top "
- + "one",
- isInLaunchedApp(launcher)));
+ expectLaunchedAppState();
// Test dismissing a task.
overview = mLauncher.goHome().switchToOverview();
- assertIsInState(
- "Launcher internal state didn't switch to Overview", ExpectedState.OVERVIEW);
- final Integer numTasks = getFromLauncher(launcher -> getTaskCount(launcher));
+ assertIsInState("Launcher internal state didn't switch to Overview",
+ ExpectedState.OVERVIEW);
+ final Integer numTasks = getFromRecentsView(RecentsView::getTaskViewCount);
task = overview.getCurrentTask();
assertNotNull("overview.getCurrentTask() returned null (2)", task);
task.dismiss();
- executeOnRecentsViewContainer(
- container -> assertEquals("Dismissing a task didn't remove 1 task from Overview",
- numTasks - 1, getTaskCount(container)));
+ runOnRecentsView(recentsView -> assertEquals(
+ "Dismissing a task didn't remove 1 task from Overview",
+ numTasks - 1, recentsView.getTaskViewCount()));
// Test dismissing all tasks.
mLauncher.goHome().switchToOverview().dismissAllTasks();
assertIsInState("Launcher internal state is not Home", ExpectedState.HOME);
- executeOnRecentsViewContainer(
- container -> assertEquals("Still have tasks after dismissing all",
- 0, getTaskCount(container)));
+ runOnRecentsView(recentsView -> assertEquals("Still have tasks after dismissing all",
+ 0, recentsView.getTaskViewCount()));
}
/**
@@ -263,26 +249,6 @@
overview.launchFocusedTaskByEnterKey(CALCULATOR_APP_PACKAGE); // Assert app is focused.
}
- private RecentsView getOverviewPanel(RecentsViewContainer recentsViewContainer) {
- return recentsViewContainer.getOverviewPanel();
- }
-
- private int getCurrentOverviewPage(RecentsViewContainer recentsViewContainer) {
- return getOverviewPanel(recentsViewContainer).getCurrentPage();
- }
-
- private int getTaskCount(RecentsViewContainer recentsViewContainer) {
- return getOverviewPanel(recentsViewContainer).getTaskViewCount();
- }
-
- private int getTopRowTaskCountForTablet(RecentsViewContainer recentsViewContainer) {
- return getOverviewPanel(recentsViewContainer).getTopRowTaskCountForTablet();
- }
-
- private int getBottomRowTaskCountForTablet(RecentsViewContainer recentsViewContainer) {
- return getOverviewPanel(recentsViewContainer).getBottomRowTaskCountForTablet();
- }
-
@Test
@NavigationModeSwitch
@PortraitLandscape
@@ -320,19 +286,6 @@
"Launcher internal state didn't switch to Overview", ExpectedState.OVERVIEW);
}
- private void quickSwitchToPreviousAppAndAssert(boolean toRight) {
- final LaunchedAppState launchedAppState = getAndAssertLaunchedApp();
- if (toRight) {
- launchedAppState.quickSwitchToPreviousApp();
- } else {
- launchedAppState.quickSwitchToPreviousAppSwipeLeft();
- }
-
- // While enable shell transition, Launcher can be resumed due to transient launch.
- waitForLauncherCondition("Launcher shouldn't stay in resume forever",
- this::isInLaunchedApp, 3000 /* timeout */);
- }
-
@Test
@NavigationModeSwitch
@PortraitLandscape
@@ -403,11 +356,6 @@
}
}
- private boolean isHardwareKeyboard() {
- return Configuration.KEYBOARD_QWERTY
- == mTargetContext.getResources().getConfiguration().keyboard;
- }
-
@Test
@NavigationModeSwitch
@PortraitLandscape
@@ -447,15 +395,14 @@
}
Overview overview = mLauncher.goHome().switchToOverview();
- executeOnRecentsViewContainer(
- container -> assertTrue("Don't have at least 13 tasks",
- getTaskCount(container) >= 13));
+ runOnRecentsView(recentsView -> assertTrue("Don't have at least 13 tasks",
+ recentsView.getTaskViewCount() >= 13));
// Test scroll the first task off screen
overview.scrollCurrentTaskOffScreen();
assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW);
- executeOnRecentsViewContainer(container -> assertTrue("Current task in Overview is still 0",
- getCurrentOverviewPage(container) > 0));
+ runOnRecentsView(recentsView -> assertTrue("Current task in Overview is still 0",
+ recentsView.getCurrentPage() > 0));
// Test opening the task.
overview.getCurrentTask().open();
@@ -470,19 +417,18 @@
overview.scrollCurrentTaskOffScreen();
assertIsInState(
"Launcher internal state is not Overview", ExpectedState.OVERVIEW);
- executeOnRecentsViewContainer(container -> assertTrue("Current task in Overview is still 0",
- getCurrentOverviewPage(container) > 0));
+ runOnRecentsView(recentsView -> assertTrue("Current task in Overview is still 0",
+ recentsView.getCurrentPage() > 0));
// Test dismissing the later task.
- final Integer numTasks = getFromLauncher(this::getTaskCount);
+ final Integer numTasks = getFromRecentsView(RecentsView::getTaskViewCount);
overview.getCurrentTask().dismiss();
- executeOnRecentsViewContainer(
- container -> assertEquals("Dismissing a task didn't remove 1 task from Overview",
- numTasks - 1, getTaskCount(container)));
- executeOnRecentsViewContainer(container -> assertTrue(
- "Grid did not rebalance after dismissal",
- (Math.abs(getTopRowTaskCountForTablet(container)
- - getBottomRowTaskCountForTablet(container)) <= 1)));
+ runOnRecentsView(recentsView -> assertEquals(
+ "Dismissing a task didn't remove 1 task from Overview",
+ numTasks - 1, recentsView.getTaskViewCount()));
+ runOnRecentsView(recentsView -> assertTrue("Grid did not rebalance after dismissal",
+ (Math.abs(recentsView.getTopRowTaskCountForTablet()
+ - recentsView.getBottomRowTaskCountForTablet()) <= 1)));
// TODO(b/308841019): Re-enable after fixing Overview jank when dismiss
// // Test dismissing more tasks.
@@ -492,17 +438,16 @@
// assertIsInState(
// "Launcher internal state didn't remain in Overview", ExpectedState.OVERVIEW);
// overview.getCurrentTask().dismiss();
-// executeOnRecentsViewContainer(container -> assertTrue(
-// "Grid did not rebalance after multiple dismissals",
-// (Math.abs(getTopRowTaskCountForTablet(container)
-// - getBottomRowTaskCountForTablet(container)) <= 1)));
+// runOnRecentsView(recentsView -> assertTrue(
+// "Grid did not rebalance after multiple dismissals",
+// (Math.abs(recentsView.getTopRowTaskCountForTablet()
+// - recentsView.getBottomRowTaskCountForTablet()) <= 1)));
// Test dismissing all tasks.
mLauncher.goHome().switchToOverview().dismissAllTasks();
assertIsInState("Launcher internal state is not Home", ExpectedState.HOME);
- executeOnRecentsViewContainer(
- container -> assertEquals("Still have tasks after dismissing all",
- 0, getTaskCount(container)));
+ runOnRecentsView(recentsView -> assertEquals("Still have tasks after dismissing all",
+ 0, recentsView.getTaskViewCount()));
}
@Test
@@ -512,9 +457,8 @@
Overview overview = mLauncher.goHome().switchToOverview();
assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW);
- executeOnRecentsViewContainer(
- container -> assertTrue("Should have at least 3 tasks",
- getTaskCount(container) >= 3));
+ runOnRecentsView(recentsView -> assertTrue("Should have at least 3 tasks",
+ recentsView.getTaskViewCount() >= 3));
// It should not dismiss overview when tapping between tasks
overview.touchBetweenTasks();
@@ -536,9 +480,8 @@
Overview overview = mLauncher.goHome().switchToOverview();
assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW);
- executeOnRecentsViewContainer(
- container -> assertTrue("Should have at least 3 tasks",
- getTaskCount(container) >= 3));
+ runOnRecentsView(recentsView -> assertTrue("Should have at least 3 tasks",
+ recentsView.getTaskViewCount() >= 3));
if (mLauncher.isTransientTaskbar()) {
// On transient taskbar, it should dismiss when tapping outside taskbar bounds.
@@ -596,6 +539,29 @@
}
}
+ private void startTestAppsWithCheck() throws Exception {
+ startTestApps();
+ expectLaunchedAppState();
+ }
+
+ private void quickSwitchToPreviousAppAndAssert(boolean toRight) {
+ final LaunchedAppState launchedAppState = getAndAssertLaunchedApp();
+ if (toRight) {
+ launchedAppState.quickSwitchToPreviousApp();
+ } else {
+ launchedAppState.quickSwitchToPreviousAppSwipeLeft();
+ }
+
+ // While enable shell transition, Launcher can be resumed due to transient launch.
+ waitForLauncherCondition("Launcher shouldn't stay in resume forever",
+ this::isInLaunchedApp, 3000 /* timeout */);
+ }
+
+ private boolean isHardwareKeyboard() {
+ return Configuration.KEYBOARD_QWERTY
+ == mTargetContext.getResources().getConfiguration().keyboard;
+ }
+
private void assertIsInState(
@NonNull String failureMessage, @NonNull ExpectedState expectedState) {
assertTrue(failureMessage, enableLauncherOverviewInWindow()
@@ -612,11 +578,26 @@
}
}
- private void executeOnRecentsViewContainer(@NonNull Consumer<RecentsViewContainer> f) {
+ private void expectLaunchedAppState() {
+ executeOnLauncher(launcher -> assertTrue(
+ "Launcher activity is the top activity; expecting another activity to be the top "
+ + "one",
+ isInLaunchedApp(launcher)));
+ }
+
+ private <T> T getFromRecentsView(Function<RecentsView, T> f) {
if (enableLauncherOverviewInWindow()) {
- executeOnRecentsWindow(f::accept);
+ return getFromRecentsWindow(
+ recentsWindowManager -> f.apply(recentsWindowManager.getOverviewPanel()));
} else {
- executeOnLauncher(f::accept);
+ return getFromLauncher(launcher -> f.apply(launcher.getOverviewPanel()));
}
}
+
+ private void runOnRecentsView(Consumer<RecentsView> f) {
+ getFromRecentsView(recentsView -> {
+ f.accept(recentsView);
+ return null;
+ });
+ }
}
diff --git a/quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchTransitionManagerTest.kt b/quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchTransitionManagerTest.kt
index ae96c09c..9ca9fe4 100644
--- a/quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchTransitionManagerTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchTransitionManagerTest.kt
@@ -29,7 +29,6 @@
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.quickstep.SystemUiProxy
-import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.google.common.truth.Truth.assertThat
@@ -75,10 +74,7 @@
}
@Test
- @EnableFlags(
- FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
- FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX,
- )
+ @EnableFlags(FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
fun registerTransitions_appLaunchFlagEnabled_registersTransition() {
transitionManager.registerTransitions()
@@ -86,10 +82,7 @@
}
@Test
- @DisableFlags(
- FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
- FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX,
- )
+ @DisableFlags(FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
fun registerTransitions_appLaunchFlagDisabled_doesntRegisterTransition() {
transitionManager.registerTransitions()
@@ -97,10 +90,7 @@
}
@Test
- @EnableFlags(
- FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
- FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX,
- )
+ @EnableFlags(FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
fun registerTransitions_usesCorrectFilter() {
transitionManager.registerTransitions()
val filterArgumentCaptor = argumentCaptor<TransitionFilter>()
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index b51feb5..f242f1e 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -46,7 +46,7 @@
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d lai ja %2$d kõrge"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"Vidin <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Vidin <xliff:g id="WIDGET_NAME">%1$s</xliff:g>, %2$d lai ja %3$d kõrge"</string>
- <string name="add_item_request_drag_hint" msgid="8730547755622776606">"Vidina teisaldamiseks avakuval puudutage vidinat pikalt"</string>
+ <string name="add_item_request_drag_hint" msgid="8730547755622776606">"Vidina teisaldamiseks avakuval puudutage vidinat pikalt."</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Lisa avakuvale"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Vidin <xliff:g id="WIDGET_NAME">%1$s</xliff:g> lisati avakuvale"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Soovitused"</string>
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 315096c..d3684b2 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -29,6 +29,7 @@
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INCREMENTAL_DOWNLOAD_ACTIVE;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -63,6 +64,7 @@
import android.widget.TextView;
import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting;
@@ -366,11 +368,6 @@
mDotScaleAnim.start();
}
- @UiThread
- public void applyFromWorkspaceItem(WorkspaceItemInfo info) {
- applyFromWorkspaceItem(info, null);
- }
-
@Override
public void setAccessibilityDelegate(AccessibilityDelegate delegate) {
if (delegate instanceof BaseAccessibilityDelegate) {
@@ -384,10 +381,10 @@
}
@UiThread
- public void applyFromWorkspaceItem(WorkspaceItemInfo info, PreloadIconDrawable icon) {
+ public void applyFromWorkspaceItem(WorkspaceItemInfo info) {
applyIconAndLabel(info);
setItemInfo(info);
- applyLoadingState(icon);
+
applyDotState(info, false /* animate */);
setDownloadStateContentDescription(info, info.getProgressLevel());
}
@@ -395,17 +392,11 @@
@UiThread
public void applyFromApplicationInfo(AppInfo info) {
applyIconAndLabel(info);
-
- // We don't need to check the info since it's not a WorkspaceItemInfo
setItemInfo(info);
-
// Verify high res immediately
verifyHighRes();
- if ((info.runtimeStatusFlags & ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0) {
- applyProgressLevel();
- }
applyDotState(info, false /* animate */);
setDownloadStateContentDescription(info, info.getProgressLevel());
}
@@ -449,6 +440,50 @@
@VisibleForTesting
@UiThread
public void applyIconAndLabel(ItemInfoWithIcon info) {
+ FastBitmapDrawable oldIcon = mIcon;
+ if (!canReuseIcon(info)) {
+ setNonPendingIcon(info);
+ }
+ applyLabel(info);
+ maybeApplyProgressLevel(info, oldIcon);
+ }
+
+ /**
+ * Check if we can reuse icon so that any animation is preserved
+ */
+ private boolean canReuseIcon(ItemInfoWithIcon info) {
+ return mIcon instanceof PreloadIconDrawable p
+ && p.hasNotCompleted() && p.isSameInfo(info.bitmap);
+ }
+
+ /**
+ * Apply progress level to the icon if necessary
+ */
+ private void maybeApplyProgressLevel(ItemInfoWithIcon info, FastBitmapDrawable oldIcon) {
+ if (!shouldApplyProgressLevel(info, oldIcon)) {
+ return;
+ }
+ PreloadIconDrawable pendingIcon = applyProgressLevel(info);
+ boolean isNoLongerPending = info instanceof WorkspaceItemInfo wii
+ ? !wii.hasPromiseIconUi() : !info.isArchived();
+ if (isNoLongerPending && info.getProgressLevel() == 100 && pendingIcon != null) {
+ pendingIcon.maybePerformFinishedAnimation(
+ (oldIcon instanceof PreloadIconDrawable p) ? p : pendingIcon,
+ () -> setNonPendingIcon(
+ (getTag() instanceof ItemInfoWithIcon iiwi) ? iiwi : info));
+ }
+ }
+
+ /**
+ * Check if progress level should be applied to the icon
+ */
+ private boolean shouldApplyProgressLevel(ItemInfoWithIcon info, FastBitmapDrawable oldIcon) {
+ return (info.runtimeStatusFlags & FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0
+ || (info instanceof WorkspaceItemInfo wii && wii.hasPromiseIconUi())
+ || (oldIcon instanceof PreloadIconDrawable p && p.hasNotCompleted());
+ }
+
+ private void setNonPendingIcon(ItemInfoWithIcon info) {
ThemeManager themeManager = ThemeManager.INSTANCE.get(getContext());
int flags = (shouldUseTheme()
&& themeManager.isMonoThemeEnabled()) ? FLAG_THEMED : 0;
@@ -463,7 +498,6 @@
mDotParams.appColor = iconDrawable.getIconColor();
mDotParams.dotColor = Themes.getAttrColor(getContext(), R.attr.notificationDotColor);
setIcon(iconDrawable);
- applyLabel(info);
}
protected boolean shouldUseTheme() {
@@ -1070,38 +1104,10 @@
mLongPressHelper.cancelLongPress();
}
- /**
- * Applies the loading progress value to the progress bar.
- *
- * If this app is installing, the progress bar will be updated with the installation progress.
- * If this app is installed and downloading incrementally, the progress bar will be updated
- * with the total download progress.
- */
- public void applyLoadingState(PreloadIconDrawable icon) {
- if (getTag() instanceof ItemInfoWithIcon) {
- WorkspaceItemInfo info = (WorkspaceItemInfo) getTag();
- if ((info.runtimeStatusFlags & FLAG_INCREMENTAL_DOWNLOAD_ACTIVE) != 0
- || info.hasPromiseIconUi()
- || (info.runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0
- || (icon != null)) {
- updateProgressBarUi(info.getProgressLevel() == 100 ? icon : null);
- }
- }
- }
-
- private void updateProgressBarUi(PreloadIconDrawable oldIcon) {
- FastBitmapDrawable originalIcon = mIcon;
- PreloadIconDrawable preloadDrawable = applyProgressLevel();
- if (preloadDrawable != null && oldIcon != null) {
- preloadDrawable.maybePerformFinishedAnimation(oldIcon, () -> setIcon(originalIcon));
- }
- }
-
/** Applies the given progress level to the this icon's progress bar. */
@Nullable
- public PreloadIconDrawable applyProgressLevel() {
- if (!(getTag() instanceof ItemInfoWithIcon info)
- || ((ItemInfoWithIcon) getTag()).isInactiveArchive()) {
+ private PreloadIconDrawable applyProgressLevel(ItemInfoWithIcon info) {
+ if (info.isInactiveArchive()) {
return null;
}
@@ -1115,23 +1121,16 @@
setContentDescription(getContext()
.getString(R.string.app_waiting_download_title, info.title));
}
- if (mIcon != null) {
- PreloadIconDrawable preloadIconDrawable;
- if (mIcon instanceof PreloadIconDrawable) {
- preloadIconDrawable = (PreloadIconDrawable) mIcon;
- preloadIconDrawable.setLevel(progressLevel);
- preloadIconDrawable.setIsDisabled(isIconDisabled(info));
- } else {
- preloadIconDrawable = makePreloadIcon();
- setIcon(preloadIconDrawable);
- if (info.isArchived() && Flags.useNewIconForArchivedApps()) {
- // reapply text without cloud icon as soon as unarchiving is triggered
- applyLabel(info);
- }
- }
- return preloadIconDrawable;
+ PreloadIconDrawable pid;
+ if (mIcon instanceof PreloadIconDrawable p) {
+ pid = p;
+ pid.setLevel(progressLevel);
+ pid.setIsDisabled(isIconDisabled(info));
+ } else {
+ pid = makePreloadIcon(info);
+ setIcon(pid);
}
- return null;
+ return pid;
}
/**
@@ -1140,11 +1139,11 @@
*/
@Nullable
public PreloadIconDrawable makePreloadIcon() {
- if (!(getTag() instanceof ItemInfoWithIcon)) {
- return null;
- }
+ return getTag() instanceof ItemInfoWithIcon info ? makePreloadIcon(info) : null;
+ }
- ItemInfoWithIcon info = (ItemInfoWithIcon) getTag();
+ @NonNull
+ private PreloadIconDrawable makePreloadIcon(ItemInfoWithIcon info) {
int progressLevel = info.getProgressLevel();
final PreloadIconDrawable preloadDrawable = newPendingIcon(getContext(), info);
@@ -1163,7 +1162,7 @@
public void applyDotState(ItemInfo itemInfo, boolean animate) {
- if (mIcon instanceof FastBitmapDrawable) {
+ if (mIcon != null) {
boolean wasDotted = mDotInfo != null;
mDotInfo = mActivity.getDotInfoForItem(itemInfo);
boolean isDotted = mDotInfo != null;
@@ -1212,7 +1211,7 @@
setContentDescription(getContext().getString(
R.string.app_archived_title, info.title));
}
- } else if ((info.runtimeStatusFlags & ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK)
+ } else if ((info.runtimeStatusFlags & FLAG_SHOW_DOWNLOAD_PROGRESS_MASK)
!= 0) {
String percentageString = NumberFormat.getPercentInstance()
.format(progressLevel * 0.01);
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index c38444c..257f911 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -16,6 +16,7 @@
package com.android.launcher3;
+import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.dragndrop.DraggableView.DRAGGABLE_ICON;
import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_REORDER_PREVIEW_OFFSET;
@@ -47,6 +48,7 @@
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
@@ -69,6 +71,7 @@
import com.android.launcher3.folder.PreviewBackground;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.util.CellAndSpan;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.MSDLPlayerWrapper;
@@ -170,6 +173,7 @@
private boolean mDragging = false;
public boolean mHasOnLayoutBeenCalled = false;
+ private boolean mPlayDragHaptics = false;
private final TimeInterpolator mEaseOutInterpolator;
protected final ShortcutAndWidgetContainer mShortcutsAndWidgets;
@@ -785,6 +789,22 @@
}
mShortcutsAndWidgets.addView(child, index, lp);
+ // Whenever an app is added, if Accessibility service is enabled, focus on that app.
+ if (mActivity instanceof Launcher) {
+ Launcher.cast(mActivity).getStateManager().addStateListener(
+ new StateManager.StateListener<LauncherState>() {
+ @Override
+ public void onStateTransitionComplete(LauncherState finalState) {
+ if (finalState == NORMAL) {
+ child.performAccessibilityAction(
+ AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+ Launcher.cast(mActivity).getStateManager()
+ .removeStateListener(this);
+ }
+ }
+ });
+ }
+
if (markCells) markCellsAsOccupiedForView(child);
return true;
@@ -1164,7 +1184,8 @@
DropTarget.DragObject dragObject) {
if (mDragCell[0] != cellX || mDragCell[1] != cellY || mDragCellSpan[0] != spanX
|| mDragCellSpan[1] != spanY) {
- if (Flags.msdlFeedback()) {
+ determineIfDragHapticsPlay();
+ if (mPlayDragHaptics && Flags.msdlFeedback()) {
mMSDLPlayerWrapper.playToken(MSDLToken.DRAG_INDICATOR_DISCRETE);
}
mDragCell[0] = cellX;
@@ -1192,6 +1213,14 @@
}
}
+ private void determineIfDragHapticsPlay() {
+ if (mDragCell[0] != -1 || mDragCell[1] != -1
+ || mDragCellSpan[0] != -1 || mDragCellSpan[1] != -1) {
+ // The nearest cell is known and we can play haptics
+ mPlayDragHaptics = true;
+ }
+ }
+
@SuppressLint("StringFormatMatches")
public String getItemMoveDescription(int cellX, int cellY) {
if (mContainerType == HOTSEAT) {
@@ -1793,6 +1822,7 @@
* @param child The child that is being dropped
*/
void onDropChild(View child) {
+ mPlayDragHaptics = false;
if (child != null) {
CellLayoutLayoutParams
lp = (CellLayoutLayoutParams) child.getLayoutParams();
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 9f47da7..813d8f1 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -25,7 +25,6 @@
import static com.android.launcher3.InvariantDeviceProfile.INDEX_TWO_PANEL_PORTRAIT;
import static com.android.launcher3.Utilities.dpiFromPx;
import static com.android.launcher3.Utilities.pxFromSp;
-import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
import static com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURCE_HANDLE;
import static com.android.launcher3.testing.shared.ResourceUtils.pxFromDp;
@@ -52,6 +51,7 @@
import com.android.launcher3.CellLayout.ContainerType;
import com.android.launcher3.DevicePaddings.DevicePadding;
+import com.android.launcher3.folder.ClippedFolderIconLayoutRule;
import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.icons.IconNormalizer;
@@ -873,7 +873,7 @@
if (renderer == null) {
renderer = new DotRenderer(
size,
- IconShape.INSTANCE.get(context).getShapeOverridePath(DEFAULT_DOT_SIZE),
+ IconShape.INSTANCE.get(context).getShape().getPath(DEFAULT_DOT_SIZE),
DEFAULT_DOT_SIZE);
cache.put(size, renderer);
}
@@ -1228,7 +1228,7 @@
}
private int getIconSizeWithOverlap(int iconSize) {
- return (int) Math.ceil(iconSize * ICON_OVERLAP_FACTOR);
+ return (int) Math.ceil(iconSize * ClippedFolderIconLayoutRule.getIconOverlapFactor());
}
/**
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 7df4014..647d2ad 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -277,11 +277,11 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;
@@ -2598,25 +2598,12 @@
mModelCallbacks.bindIncrementalDownloadProgressUpdated(app);
}
- @Override
- public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets) {
- mModelCallbacks.bindWidgetsRestored(widgets);
- }
-
/**
* See {@code LauncherBindingDelegate}
*/
@Override
- public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) {
- mModelCallbacks.bindWorkspaceItemsChanged(updated);
- }
-
- /**
- * See {@code LauncherBindingDelegate}
- */
- @Override
- public void bindRestoreItemsChange(HashSet<ItemInfo> updates) {
- mModelCallbacks.bindRestoreItemsChange(updates);
+ public void bindItemsUpdated(Set<ItemInfo> updates) {
+ mModelCallbacks.bindItemsUpdated(updates);
}
/**
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index d8bb84e..484cef4 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -58,11 +58,11 @@
else encryptedContext.getSharedPreferences(sharedPrefFile, MODE_PRIVATE)
/** Returns the value with type [T] for [item]. */
- fun <T> get(item: ContextualItem<T>): T =
+ open fun <T> get(item: ContextualItem<T>): T =
getInner(item, item.defaultValueFromContext(encryptedContext))
/** Returns the value with type [T] for [item]. */
- fun <T> get(item: ConstantItem<T>): T = getInner(item, item.defaultValue)
+ open fun <T> get(item: ConstantItem<T>): T = getInner(item, item.defaultValue)
/**
* Retrieves the value for an [Item] from [SharedPreferences]. It handles method typing via the
@@ -406,3 +406,20 @@
ENCRYPTED,
DEVICE_PROTECTED,
}
+
+/**
+ * LauncherPrefs which delegates all lookup to [prefs] but uses the real prefs for initial values
+ */
+class ProxyPrefs(context: Context, private val prefs: SharedPreferences) : LauncherPrefs(context) {
+
+ private val realPrefs = LauncherPrefs(context)
+
+ override val Item.sharedPrefs: SharedPreferences
+ get() = prefs
+
+ override fun <T> get(item: ConstantItem<T>) =
+ super.get(backedUpItem(item.sharedPrefKey, realPrefs.get(item)))
+
+ override fun <T> get(item: ContextualItem<T>) =
+ super.get(backedUpItem(item.sharedPrefKey, realPrefs.get(item)))
+}
diff --git a/src/com/android/launcher3/ModelCallbacks.kt b/src/com/android/launcher3/ModelCallbacks.kt
index 5d32525..5338fb4 100644
--- a/src/com/android/launcher3/ModelCallbacks.kt
+++ b/src/com/android/launcher3/ModelCallbacks.kt
@@ -17,8 +17,6 @@
import com.android.launcher3.model.StringCache
import com.android.launcher3.model.data.AppInfo
import com.android.launcher3.model.data.ItemInfo
-import com.android.launcher3.model.data.LauncherAppWidgetInfo
-import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.popup.PopupContainerWithArrow
import com.android.launcher3.util.ComponentKey
import com.android.launcher3.util.IntArray as LIntArray
@@ -215,29 +213,13 @@
launcher.appsView.appsStore.updateProgressBar(app)
}
- override fun bindWidgetsRestored(widgets: ArrayList<LauncherAppWidgetInfo?>?) {
- launcher.workspace.widgetsRestored(widgets)
- }
-
- /**
- * Some shortcuts were updated in the background. Implementation of the method from
- * LauncherModel.Callbacks.
- *
- * @param updated list of shortcuts which have changed.
- */
- override fun bindWorkspaceItemsChanged(updated: List<WorkspaceItemInfo?>) {
- if (updated.isNotEmpty()) {
- launcher.workspace.updateWorkspaceItems(updated, launcher)
- PopupContainerWithArrow.dismissInvalidPopup(launcher)
- }
- }
-
/**
* Update the state of a package, typically related to install state. Implementation of the
* method from LauncherModel.Callbacks.
*/
- override fun bindRestoreItemsChange(updates: HashSet<ItemInfo?>?) {
- launcher.workspace.updateRestoreItems(updates, launcher)
+ override fun bindItemsUpdated(updates: Set<ItemInfo>) {
+ launcher.workspace.updateContainerItems(updates, launcher)
+ PopupContainerWithArrow.dismissInvalidPopup(launcher)
}
/**
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 7563493..d93c07f 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -659,7 +659,8 @@
// Only fetch badge if the icon is on workspace
if (info.id != ItemInfo.NO_ID && badge == null) {
badge = appState.getIconCache().getShortcutInfoBadge(si)
- .newIcon(context, FLAG_THEMED);
+ .newIcon(context, ThemeManager.INSTANCE.get(context)
+ .isMonoThemeEnabled() ? FLAG_THEMED : 0);
}
}
} else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 86c49d0..b41a425 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -53,8 +53,6 @@
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.Message;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
@@ -125,13 +123,9 @@
import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.WallpaperOffsetInterpolator;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
-import com.android.launcher3.widget.LauncherWidgetHolder;
-import com.android.launcher3.widget.LauncherWidgetHolder.ProviderChangedListener;
import com.android.launcher3.widget.NavigableAppWidgetHostView;
import com.android.launcher3.widget.PendingAddShortcutInfo;
import com.android.launcher3.widget.PendingAddWidgetInfo;
-import com.android.launcher3.widget.PendingAppWidgetHostView;
-import com.android.launcher3.widget.WidgetManagerHelper;
import com.android.launcher3.widget.util.WidgetSizes;
import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlayCallbacks;
import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlayTouchProxy;
@@ -664,9 +658,6 @@
bindAndInitFirstWorkspaceScreen();
}
- // Remove any deferred refresh callbacks
- mLauncher.mHandler.removeCallbacksAndMessages(DeferredWidgetRefresh.class);
-
// Re-enable the layout transitions
enableLayoutTransitions();
}
@@ -1771,7 +1762,7 @@
}
final DragView dv;
- if (contentView instanceof View) {
+ if (contentView != null) {
dv = mDragController.startDrag(
contentView,
draggableView,
@@ -3465,43 +3456,6 @@
removeItemsByMatcher(matcher);
}
- public void widgetsRestored(final ArrayList<LauncherAppWidgetInfo> changedInfo) {
- if (!changedInfo.isEmpty()) {
- DeferredWidgetRefresh widgetRefresh = new DeferredWidgetRefresh(changedInfo,
- mLauncher.getAppWidgetHolder());
-
- LauncherAppWidgetInfo item = changedInfo.get(0);
- final AppWidgetProviderInfo widgetInfo;
- WidgetManagerHelper widgetHelper = new WidgetManagerHelper(getContext());
- if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
- widgetInfo = widgetHelper.findProvider(item.providerName, item.user);
- } else {
- widgetInfo = widgetHelper.getLauncherAppWidgetInfo(item.appWidgetId,
- item.getTargetComponent());
- }
-
- if (widgetInfo != null) {
- // Re-inflate the widgets which have changed status
- widgetRefresh.run();
- } else {
- // widgetRefresh will automatically run when the packages are updated.
- // For now just update the progress bars
- mapOverItems(new ItemOperator() {
- @Override
- public boolean evaluate(ItemInfo info, View view) {
- if (view instanceof PendingAppWidgetHostView
- && changedInfo.contains(info)) {
- ((LauncherAppWidgetInfo) info).installProgress = 100;
- ((PendingAppWidgetHostView) view).applyState();
- }
- // process all the shortcuts
- return false;
- }
- });
- }
- }
- }
-
public boolean isOverlayShown() {
return mOverlayShown;
}
@@ -3608,62 +3562,6 @@
return mLauncher.getCellPosMapper();
}
- /**
- * Used as a workaround to ensure that the AppWidgetService receives the
- * PACKAGE_ADDED broadcast before updating widgets.
- */
- private class DeferredWidgetRefresh implements Runnable, ProviderChangedListener {
- private final ArrayList<LauncherAppWidgetInfo> mInfos;
- private final LauncherWidgetHolder mWidgetHolder;
- private final Handler mHandler;
-
- private boolean mRefreshPending;
-
- DeferredWidgetRefresh(ArrayList<LauncherAppWidgetInfo> infos,
- LauncherWidgetHolder holder) {
- mInfos = infos;
- mWidgetHolder = holder;
- mHandler = mLauncher.mHandler;
- mRefreshPending = true;
-
- mWidgetHolder.addProviderChangeListener(this);
- // Force refresh after 10 seconds, if we don't get the provider changed event.
- // This could happen when the provider is no longer available in the app.
- Message msg = Message.obtain(mHandler, this);
- msg.obj = DeferredWidgetRefresh.class;
- mHandler.sendMessageDelayed(msg, 10000);
- }
-
- @Override
- public void run() {
- mWidgetHolder.removeProviderChangeListener(this);
- mHandler.removeCallbacks(this);
-
- if (!mRefreshPending) {
- return;
- }
-
- mRefreshPending = false;
-
- ArrayList<PendingAppWidgetHostView> views = new ArrayList<>(mInfos.size());
- mapOverItems((info, view) -> {
- if (view instanceof PendingAppWidgetHostView && mInfos.contains(info)) {
- views.add((PendingAppWidgetHostView) view);
- }
- // process all children
- return false;
- });
- for (PendingAppWidgetHostView view : views) {
- view.reInflate();
- }
- }
-
- @Override
- public void notifyWidgetProvidersChanged() {
- run();
- }
- }
-
private class StateTransitionListener extends AnimatorListenerAdapter
implements AnimatorUpdateListener {
diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java
index 9afe06c..d5a4022 100644
--- a/src/com/android/launcher3/allapps/AllAppsStore.java
+++ b/src/com/android/launcher3/allapps/AllAppsStore.java
@@ -17,7 +17,6 @@
import static com.android.launcher3.model.data.AppInfo.COMPONENT_KEY_COMPARATOR;
import static com.android.launcher3.model.data.AppInfo.EMPTY_ARRAY;
-import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK;
import android.content.Context;
import android.os.UserHandle;
@@ -229,11 +228,7 @@
public void updateProgressBar(AppInfo app) {
updateAllIcons((child) -> {
if (child.getTag() == app) {
- if ((app.runtimeStatusFlags & FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) == 0) {
- child.applyFromApplicationInfo(app);
- } else {
- child.applyProgressLevel();
- }
+ child.applyFromApplicationInfo(app);
}
});
}
diff --git a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
index 0b7b20f..7bd7c3e 100644
--- a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
+++ b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
@@ -23,6 +23,7 @@
import com.android.launcher3.graphics.ThemeManager;
import com.android.launcher3.model.ItemInstallQueue;
import com.android.launcher3.pm.InstallSessionHelper;
+import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.ApiWrapper;
import com.android.launcher3.util.DaggerSingletonTracker;
import com.android.launcher3.util.DisplayController;
@@ -67,6 +68,7 @@
WindowManagerProxy getWmProxy();
LauncherPrefs getLauncherPrefs();
ThemeManager getThemeManager();
+ UserCache getUserCache();
DisplayController getDisplayController();
WallpaperColorHints getWallpaperColorHints();
LockedUserState getLockedUserState();
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 9c82748..072673d 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -273,7 +273,11 @@
Rect shrunkBounds = new Rect(bounds);
Utilities.scaleRectAboutCenter(shrunkBounds, 0.98f);
adaptiveIcon.setBounds(shrunkBounds);
- final Path mask = IconShape.INSTANCE.get(getContext()).getShapeOverridePath(w);
+
+ IconShape iconShape = IconShape.INSTANCE.get(getContext());
+ final Path mask = (adaptiveIcon instanceof FolderAdaptiveIcon
+ ? iconShape.getFolderShape() : iconShape.getShape())
+ .getPath(shrunkBounds);
mTranslateX = new SpringFloatValue(DragView.this,
w * AdaptiveIconDrawable.getExtraInsetFraction());
diff --git a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
index 6a43b24..929e52e 100644
--- a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
+++ b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
@@ -165,7 +165,7 @@
Paint backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
backgroundPaint.setColor(bg.getBgColor());
bg.drawShadow(backgroundCanvas);
- backgroundCanvas.drawCircle(size / 2f, size / 2f, bg.getRadius(), backgroundPaint);
+ backgroundCanvas.drawPaint(backgroundPaint);
bg.drawBackgroundStroke(backgroundCanvas);
}
diff --git a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
index 8cd91d3..cf5150a 100644
--- a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
+++ b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
@@ -1,5 +1,7 @@
package com.android.launcher3.folder;
+import com.android.launcher3.Flags;
+
public class ClippedFolderIconLayoutRule {
public static final int MAX_NUM_ITEMS_IN_PREVIEW = 4;
@@ -7,9 +9,12 @@
private static final float MIN_SCALE = 0.44f;
private static final float MAX_SCALE = 0.51f;
+ // TODO: figure out exact radius for different icons
+ private static final float MAX_RADIUS_DILATION_SHAPES = 0.15f;
private static final float MAX_RADIUS_DILATION = 0.25f;
// The max amount of overlap the preview items can go outside of the background bounds.
public static final float ICON_OVERLAP_FACTOR = 1 + (MAX_RADIUS_DILATION / 2f);
+ public static final float ICON_OVERLAP_FACTOR_SHAPES = 1f;
private static final float ITEM_RADIUS_SCALE_FACTOR = 1.15f;
public static final int EXIT_INDEX = -2;
@@ -28,7 +33,7 @@
mRadius = ITEM_RADIUS_SCALE_FACTOR * availableSpace / 2f;
mIconSize = intrinsicIconSize;
mIsRtl = rtl;
- mBaselineIconScale = availableSpace / (intrinsicIconSize * 1f);
+ mBaselineIconScale = availableSpace / intrinsicIconSize;
}
public PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
@@ -84,6 +89,7 @@
result[1] = top + (row * dy);
}
+ // b/392610664 TODO: Change positioning from circular geometry to square / grid-based.
private void getPosition(int index, int curNumItems, float[] result) {
// The case of two items is homomorphic to the case of one.
curNumItems = Math.max(curNumItems, 2);
@@ -113,8 +119,10 @@
}
// We bump the radius up between 0 and MAX_RADIUS_DILATION % as the number of items increase
- float radius = mRadius * (1 + MAX_RADIUS_DILATION * (curNumItems -
- MIN_NUM_ITEMS_IN_PREVIEW) / (MAX_NUM_ITEMS_IN_PREVIEW - MIN_NUM_ITEMS_IN_PREVIEW));
+ float radiusDilation = Flags.enableLauncherIconShapes() ? MAX_RADIUS_DILATION_SHAPES
+ : MAX_RADIUS_DILATION;
+ float radius = mRadius * (1 + radiusDilation * (curNumItems - MIN_NUM_ITEMS_IN_PREVIEW)
+ / (MAX_NUM_ITEMS_IN_PREVIEW - MIN_NUM_ITEMS_IN_PREVIEW));
double theta = theta0 + index * (2 * Math.PI / curNumItems) * direction;
float halfIconSize = (mIconSize * scaleForItem(curNumItems)) / 2;
@@ -130,7 +138,7 @@
public float scaleForItem(int numItems) {
// Scale is determined by the number of items in the preview.
final float scale;
- if (numItems <= 3) {
+ if (numItems <= 3 && !Flags.enableLauncherIconShapes()) {
scale = MAX_SCALE;
} else {
scale = MIN_SCALE;
@@ -141,4 +149,15 @@
public float getIconSize() {
return mIconSize;
}
+
+ /**
+ * Gets correct constant for icon overlap.
+ */
+ public static float getIconOverlapFactor() {
+ if (Flags.enableLauncherIconShapes()) {
+ return ICON_OVERLAP_FACTOR_SHAPES;
+ } else {
+ return ICON_OVERLAP_FACTOR;
+ }
+ }
}
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index 588a6db..d2ff2cb 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -233,7 +233,7 @@
}
play(a, getAnimator(mFolder.mFooter, ALPHA, 0, 1f), footerStartDelay, footerAlphaDuration);
- ShapeDelegate shapeDelegate = IconShape.INSTANCE.get(mContext).getShape();
+ ShapeDelegate shapeDelegate = IconShape.INSTANCE.get(mContext).getFolderShape();
// Create reveal animator for the folder background
play(a, shapeDelegate.createRevealAnimator(
mFolder, startRect, endRect, finalRadius, !mIsOpening));
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 2481a1a..0ed8787 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -17,7 +17,6 @@
package com.android.launcher3.folder;
import static com.android.launcher3.Flags.enableCursorHoverStates;
-import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
import static com.android.launcher3.folder.FolderGridOrganizer.createFolderGridOrganizer;
import static com.android.launcher3.folder.PreviewItemManager.INITIAL_ITEM_ANIMATION_DURATION;
@@ -177,12 +176,16 @@
FolderIcon icon = inflateIcon(resId, activityContext, group, folderInfo);
folder.setFolderIcon(icon);
folder.bind(folderInfo);
+
icon.setFolder(folder);
+ folderInfo.addListener(icon);
return icon;
}
/**
- * Builds a FolderIcon to be added to the Launcher
+ * Builds a FolderIcon to be added to the activity.
+ * This method doesn't add any listeners to the FolderInfo, and hence any changes to the info
+ * will not be reflected in the folder.
*/
public static FolderIcon inflateIcon(int resId, ActivityContext activity,
@Nullable ViewGroup group, FolderInfo folderInfo) {
@@ -228,8 +231,6 @@
icon.mPreviewVerifier.setFolderInfo(folderInfo);
icon.updatePreviewItems(false);
- folderInfo.addListener(icon);
-
return icon;
}
@@ -246,7 +247,8 @@
mPreviewItemManager.recomputePreviewDrawingParams();
mBackground.getBounds(outBounds);
// The preview items go outside of the bounds of the background.
- Utilities.scaleRectAboutCenter(outBounds, ICON_OVERLAP_FACTOR);
+ Utilities.scaleRectAboutCenter(outBounds,
+ ClippedFolderIconLayoutRule.getIconOverlapFactor());
}
public float getBackgroundStrokeWidth() {
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
index be5f8f7..8a1f96d 100644
--- a/src/com/android/launcher3/folder/FolderNameProvider.java
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.folder;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
+
import android.annotation.SuppressLint;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
@@ -37,6 +39,7 @@
import com.android.launcher3.model.StringCache;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.CollectionInfo;
+import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.Preconditions;
@@ -197,9 +200,18 @@
@Override
public void execute(@NonNull ModelTaskController taskController,
@NonNull BgDataModel dataModel, @NonNull AllAppsList apps) {
- mCollectionInfos = dataModel.collections.clone();
+ mCollectionInfos = getCollectionForSuggestions(dataModel);
mAppInfos = Arrays.asList(apps.copyData());
}
}
+ public static IntSparseArrayMap<CollectionInfo> getCollectionForSuggestions(
+ BgDataModel dataModel) {
+ IntSparseArrayMap<CollectionInfo> result = new IntSparseArrayMap<>();
+ dataModel.itemsIdMap.stream()
+ .filter(item -> item.itemType == ITEM_TYPE_FOLDER)
+ .forEach(item -> result.put(item.id, (FolderInfo) item));
+ return result;
+ }
+
}
diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java
index df41d47..77fa355 100644
--- a/src/com/android/launcher3/folder/PreviewBackground.java
+++ b/src/com/android/launcher3/folder/PreviewBackground.java
@@ -18,7 +18,6 @@
import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE;
-import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
import android.animation.Animator;
@@ -261,7 +260,7 @@
}
private ShapeDelegate getShape() {
- return IconShape.INSTANCE.get(mContext).getShape();
+ return IconShape.INSTANCE.get(mContext).getFolderShape();
}
public void drawShadow(Canvas canvas) {
@@ -373,7 +372,7 @@
public Path getClipPath() {
mPath.reset();
- float radius = getScaledRadius() * ICON_OVERLAP_FACTOR;
+ float radius = getScaledRadius() * ClippedFolderIconLayoutRule.getIconOverlapFactor();
// Find the difference in radius so that the clip path remains centered.
float radiusDifference = radius - getRadius();
float offsetX = basePreviewOffsetX - radiusDifference;
diff --git a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
index ad176dc..c9e688b 100644
--- a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
+++ b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
@@ -16,10 +16,12 @@
package com.android.launcher3.graphics;
-import static com.android.launcher3.graphics.IconShape.PREF_ICON_SHAPE;
+import static com.android.launcher3.graphics.ThemeManager.PREF_ICON_SHAPE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static java.util.Objects.requireNonNullElse;
+
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
@@ -45,16 +47,16 @@
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.shapes.IconShapeModel;
-import com.android.launcher3.shapes.IconShapesProvider;
+import com.android.launcher3.shapes.ShapesProvider;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.RunnableList;
import com.android.systemui.shared.Flags;
+import java.lang.ref.WeakReference;
import java.util.Collections;
-import java.util.List;
import java.util.Set;
-import java.util.WeakHashMap;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
/**
@@ -120,7 +122,7 @@
// Set of all active previews used to track duplicate memory allocations
private final Set<PreviewLifecycleObserver> mActivePreviews =
- Collections.newSetFromMap(new WeakHashMap<>());
+ Collections.newSetFromMap(new ConcurrentHashMap<>());
@Override
public boolean onCreate() {
@@ -141,23 +143,15 @@
if (Flags.newCustomizationPickerUi()) {
MatrixCursor cursor = new MatrixCursor(new String[]{
KEY_SHAPE_KEY, KEY_SHAPE_TITLE, KEY_PATH, KEY_IS_DEFAULT});
- List<IconShapeModel> shapes = IconShapesProvider.INSTANCE.getShapes()
- .values()
- .stream()
- .toList();
- String currentPath = LauncherPrefs.get(context).get(PREF_ICON_SHAPE);
- IconShapeModel currentShape = shapes.stream()
- .filter(shape -> currentPath.equals(shape.getPathString()))
- .findFirst()
- .orElse(IconShapesProvider.INSTANCE.getShapes().get("circle"));
-
- for (int i = 0; i < shapes.size(); i++) {
- IconShapeModel shape = shapes.get(i);
+ String currentShapePath =
+ ThemeManager.INSTANCE.get(context).getIconState().getIconMask();
+ for (IconShapeModel shape : ShapesProvider.INSTANCE.getIconShapes().values()) {
cursor.newRow()
.add(KEY_SHAPE_KEY, shape.getKey())
.add(KEY_SHAPE_TITLE, shape.getTitle())
.add(KEY_PATH, shape.getPathString())
- .add(KEY_IS_DEFAULT, shape.equals(currentShape));
+ .add(KEY_IS_DEFAULT,
+ shape.getPathString().equals(currentShapePath));
}
return cursor;
} else {
@@ -218,9 +212,8 @@
switch (path) {
case KEY_DEFAULT_GRID: {
if (Flags.newCustomizationPickerUi()) {
- String shapeKey = values.getAsString(KEY_SHAPE_KEY);
- IconShapeModel shape = IconShapesProvider.INSTANCE.getShapes().get(shapeKey);
- IconShape.INSTANCE.get(context).setShapeOverride(shape);
+ LauncherPrefs.INSTANCE.get(context).put(PREF_ICON_SHAPE,
+ requireNonNullElse(values.getAsString(KEY_SHAPE_KEY), ""));
}
String gridName = values.getAsString(KEY_NAME);
InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(context);
@@ -321,8 +314,15 @@
Bundle result = new Bundle();
result.putParcelable(KEY_SURFACE_PACKAGE, renderer.getSurfacePackage());
- Messenger messenger =
- new Messenger(new Handler(UI_HELPER_EXECUTOR.getLooper(), observer));
+ mActivePreviews.add(observer);
+ lifeCycleTracker.add(() -> mActivePreviews.remove(observer));
+
+ // Wrap the callback in a weak reference. This ensures that the callback is not kept
+ // alive due to the Messenger's IBinder
+ Messenger messenger = new Messenger(new Handler(
+ UI_HELPER_EXECUTOR.getLooper(),
+ new WeakCallbackWrapper(observer)));
+
Message msg = Message.obtain();
msg.replyTo = messenger;
result.putParcelable(KEY_CALLBACK, msg);
@@ -362,9 +362,7 @@
if (Flags.newCustomizationPickerUi()
&& com.android.launcher3.Flags.enableLauncherIconShapes()) {
String shapeKey = message.getData().getString(KEY_SHAPE_KEY);
- IconShapeModel shape =
- IconShapesProvider.INSTANCE.getShapes().get(shapeKey);
- renderer.updateShape(shape);
+ renderer.updateShape(shapeKey);
}
break;
case MESSAGE_ID_UPDATE_GRID:
@@ -402,4 +400,34 @@
&& plo.renderer.getDisplayId() == renderer.getDisplayId();
}
}
+
+ /**
+ * A WeakReference wrapper around Handler.Callback to avoid passing hard-reference over IPC
+ * when using a Messenger
+ */
+ private static class WeakCallbackWrapper implements Handler.Callback {
+
+ private final WeakReference<Handler.Callback> mActual;
+ private final Message mCleanupMessage;
+
+ WeakCallbackWrapper(Handler.Callback actual) {
+ mActual = new WeakReference<>(actual);
+ mCleanupMessage = new Message();
+ }
+
+ @Override
+ public boolean handleMessage(Message message) {
+ Handler.Callback actual = mActual.get();
+ return actual != null && actual.handleMessage(message);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ super.finalize();
+ Handler.Callback actual = mActual.get();
+ if (actual != null) {
+ actual.handleMessage(mCleanupMessage);
+ }
+ }
+ }
}
diff --git a/src/com/android/launcher3/graphics/IconShape.kt b/src/com/android/launcher3/graphics/IconShape.kt
index 2c4d8e4..e62936c 100644
--- a/src/com/android/launcher3/graphics/IconShape.kt
+++ b/src/com/android/launcher3/graphics/IconShape.kt
@@ -18,7 +18,6 @@
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
-import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Matrix
@@ -41,19 +40,12 @@
import androidx.graphics.shapes.SvgPathParser
import androidx.graphics.shapes.toPath
import androidx.graphics.shapes.transformed
-import com.android.launcher3.EncryptionType
-import com.android.launcher3.LauncherPrefs
-import com.android.launcher3.LauncherPrefs.Companion.backedUpItem
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider
-import com.android.launcher3.dagger.ApplicationContext
import com.android.launcher3.dagger.LauncherAppComponent
import com.android.launcher3.dagger.LauncherAppSingleton
-import com.android.launcher3.graphics.LauncherPreviewRenderer.PreviewContext
import com.android.launcher3.graphics.ThemeManager.ThemeChangeListener
import com.android.launcher3.icons.GraphicsUtils
-import com.android.launcher3.icons.IconNormalizer
-import com.android.launcher3.shapes.IconShapeModel
-import com.android.launcher3.shapes.IconShapesProvider
+import com.android.launcher3.icons.IconNormalizer.normalizeAdaptiveIcon
import com.android.launcher3.util.DaggerSingletonObject
import com.android.launcher3.util.DaggerSingletonTracker
import com.android.launcher3.views.ClipPathView
@@ -63,56 +55,52 @@
@LauncherAppSingleton
class IconShape
@Inject
-constructor(
- @ApplicationContext private val context: Context,
- private val prefs: LauncherPrefs,
- private val themeManager: ThemeManager,
- lifeCycle: DaggerSingletonTracker,
-) {
+constructor(private val themeManager: ThemeManager, lifeCycle: DaggerSingletonTracker) {
- var shapeOverride: IconShapeModel? = getShapeFromPathString(prefs.get(PREF_ICON_SHAPE))
- set(value) {
- field = value
- if (context !is PreviewContext) {
- value?.let { prefs.put(PREF_ICON_SHAPE, value.pathString) }
- }
- }
+ val normalizationScale =
+ normalizeAdaptiveIcon(
+ AdaptiveIconDrawable(null, ColorDrawable(Color.BLACK)),
+ AREA_CALC_SIZE,
+ )
- var normalizationScale: Float = IconNormalizer.ICON_VISIBLE_AREA_FACTOR
+ var shape: ShapeDelegate = pickBestShape(themeManager.iconState.iconMask)
private set
- var shape: ShapeDelegate = pickBestShape(themeManager)
+ var folderShape: ShapeDelegate =
+ themeManager.iconState.run {
+ if (folderShapeMask == iconMask || folderShapeMask.isEmpty()) shape
+ else pickBestShape(folderShapeMask)
+ }
private set
init {
- val changeListener = ThemeChangeListener { shape = pickBestShape(themeManager) }
+ val changeListener = ThemeChangeListener {
+ shape = pickBestShape(themeManager.iconState.iconMask)
+ folderShape =
+ themeManager.iconState.run {
+ if (folderShapeMask == iconMask || folderShapeMask.isEmpty()) shape
+ else pickBestShape(folderShapeMask)
+ }
+ }
themeManager.addChangeListener(changeListener)
lifeCycle.addCloseable { themeManager.removeChangeListener(changeListener) }
}
- fun getShapeOverridePath(pathSize: Float = DEFAULT_PATH_SIZE): Path {
- val path = PathParser.createPathFromPathData(themeManager.iconState.iconMask)
- if (pathSize != DEFAULT_PATH_SIZE) {
- val matrix = Matrix()
- val scale: Float = pathSize / DEFAULT_PATH_SIZE
- matrix.setScale(scale, scale)
- path.transform(matrix)
- }
- return path
- }
+ interface ShapeDelegate {
+ fun getPath(pathSize: Float = DEFAULT_PATH_SIZE) =
+ Path().apply { addToPath(this, 0f, 0f, pathSize / 2) }
- /** Initializes the shape which is closest to the [AdaptiveIconDrawable] */
- private fun pickBestShape(themeManager: ThemeManager): ShapeDelegate {
- val drawable =
- AdaptiveIconDrawable(null, ColorDrawable(Color.BLACK)).apply {
- setBounds(0, 0, AREA_CALC_SIZE, AREA_CALC_SIZE)
+ fun getPath(bounds: Rect) =
+ Path().apply {
+ addToPath(
+ this,
+ bounds.left.toFloat(),
+ bounds.top.toFloat(),
+ // Radius is half of the average size of the icon
+ (bounds.width() + bounds.height()) / 4f,
+ )
}
- normalizationScale = IconNormalizer.normalizeAdaptiveIcon(drawable, AREA_CALC_SIZE)
- return pickBestShape(drawable.iconMask, themeManager.iconState.iconMask)
- }
-
- interface ShapeDelegate {
fun drawShape(canvas: Canvas, offsetX: Float, offsetY: Float, radius: Float, paint: Paint)
fun addToPath(path: Path, offsetX: Float, offsetY: Float, radius: Float)
@@ -126,7 +114,6 @@
): ValueAnimator where T : View, T : ClipPathView
}
- @VisibleForTesting
class Circle : RoundedSquare(1f) {
override fun drawShape(
@@ -217,14 +204,24 @@
paint: Paint,
) {
tmpPath.reset()
- addToPath(tmpPath, offsetX, offsetY, radius)
+ addToPath(tmpPath, offsetX, offsetY, radius, tmpMatrix)
canvas.drawPath(tmpPath, paint)
}
override fun addToPath(path: Path, offsetX: Float, offsetY: Float, radius: Float) {
- tmpMatrix.setScale(radius / 50, radius / 50)
- tmpMatrix.postTranslate(offsetX, offsetY)
- basePath.transform(tmpMatrix, path)
+ addToPath(path, offsetX, offsetY, radius, Matrix())
+ }
+
+ private fun addToPath(
+ path: Path,
+ offsetX: Float,
+ offsetY: Float,
+ radius: Float,
+ matrix: Matrix,
+ ) {
+ matrix.setScale(radius / 50, radius / 50)
+ matrix.postTranslate(offsetX, offsetY)
+ basePath.transform(matrix, path)
}
override fun <T> createRevealAnimator(
@@ -292,20 +289,11 @@
@JvmField var INSTANCE = DaggerSingletonObject(LauncherAppComponent::getIconShape)
const val TAG = "IconShape"
- const val KEY_ICON_SHAPE = "icon_shape"
const val DEFAULT_PATH_SIZE = 100f
const val AREA_CALC_SIZE = 1000
// .1% error margin
const val AREA_DIFF_THRESHOLD = AREA_CALC_SIZE * AREA_CALC_SIZE / 1000
- @JvmField val PREF_ICON_SHAPE = backedUpItem(KEY_ICON_SHAPE, "", EncryptionType.ENCRYPTED)
-
- private fun getShapeFromPathString(pathString: String): IconShapeModel? {
- return IconShapesProvider.shapes.values.firstOrNull { shape: IconShapeModel ->
- shape.pathString == pathString
- }
- }
-
/** Returns a function to calculate area diff from [base] */
@VisibleForTesting
fun areaDiffCalculator(base: Path): (ShapeDelegate) -> Int {
@@ -324,6 +312,26 @@
}
@VisibleForTesting
+ fun pickBestShape(shapeStr: String): ShapeDelegate {
+ val baseShape =
+ if (shapeStr.isNotEmpty()) {
+ PathParser.createPathFromPathData(shapeStr).apply {
+ transform(
+ Matrix().apply {
+ setScale(AREA_CALC_SIZE / 100f, AREA_CALC_SIZE / 100f)
+ }
+ )
+ }
+ } else {
+ AdaptiveIconDrawable(null, ColorDrawable(Color.BLACK)).let {
+ it.setBounds(0, 0, AREA_CALC_SIZE, AREA_CALC_SIZE)
+ it.iconMask
+ }
+ }
+ return pickBestShape(baseShape, shapeStr)
+ }
+
+ @VisibleForTesting
fun pickBestShape(baseShape: Path, shapeStr: String): ShapeDelegate {
val calcAreaDiff = areaDiffCalculator(baseShape)
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index f0e4fc4..911064c 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -24,10 +24,10 @@
import static com.android.launcher3.BubbleTextView.DISPLAY_WORKSPACE;
import static com.android.launcher3.DeviceProfile.DEFAULT_SCALE;
import static com.android.launcher3.Hotseat.ALPHA_CHANNEL_PREVIEW_RENDERER;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
-import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
-import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks;
+import static com.android.launcher3.model.ModelUtils.currentScreenContentFilter;
import android.app.Fragment;
import android.app.WallpaperColors;
@@ -67,7 +67,9 @@
import com.android.launcher3.InsettableFrameLayout;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.ProxyPrefs;
import com.android.launcher3.R;
import com.android.launcher3.Workspace;
import com.android.launcher3.WorkspaceLayoutManager;
@@ -75,6 +77,9 @@
import com.android.launcher3.celllayout.CellLayoutLayoutParams;
import com.android.launcher3.celllayout.CellPosMapper;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherAppModule;
+import com.android.launcher3.dagger.LauncherAppSingleton;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
@@ -100,12 +105,18 @@
import com.android.launcher3.widget.util.WidgetSizes;
import com.android.systemui.shared.Flags;
+import dagger.BindsInstance;
+import dagger.Component;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
+import java.util.UUID;
import java.util.stream.Collectors;
+import java.util.stream.IntStream;
/**
* Utility class for generating the preview of Launcher for a given InvariantDeviceProfile.
@@ -124,12 +135,25 @@
*/
public static class PreviewContext extends SandboxContext {
+ private final String mPrefName;
+
public PreviewContext(Context base, InvariantDeviceProfile idp) {
super(base);
+ mPrefName = "preview-" + UUID.randomUUID().toString();
+ initDaggerComponent(DaggerLauncherPreviewRenderer_PreviewAppComponent.builder()
+ .bindPrefs(new ProxyPrefs(
+ this, getSharedPreferences(mPrefName, MODE_PRIVATE))));
+
putObject(InvariantDeviceProfile.INSTANCE, idp);
putObject(LauncherAppState.INSTANCE,
new LauncherAppState(this, null /* iconCacheFileName */));
}
+
+ @Override
+ protected void cleanUpObjects() {
+ super.cleanUpObjects();
+ deleteSharedPreferences(mPrefName);
+ }
}
private final List<OnDeviceProfileChangeListener> mDpChangeListeners = new ArrayList<>();
@@ -456,54 +480,48 @@
private void populate(BgDataModel dataModel,
Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) {
- // Separate the items that are on the current screen, and the other remaining items.
- ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
- ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
- ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
- ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
+ IntSet missingHotseatRank = new IntSet();
+ IntStream.range(0, mDp.numShownHotseatIcons).forEach(missingHotseatRank::add);
- IntSet currentScreenIds = IntSet.wrap(mWorkspaceScreens.keySet());
- filterCurrentWorkspaceItems(currentScreenIds, dataModel.workspaceItems,
- currentWorkspaceItems, otherWorkspaceItems);
- filterCurrentWorkspaceItems(currentScreenIds, dataModel.appWidgets, currentAppWidgets,
- otherAppWidgets);
- for (ItemInfo itemInfo : currentWorkspaceItems) {
- switch (itemInfo.itemType) {
- case Favorites.ITEM_TYPE_APPLICATION:
- case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
- inflateAndAddIcon((WorkspaceItemInfo) itemInfo);
- break;
- case Favorites.ITEM_TYPE_FOLDER:
- case Favorites.ITEM_TYPE_APP_PAIR:
- inflateAndAddCollectionIcon((CollectionInfo) itemInfo);
- break;
- default:
- 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 (widgetsMap == null) {
- widgetsMap = dataModel.widgetsModel.getWidgetsByComponentKey()
- .entrySet()
- .stream()
- .filter(entry -> entry.getValue().widgetInfo != null)
- .collect(Collectors.toMap(
- Map.Entry::getKey,
- entry -> entry.getValue().widgetInfo
- ));
+ Map<ComponentKey, AppWidgetProviderInfo>[] widgetsMap = new Map[] { widgetProviderInfoMap};
+
+ // Separate the items that are on the current screen, and the other remaining items.
+ dataModel.itemsIdMap.stream()
+ .filter(currentScreenContentFilter(IntSet.wrap(mWorkspaceScreens.keySet())))
+ .forEach(itemInfo -> {
+ switch (itemInfo.itemType) {
+ case Favorites.ITEM_TYPE_APPLICATION:
+ case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+ inflateAndAddIcon((WorkspaceItemInfo) itemInfo);
+ break;
+ case Favorites.ITEM_TYPE_FOLDER:
+ case Favorites.ITEM_TYPE_APP_PAIR:
+ inflateAndAddCollectionIcon((CollectionInfo) itemInfo);
+ break;
+ case Favorites.ITEM_TYPE_APPWIDGET:
+ case Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
+ if (widgetsMap[0] == null) {
+ widgetsMap[0] = dataModel.widgetsModel.getWidgetsByComponentKey()
+ .entrySet()
+ .stream()
+ .filter(entry -> entry.getValue().widgetInfo != null)
+ .collect(Collectors.toMap(
+ Entry::getKey,
+ entry -> entry.getValue().widgetInfo
+ ));
+ }
+ inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo, widgetsMap[0]);
+ break;
+ default:
+ break;
}
- inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo, widgetsMap);
- break;
- default:
- break;
- }
- }
- IntArray ranks = getMissingHotseatRanks(currentWorkspaceItems,
- mDp.numShownHotseatIcons);
+
+ if (itemInfo.container == CONTAINER_HOTSEAT) {
+ missingHotseatRank.remove(itemInfo.screenId);
+ }
+ });
+
+ IntArray ranks = missingHotseatRank.getArray();
FixedContainerItems hotseatPredictions =
dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION);
List<ItemInfo> predictions = hotseatPredictions == null
@@ -582,4 +600,16 @@
return true;
}
}
+
+ @LauncherAppSingleton
+ @Component(modules = LauncherAppModule.class)
+ public interface PreviewAppComponent extends LauncherAppComponent {
+
+ /** Builder for NexusLauncherAppComponent. */
+ @Component.Builder
+ interface Builder extends LauncherAppComponent.Builder {
+ @BindsInstance Builder bindPrefs(LauncherPrefs prefs);
+ PreviewAppComponent build();
+ }
+ }
}
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
index 3464e9b..50d6d1c 100644
--- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -24,7 +24,6 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
-import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
@@ -33,12 +32,14 @@
import android.graphics.Rect;
import android.util.Property;
+import androidx.annotation.VisibleForTesting;
import androidx.core.graphics.ColorUtils;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.anim.AnimatorListeners;
+import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.util.Themes;
@@ -63,8 +64,6 @@
private static final int DEFAULT_PATH_SIZE = 100;
private static final int MAX_PAINT_ALPHA = 255;
- private static final int TRACK_ALPHA = (int) (0.27f * MAX_PAINT_ALPHA);
- private static final int DISABLED_ICON_ALPHA = (int) (0.6f * MAX_PAINT_ALPHA);
private static final long DURATION_SCALE = 500;
private static final long SCALE_AND_ALPHA_ANIM_DURATION = 500;
@@ -120,7 +119,7 @@
IconPalette.getPreloadProgressColor(context, info.bitmap.color),
getPreloadColors(context),
Utilities.isDarkTheme(context),
- IconShape.INSTANCE.get(context).getShapeOverridePath(DEFAULT_PATH_SIZE)
+ IconShape.INSTANCE.get(context).getShape().getPath(DEFAULT_PATH_SIZE)
);
}
@@ -284,20 +283,25 @@
(long) ((finalProgress - mInternalStateProgress) * DURATION_SCALE));
mCurrentAnim.setInterpolator(LINEAR);
if (isFinish) {
- if (onFinishCallback != null) {
- mCurrentAnim.addListener(AnimatorListeners.forEndCallback(onFinishCallback));
- }
mCurrentAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mRanFinishAnimation = true;
}
});
+ if (onFinishCallback != null) {
+ mCurrentAnim.addListener(AnimatorListeners.forEndCallback(onFinishCallback));
+ }
}
mCurrentAnim.start();
}
}
+ @VisibleForTesting
+ public ObjectAnimator getActiveAnimation() {
+ return mCurrentAnim;
+ }
+
/**
* Sets the internal progress and updates the UI accordingly
* for progress <= 0:
@@ -358,8 +362,7 @@
@Override
public FastBitmapConstantState newConstantState() {
return new PreloadIconConstantState(
- mBitmap,
- mIconColor,
+ mBitmapInfo,
mItem,
mIndicatorColor,
new int[] {mSystemAccentColor, mSystemBackgroundColor},
@@ -377,14 +380,13 @@
private final Path mShapePath;
public PreloadIconConstantState(
- Bitmap bitmap,
- int iconColor,
+ BitmapInfo bitmapInfo,
ItemInfoWithIcon info,
int indicatorColor,
int[] preloadColors,
boolean isDarkMode,
Path shapePath) {
- super(bitmap, iconColor);
+ super(bitmapInfo);
mInfo = info;
mIndicatorColor = indicatorColor;
mPreloadColors = preloadColors;
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index 6afac71..7a60814 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -21,6 +21,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
+import static com.android.launcher3.graphics.ThemeManager.PREF_ICON_SHAPE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -52,6 +53,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Workspace;
import com.android.launcher3.graphics.LauncherPreviewRenderer.PreviewContext;
@@ -62,7 +64,6 @@
import com.android.launcher3.model.LoaderTask;
import com.android.launcher3.model.ModelDbController;
import com.android.launcher3.provider.LauncherDbUtils;
-import com.android.launcher3.shapes.IconShapeModel;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.Themes;
@@ -71,6 +72,7 @@
import java.util.ArrayList;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.TimeUnit;
/** Render preview using surface view. */
@@ -91,10 +93,10 @@
private Context mContext;
private SparseIntArray mPreviewColorOverride;
private String mGridName;
- private IconShapeModel mShape;
+ private String mShapeKey;
+
@Nullable private Boolean mDarkMode;
private boolean mDestroyed = false;
- private LauncherPreviewRenderer mRenderer;
private boolean mHideQsb;
@Nullable private FrameLayout mViewRoot = null;
@@ -220,14 +222,14 @@
/**
* Update the shapes of the launcher preview
*
- * @param shape path for shapes
+ * @param shapeKey key for the IconShape model
*/
- public void updateShape(@NonNull IconShapeModel shape) {
- if (shape.equals(mShape)) {
- Log.w(TAG, "Preview shape already set, skipping. shape=" + shape);
+ public void updateShape(@Nullable String shapeKey) {
+ if (Objects.equals(mShapeKey, shapeKey)) {
+ Log.w(TAG, "Preview shape already set, skipping. shape=" + mShapeKey);
return;
}
- mShape = shape;
+ mShapeKey = shapeKey;
loadAsync();
}
@@ -237,9 +239,8 @@
* @param hide True to hide and false to show.
*/
public void hideBottomRow(boolean hide) {
- if (mRenderer != null) {
- mRenderer.hideBottomRow(hide);
- }
+ mHideQsb = hide;
+ loadAsync();
}
/**
@@ -317,11 +318,11 @@
final Context inflationContext = getPreviewContext();
final InvariantDeviceProfile idp = new InvariantDeviceProfile(inflationContext, mGridName);
if (GridSizeMigrationDBController.needsToMigrate(inflationContext, idp)
- || mShape != null) {
+ || mShapeKey != null) {
// Start the migration
PreviewContext previewContext = new PreviewContext(inflationContext, idp);
- if (mShape != null) {
- IconShape.INSTANCE.get(previewContext).setShapeOverride(mShape);
+ if (mShapeKey != null) {
+ LauncherPrefs.INSTANCE.get(previewContext).put(PREF_ICON_SHAPE, mShapeKey);
}
// Copy existing data to preview DB
LauncherDbUtils.copyTable(LauncherAppState.getInstance(mContext)
@@ -385,15 +386,16 @@
if (mDestroyed) {
return;
}
+ LauncherPreviewRenderer renderer;
if (Flags.newCustomizationPickerUi()) {
- mRenderer = new LauncherPreviewRenderer(inflationContext, idp, mPreviewColorOverride,
+ renderer = new LauncherPreviewRenderer(inflationContext, idp, mPreviewColorOverride,
mWallpaperColors, launcherWidgetSpanInfo);
} else {
- mRenderer = new LauncherPreviewRenderer(inflationContext, idp,
+ renderer = new LauncherPreviewRenderer(inflationContext, idp,
mWallpaperColors, launcherWidgetSpanInfo);
}
- mRenderer.hideBottomRow(mHideQsb);
- View view = mRenderer.getRenderedView(dataModel, widgetProviderInfoMap);
+ renderer.hideBottomRow(mHideQsb);
+ View view = renderer.getRenderedView(dataModel, widgetProviderInfoMap);
// This aspect scales the view to fit in the surface and centers it
final float scale = Math.min(mWidth / (float) view.getMeasuredWidth(),
mHeight / (float) view.getMeasuredHeight());
diff --git a/src/com/android/launcher3/graphics/ThemeManager.kt b/src/com/android/launcher3/graphics/ThemeManager.kt
index 989471f..9f35e4a 100644
--- a/src/com/android/launcher3/graphics/ThemeManager.kt
+++ b/src/com/android/launcher3/graphics/ThemeManager.kt
@@ -25,10 +25,9 @@
import com.android.launcher3.dagger.ApplicationContext
import com.android.launcher3.dagger.LauncherAppComponent
import com.android.launcher3.dagger.LauncherAppSingleton
-import com.android.launcher3.graphics.IconShape.Companion.KEY_ICON_SHAPE
-import com.android.launcher3.graphics.IconShape.Companion.PREF_ICON_SHAPE
import com.android.launcher3.icons.IconThemeController
import com.android.launcher3.icons.mono.MonoIconThemeController
+import com.android.launcher3.shapes.ShapesProvider
import com.android.launcher3.util.DaggerSingletonObject
import com.android.launcher3.util.DaggerSingletonTracker
import com.android.launcher3.util.Executors.MAIN_EXECUTOR
@@ -93,20 +92,26 @@
fun removeChangeListener(listener: ThemeChangeListener) = listeners.remove(listener)
private fun parseIconState(): IconState {
- val shapeOverride = prefs.get(PREF_ICON_SHAPE)
+ val shapeModel =
+ prefs.get(PREF_ICON_SHAPE).let { shapeOverride ->
+ ShapesProvider.iconShapes.values.firstOrNull { it.key == shapeOverride }
+ }
+ val iconMask =
+ when {
+ shapeModel != null -> shapeModel.pathString
+ CONFIG_ICON_MASK_RES_ID == Resources.ID_NULL -> ""
+ else -> context.resources.getString(CONFIG_ICON_MASK_RES_ID)
+ }
return IconState(
- iconMask =
- when {
- shapeOverride.isNotEmpty() -> shapeOverride
- CONFIG_ICON_MASK_RES_ID == Resources.ID_NULL -> ""
- else -> context.resources.getString(CONFIG_ICON_MASK_RES_ID)
- },
+ iconMask = iconMask,
+ folderShapeMask = shapeModel?.folderPathString ?: iconMask,
isMonoTheme = isMonoThemeEnabled,
)
}
data class IconState(
val iconMask: String,
+ val folderShapeMask: String,
val isMonoTheme: Boolean,
val themeCode: String = if (isMonoTheme) "with-theme" else "no-theme",
) {
@@ -121,9 +126,11 @@
companion object {
@JvmField val INSTANCE = DaggerSingletonObject(LauncherAppComponent::getThemeManager)
+ const val KEY_ICON_SHAPE = "icon_shape_model"
const val KEY_THEMED_ICONS = "themed_icons"
@JvmField val THEMED_ICONS = backedUpItem(KEY_THEMED_ICONS, false, EncryptionType.ENCRYPTED)
+ @JvmField val PREF_ICON_SHAPE = backedUpItem(KEY_ICON_SHAPE, "", EncryptionType.ENCRYPTED)
private const val ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED"
private val CONFIG_ICON_MASK_RES_ID: Int =
diff --git a/src/com/android/launcher3/icons/LauncherIconProvider.java b/src/com/android/launcher3/icons/LauncherIconProvider.java
index e40f526..482360c 100644
--- a/src/com/android/launcher3/icons/LauncherIconProvider.java
+++ b/src/com/android/launcher3/icons/LauncherIconProvider.java
@@ -19,14 +19,18 @@
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.graphics.ThemeManager;
import com.android.launcher3.util.ApiWrapper;
@@ -49,9 +53,14 @@
private Map<String, ThemeData> mThemedIconMap;
+ private final ApiWrapper mApiWrapper;
+ private final IconShape mIconShape;
+
public LauncherIconProvider(Context context) {
super(context);
setIconThemeSupported(ThemeManager.INSTANCE.get(context).isMonoThemeEnabled());
+ mApiWrapper = ApiWrapper.INSTANCE.get(context);
+ mIconShape = IconShape.INSTANCE.get(context);
}
/**
@@ -75,7 +84,25 @@
@Override
protected String getApplicationInfoHash(@NonNull ApplicationInfo appInfo) {
- return ApiWrapper.INSTANCE.get(mContext).getApplicationInfoHash(appInfo);
+ return mApiWrapper.getApplicationInfoHash(appInfo);
+ }
+
+ @Nullable
+ @Override
+ protected Drawable loadAppInfoIcon(ApplicationInfo info, Resources resources, int density) {
+ // Tries to load the round icon res, if the app defines it as an adaptive icon
+ if (mIconShape.getShape() instanceof IconShape.Circle) {
+ int roundIconRes = mApiWrapper.getRoundIconRes(info);
+ if (roundIconRes != 0 && roundIconRes != info.icon) {
+ try {
+ Drawable d = resources.getDrawableForDensity(roundIconRes, density);
+ if (d instanceof AdaptiveIconDrawable) {
+ return d;
+ }
+ } catch (Resources.NotFoundException exc) { }
+ }
+ }
+ return super.loadAppInfoIcon(info, resources, density);
}
private Map<String, ThemeData> getThemedIconMap() {
diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java
index 2ffbeb8..59fff62 100644
--- a/src/com/android/launcher3/icons/LauncherIcons.java
+++ b/src/com/android/launcher3/icons/LauncherIcons.java
@@ -17,22 +17,18 @@
package com.android.launcher3.icons;
import android.content.Context;
-import android.graphics.Matrix;
import android.graphics.Path;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.os.UserHandle;
import androidx.annotation.NonNull;
-import androidx.core.graphics.PathParser;
import com.android.launcher3.Flags;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.graphics.ThemeManager;
import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.shapes.IconShapeModel;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.UserIconInfo;
@@ -87,22 +83,7 @@
@Override
public Path getShapePath(AdaptiveIconDrawable drawable, Rect iconBounds) {
if (!Flags.enableLauncherIconShapes()) return drawable.getIconMask();
-
- IconShapeModel shapeOverride = IconShape.INSTANCE.get(mContext).getShapeOverride();
- if (shapeOverride != null) {
- Path maskPath = PathParser.createPathFromPathData(shapeOverride.getPathString());
- Matrix matrix = new Matrix();
- // Assuming Path is in [0, 0, 100, 100] coordinate space.
- matrix.setRectToRect(
- new RectF(0, 0, 100, 100),
- new RectF(iconBounds),
- Matrix.ScaleToFit.CENTER // Todo: CENTER or FILL?
- );
- maskPath.transform(matrix);
- return maskPath;
- } else {
- return drawable.getIconMask();
- }
+ return IconShape.INSTANCE.get(mContext).getShape().getPath(iconBounds);
}
@Override
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 6eb02ab..2d30466 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -225,9 +225,12 @@
@UiEvent(doc = "User tapped on desktop icon on a task menu.")
LAUNCHER_SYSTEM_SHORTCUT_DESKTOP_TAP(1706),
- @UiEvent(doc = "Use tapped on external display icon on a task menu,")
+ @UiEvent(doc = "User tapped on external display icon on a task menu,")
LAUNCHER_SYSTEM_SHORTCUT_EXTERNAL_DISPLAY_TAP(1957),
+ @UiEvent(doc = "User tapped on close app on a task menu,")
+ LAUNCHER_SYSTEM_SHORTCUT_CLOSE_APP_TAP(2081),
+
@UiEvent(doc = "User tapped on pause app system shortcut.")
LAUNCHER_SYSTEM_SHORTCUT_PAUSE_TAP(521),
diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java
index c251114..de74ae8 100644
--- a/src/com/android/launcher3/model/BaseLauncherBinder.java
+++ b/src/com/android/launcher3/model/BaseLauncherBinder.java
@@ -19,8 +19,10 @@
import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED;
import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
import static com.android.launcher3.Flags.enableWorkspaceInflation;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
import static com.android.launcher3.model.ItemInstallQueue.FLAG_LOADER_RUNNING;
-import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
+import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER;
+import static com.android.launcher3.model.ModelUtils.currentScreenContentFilter;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -44,11 +46,11 @@
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
+import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.ItemInflater;
import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.util.LooperIdleLock;
@@ -102,14 +104,12 @@
Trace.beginSection("BaseLauncherBinder#bindWorkspace");
try {
// Save a copy of all the bg-thread collections
- ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
- ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
+ IntSparseArrayMap<ItemInfo> itemsIdMap;
final IntArray orderedScreenIds = new IntArray();
ArrayList<FixedContainerItems> extraItems = new ArrayList<>();
final int workspaceItemCount;
synchronized (mBgDataModel) {
- workspaceItems.addAll(mBgDataModel.workspaceItems);
- appWidgets.addAll(mBgDataModel.appWidgets);
+ itemsIdMap = mBgDataModel.itemsIdMap.clone();
orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens());
mBgDataModel.extraItems.forEach(extraItems::add);
if (incrementBindId) {
@@ -122,7 +122,7 @@
for (Callbacks cb : mCallbacksList) {
new UnifiedWorkspaceBinder(cb, mUiExecutor, mApp, mBgDataModel, mMyBindingId,
- workspaceItems, appWidgets, extraItems, orderedScreenIds)
+ itemsIdMap, extraItems, orderedScreenIds)
.bind(isBindSync, workspaceItemCount);
}
} finally {
@@ -258,8 +258,7 @@
private final BgDataModel mBgDataModel;
private final int mMyBindingId;
- private final ArrayList<ItemInfo> mWorkspaceItems;
- private final ArrayList<LauncherAppWidgetInfo> mAppWidgets;
+ private final IntSparseArrayMap<ItemInfo> mItemIdMap;
private final IntArray mOrderedScreenIds;
private final ArrayList<FixedContainerItems> mExtraItems;
@@ -268,8 +267,7 @@
LauncherAppState app,
BgDataModel bgDataModel,
int myBindingId,
- ArrayList<ItemInfo> workspaceItems,
- ArrayList<LauncherAppWidgetInfo> appWidgets,
+ IntSparseArrayMap<ItemInfo> itemIdMap,
ArrayList<FixedContainerItems> extraItems,
IntArray orderedScreenIds) {
mCallbacks = callbacks;
@@ -277,8 +275,7 @@
mApp = app;
mBgDataModel = bgDataModel;
mMyBindingId = myBindingId;
- mWorkspaceItems = workspaceItems;
- mAppWidgets = appWidgets;
+ mItemIdMap = itemIdMap;
mExtraItems = extraItems;
mOrderedScreenIds = orderedScreenIds;
}
@@ -294,10 +291,15 @@
ArrayList<ItemInfo> currentAppWidgets = new ArrayList<>();
ArrayList<ItemInfo> otherAppWidgets = new ArrayList<>();
- filterCurrentWorkspaceItems(currentScreenIds, mWorkspaceItems, currentWorkspaceItems,
- otherWorkspaceItems);
- filterCurrentWorkspaceItems(currentScreenIds, mAppWidgets, currentAppWidgets,
- otherAppWidgets);
+ Predicate<ItemInfo> currentScreenCheck = currentScreenContentFilter(currentScreenIds);
+ mItemIdMap.forEach(item -> {
+ if (currentScreenCheck.test(item)) {
+ (WIDGET_FILTER.test(item) ? currentAppWidgets : currentWorkspaceItems)
+ .add(item);
+ } else if (item.container == CONTAINER_DESKTOP) {
+ (WIDGET_FILTER.test(item) ? otherAppWidgets : otherWorkspaceItems).add(item);
+ }
+ });
final InvariantDeviceProfile idp = mApp.getInvariantDeviceProfile();
sortWorkspaceItemsSpatially(idp, currentWorkspaceItems);
sortWorkspaceItemsSpatially(idp, otherWorkspaceItems);
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index b9b1e98..ddc775d 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -20,6 +20,11 @@
import static com.android.launcher3.BuildConfig.QSB_ON_FIRST_SCREEN;
import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED;
import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
import static com.android.launcher3.shortcuts.ShortcutRequest.PINNED;
@@ -31,7 +36,6 @@
import android.content.pm.ShortcutInfo;
import android.os.UserHandle;
import android.text.TextUtils;
-import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.view.View;
@@ -39,16 +43,12 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.BuildConfig;
import com.android.launcher3.Workspace;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.AppPairInfo;
import com.android.launcher3.model.data.CollectionInfo;
-import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.shortcuts.ShortcutKey;
@@ -69,7 +69,6 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -93,22 +92,6 @@
public final IntSparseArrayMap<ItemInfo> itemsIdMap = new IntSparseArrayMap<>();
/**
- * List of all the folders and shortcuts directly on the home screen (no widgets
- * or shortcuts within folders).
- */
- public final ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
-
- /**
- * All LauncherAppWidgetInfo created by LauncherModel.
- */
- public final ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
-
- /**
- * Map of id to CollectionInfos of all the folders or app pairs created by LauncherModel
- */
- public final IntSparseArrayMap<CollectionInfo> collections = new IntSparseArrayMap<>();
-
- /**
* Extra container based items
*/
public final IntSparseArrayMap<FixedContainerItems> extraItems = new IntSparseArrayMap<>();
@@ -144,9 +127,6 @@
* Clears all the data
*/
public synchronized void clear() {
- workspaceItems.clear();
- appWidgets.clear();
- collections.clear();
itemsIdMap.clear();
deepShortcutMap.clear();
extraItems.clear();
@@ -158,7 +138,7 @@
public synchronized IntArray collectWorkspaceScreens() {
IntSet screenSet = new IntSet();
for (ItemInfo item: itemsIdMap) {
- if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ if (item.container == CONTAINER_DESKTOP) {
screenSet.add(item.screenId);
}
}
@@ -173,26 +153,14 @@
public synchronized void dump(String prefix, FileDescriptor fd, PrintWriter writer,
String[] args) {
writer.println(prefix + "Data Model:");
- writer.println(prefix + " ---- workspace items ");
- for (int i = 0; i < workspaceItems.size(); i++) {
- writer.println(prefix + '\t' + workspaceItems.get(i).toString());
- }
- writer.println(prefix + " ---- appwidget items ");
- for (int i = 0; i < appWidgets.size(); i++) {
- writer.println(prefix + '\t' + appWidgets.get(i).toString());
- }
- writer.println(prefix + " ---- collection items ");
- for (int i = 0; i < collections.size(); i++) {
- writer.println(prefix + '\t' + collections.valueAt(i).toString());
+ writer.println(prefix + " ---- items id map ");
+ for (int i = 0; i < itemsIdMap.size(); i++) {
+ writer.println(prefix + '\t' + itemsIdMap.valueAt(i).toString());
}
writer.println(prefix + " ---- extra items ");
for (int i = 0; i < extraItems.size(); i++) {
writer.println(prefix + '\t' + extraItems.valueAt(i).toString());
}
- writer.println(prefix + " ---- items id map ");
- for (int i = 0; i < itemsIdMap.size(); i++) {
- writer.println(prefix + '\t' + itemsIdMap.valueAt(i).toString());
- }
if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
writer.println(prefix + "shortcut counts ");
@@ -207,94 +175,38 @@
removeItem(context, Arrays.asList(items));
}
- public synchronized void removeItem(Context context, Iterable<? extends ItemInfo> items) {
- ArraySet<UserHandle> updatedDeepShortcuts = new ArraySet<>();
- for (ItemInfo item : items) {
- switch (item.itemType) {
- case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
- case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR:
- collections.remove(item.id);
- if (FeatureFlags.IS_STUDIO_BUILD) {
- for (ItemInfo info : itemsIdMap) {
- if (info.container == item.id) {
- // We are deleting a collection which still contains items that
- // think they are contained by that collection.
- String msg = "deleting a collection (" + item + ") which still "
- + "contains items (" + info + ")";
- Log.e(TAG, msg);
- }
- }
- }
- workspaceItems.remove(item);
- break;
- case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
- updatedDeepShortcuts.add(item.user);
- // Fall through.
- }
- case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
- workspaceItems.remove(item);
- break;
- case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
- case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
- appWidgets.remove(item);
- break;
- }
- itemsIdMap.remove(item.id);
+ public synchronized void removeItem(Context context, List<? extends ItemInfo> items) {
+ if (BuildConfig.IS_STUDIO_BUILD) {
+ items.stream()
+ .filter(item -> item.itemType == ITEM_TYPE_FOLDER
+ || item.itemType == ITEM_TYPE_APP_PAIR)
+ .forEach(item -> itemsIdMap.stream()
+ .filter(info -> info.container == item.id)
+ // We are deleting a collection which still contains items that
+ // think they are contained by that collection.
+ .forEach(info -> Log.e(TAG,
+ "deleting a collection (" + item + ") which still contains"
+ + " items (" + info + ")")));
}
- updatedDeepShortcuts.forEach(user -> updateShortcutPinnedState(context, user));
+
+ items.forEach(item -> itemsIdMap.remove(item.id));
+ items.stream().map(info -> info.user).distinct().forEach(
+ user -> updateShortcutPinnedState(context, user));
}
public synchronized void addItem(Context context, ItemInfo item, boolean newItem) {
- addItem(context, item, newItem, null);
- }
-
- public synchronized void addItem(
- Context context, ItemInfo item, boolean newItem, @Nullable LoaderMemoryLogger logger) {
- if (logger != null) {
- logger.addLog(
- Log.DEBUG,
- TAG,
- String.format("Adding item to ID map: %s", item.toString()),
- /* stackTrace= */ null);
- }
itemsIdMap.put(item.id, item);
- switch (item.itemType) {
- case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
- collections.put(item.id, (FolderInfo) item);
- workspaceItems.add(item);
- break;
- case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR:
- collections.put(item.id, (AppPairInfo) item);
- // Fall through here. App pairs are both containers (like folders) and containable
- // items (can be placed in folders). So we need to add app pairs to the folders
- // array (above) but also verify the existence of their container, like regular
- // apps (below).
- case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
- case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
- if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
- item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
- workspaceItems.add(item);
- } else {
- if (newItem) {
- if (!collections.containsKey(item.container)) {
- // Adding an item to a nonexistent collection.
- String msg = "attempted to add item: " + item + " to a nonexistent app"
- + " collection";
- Log.e(TAG, msg);
- }
- } else {
- findOrMakeFolder(item.container).add(item);
- }
- }
- break;
- case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
- case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
- appWidgets.add((LauncherAppWidgetInfo) item);
- break;
- }
- if (newItem && item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+ if (newItem && item.itemType == ITEM_TYPE_DEEP_SHORTCUT) {
updateShortcutPinnedState(context, item.user);
}
+ if (BuildConfig.IS_DEBUG_DEVICE
+ && newItem
+ && item.container != CONTAINER_DESKTOP
+ && item.container != CONTAINER_HOTSEAT
+ && !(itemsIdMap.get(item.container) instanceof CollectionInfo)) {
+ // Adding an item to a nonexistent collection.
+ Log.e(TAG, "attempted to add item: " + item + " to a nonexistent app collection");
+ }
}
/**
@@ -334,7 +246,7 @@
Map<String, Set<String>> modelMap = Stream.concat(
// Model shortcuts
itemStream.build()
- .filter(wi -> wi.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
+ .filter(wi -> wi.itemType == ITEM_TYPE_DEEP_SHORTCUT)
.map(ShortcutKey::fromItemInfo),
// Pending shortcuts
ItemInstallQueue.INSTANCE.get(context).getPendingShortcuts(user))
@@ -375,24 +287,6 @@
}
/**
- * Return an existing FolderInfo object if we have encountered this ID previously,
- * or make a new one.
- */
- public synchronized CollectionInfo findOrMakeFolder(int id) {
- // See if a placeholder was created for us already
- CollectionInfo collectionInfo = collections.get(id);
- if (collectionInfo == null) {
- // No placeholder -- create a new blank folder instance. At this point, we don't know
- // if the desired container is supposed to be a folder or an app pair. In the case that
- // it is an app pair, the blank folder will be replaced by a blank app pair when the app
- // pair is getting processed, in WorkspaceItemProcessor.processFolderOrAppPair().
- collectionInfo = new FolderInfo();
- collections.put(id, collectionInfo);
- }
- return collectionInfo;
- }
-
- /**
* Clear all the deep shortcut counts for the given package, and re-add the new shortcut counts.
*/
public synchronized void updateDeepShortcutCounts(
@@ -424,16 +318,6 @@
}
/**
- * Returns a list containing all workspace items including widgets.
- */
- public synchronized ArrayList<ItemInfo> getAllWorkspaceItems() {
- ArrayList<ItemInfo> items = new ArrayList<>(workspaceItems.size() + appWidgets.size());
- items.addAll(workspaceItems);
- items.addAll(appWidgets);
- return items;
- }
-
- /**
* Calls the provided {@code op} for all workspaceItems in the in-memory model (both persisted
* items and dynamic/predicted items for the provided {@code userHandle}.
* Note the call is not synchronized over the model, that should be handled by the called.
@@ -533,9 +417,9 @@
* Binds updated incremental download progress
*/
default void bindIncrementalDownloadProgressUpdated(AppInfo app) { }
- default void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) { }
- default void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets) { }
- default void bindRestoreItemsChange(HashSet<ItemInfo> updates) { }
+
+ /** Called when a runtime property of the ItemInfo is updated due to some system event */
+ default void bindItemsUpdated(Set<ItemInfo> updates) { }
default void bindWorkspaceComponentsRemoved(Predicate<ItemInfo> matcher) { }
/**
diff --git a/src/com/android/launcher3/model/CacheDataUpdatedTask.java b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
index b544b91..48934e2 100644
--- a/src/com/android/launcher3/model/CacheDataUpdatedTask.java
+++ b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
@@ -15,6 +15,9 @@
*/
package com.android.launcher3.model;
+import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
+import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER;
+
import android.content.ComponentName;
import android.os.UserHandle;
@@ -23,6 +26,8 @@
import com.android.launcher3.LauncherModel.ModelUpdateTask;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import java.util.ArrayList;
@@ -55,7 +60,7 @@
public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
@NonNull AllAppsList apps) {
IconCache iconCache = taskController.getApp().getIconCache();
- ArrayList<WorkspaceItemInfo> updatedShortcuts = new ArrayList<>();
+ ArrayList<ItemInfo> updatedItems = new ArrayList<>();
synchronized (dataModel) {
dataModel.forAllWorkspaceItemInfos(mUser, si -> {
@@ -64,12 +69,25 @@
&& isValidShortcut(si) && cn != null
&& mPackages.contains(cn.getPackageName())) {
iconCache.getTitleAndIcon(si, si.getMatchingLookupFlag());
- updatedShortcuts.add(si);
+ updatedItems.add(si);
}
});
+
+ dataModel.itemsIdMap.stream()
+ .filter(WIDGET_FILTER)
+ .filter(item -> mUser.equals(item.user))
+ .map(item -> (LauncherAppWidgetInfo) item)
+ .filter(widget -> mPackages.contains(widget.providerName.getPackageName())
+ && widget.pendingItemInfo != null)
+ .forEach(widget -> {
+ iconCache.getTitleAndIconForApp(
+ widget.pendingItemInfo, DEFAULT_LOOKUP_FLAG);
+ updatedItems.add(widget);
+ });
+
apps.updateIconsAndLabels(mPackages, mUser);
}
- taskController.bindUpdatedWorkspaceItems(updatedShortcuts);
+ taskController.bindUpdatedWorkspaceItems(updatedItems);
taskController.bindApplicationsIfNeeded();
}
diff --git a/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt b/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt
index aa62c32..6ad52ea 100644
--- a/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt
+++ b/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt
@@ -30,7 +30,6 @@
import com.android.launcher3.model.data.ItemInfo
import com.android.launcher3.model.data.LauncherAppWidgetInfo
import com.android.launcher3.model.data.WorkspaceItemInfo
-import com.android.launcher3.pm.InstallSessionHelper
import com.android.launcher3.util.Executors
import com.android.launcher3.util.PackageManagerHelper
import com.android.launcher3.util.PackageUserKey
@@ -80,21 +79,22 @@
packageManagerHelper: PackageManagerHelper,
firstScreenItems: List<ItemInfo>,
userKeyToSessionMap: Map<PackageUserKey, SessionInfo>,
- allWidgets: List<LauncherAppWidgetInfo>
+ allWidgets: List<ItemInfo>,
): List<FirstScreenBroadcastModel> {
// installers for installing items
- val pendingItemInstallerMap: Map<String, MutableSet<String>> =
+ val pendingItemInstallerMap: Map<String, Set<String>> =
createPendingItemsMap(userKeyToSessionMap)
+
val installingPackages = pendingItemInstallerMap.values.flatten().toSet()
// installers for installed items on first screen
- val installedItemInstallerMap: Map<String, MutableSet<ItemInfo>> =
+ val installedItemInstallerMap: Map<String, List<ItemInfo>> =
createInstalledItemsMap(firstScreenItems, installingPackages, packageManagerHelper)
// installers for widgets on all screens
- val allInstalledWidgetsMap: Map<String, MutableSet<LauncherAppWidgetInfo>> =
- createAllInstalledWidgetsMap(allWidgets, installingPackages, packageManagerHelper)
+ val allInstalledWidgetsMap: Map<String, List<ItemInfo>> =
+ createInstalledItemsMap(allWidgets, installingPackages, packageManagerHelper)
val allInstallers: Set<String> =
pendingItemInstallerMap.keys +
@@ -131,39 +131,39 @@
context,
0 /* requestCode */,
Intent(),
- PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
- )
+ PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE,
+ ),
)
.putStringArrayListExtra(
PENDING_COLLECTION_ITEM_EXTRA,
- ArrayList(model.pendingCollectionItems)
+ ArrayList(model.pendingCollectionItems),
)
.putStringArrayListExtra(
PENDING_WORKSPACE_ITEM_EXTRA,
- ArrayList(model.pendingWorkspaceItems)
+ ArrayList(model.pendingWorkspaceItems),
)
.putStringArrayListExtra(
PENDING_HOTSEAT_ITEM_EXTRA,
- ArrayList(model.pendingHotseatItems)
+ ArrayList(model.pendingHotseatItems),
)
.putStringArrayListExtra(
PENDING_WIDGET_ITEM_EXTRA,
- ArrayList(model.pendingWidgetItems)
+ ArrayList(model.pendingWidgetItems),
)
.putStringArrayListExtra(
INSTALLED_WORKSPACE_ITEMS_EXTRA,
- ArrayList(model.installedWorkspaceItems)
+ ArrayList(model.installedWorkspaceItems),
)
.putStringArrayListExtra(
INSTALLED_HOTSEAT_ITEMS_EXTRA,
- ArrayList(model.installedHotseatItems)
+ ArrayList(model.installedHotseatItems),
)
.putStringArrayListExtra(
ALL_INSTALLED_WIDGETS_ITEM_EXTRA,
ArrayList(
model.firstScreenInstalledWidgets +
model.secondaryScreenInstalledWidgets
- )
+ ),
)
context.sendBroadcast(intent)
}
@@ -172,66 +172,46 @@
/** Maps Installer packages to Set of app packages from install sessions */
private fun createPendingItemsMap(
userKeyToSessionMap: Map<PackageUserKey, SessionInfo>
- ): Map<String, MutableSet<String>> {
+ ): Map<String, Set<String>> {
val myUser = Process.myUserHandle()
- val result = mutableMapOf<String, MutableSet<String>>()
- userKeyToSessionMap.forEach { entry ->
- if (!myUser.equals(InstallSessionHelper.getUserHandle(entry.value))) return@forEach
- val installer = entry.value.installerPackageName
- val appPackage = entry.value.appPackageName
- if (installer.isNullOrEmpty() || appPackage.isNullOrEmpty()) return@forEach
- result.getOrPut(installer) { mutableSetOf() }.add(appPackage)
- }
- return result
- }
-
- /**
- * Maps Installer packages to Set of ItemInfo from first screen. Filter out installing packages.
- */
- private fun createInstalledItemsMap(
- firstScreenItems: List<ItemInfo>,
- installingPackages: Set<String>,
- packageManagerHelper: PackageManagerHelper
- ): Map<String, MutableSet<ItemInfo>> {
- val result = mutableMapOf<String, MutableSet<ItemInfo>>()
- firstScreenItems.forEach { item ->
- val appPackage = getPackageName(item) ?: return@forEach
- if (installingPackages.contains(appPackage)) return@forEach
- val installer = packageManagerHelper.getAppInstallerPackage(appPackage)
- if (installer.isNullOrEmpty()) return@forEach
- result.getOrPut(installer) { mutableSetOf() }.add(item)
- }
- return result
- }
-
- /**
- * Maps Installer packages to Set of AppWidget packages installed on all screens. Filter out
- * installing packages.
- */
- private fun createAllInstalledWidgetsMap(
- allWidgets: List<LauncherAppWidgetInfo>,
- installingPackages: Set<String>,
- packageManagerHelper: PackageManagerHelper
- ): Map<String, MutableSet<LauncherAppWidgetInfo>> {
- val result = mutableMapOf<String, MutableSet<LauncherAppWidgetInfo>>()
- allWidgets
- .sortedBy { widget -> widget.screenId }
- .forEach { widget ->
- val appPackage = getPackageName(widget) ?: return@forEach
- if (installingPackages.contains(appPackage)) return@forEach
- val installer = packageManagerHelper.getAppInstallerPackage(appPackage)
- if (installer.isNullOrEmpty()) return@forEach
- result.getOrPut(installer) { mutableSetOf() }.add(widget)
+ return userKeyToSessionMap.values
+ .filter {
+ it.user == myUser &&
+ !it.installerPackageName.isNullOrEmpty() &&
+ !it.appPackageName.isNullOrEmpty()
}
- return result
+ .groupBy(
+ keySelector = { it.installerPackageName },
+ valueTransform = { it.appPackageName },
+ )
+ .mapValues { it.value.filterNotNull().toSet() } as Map<String, Set<String>>
}
+ /** Maps Installer packages to Set of ItemInfos. Filter out installing packages. */
+ private fun createInstalledItemsMap(
+ allItems: Iterable<ItemInfo>,
+ installingPackages: Set<String>,
+ packageManagerHelper: PackageManagerHelper,
+ ): Map<String, List<ItemInfo>> =
+ allItems
+ .sortedBy { it.screenId }
+ .groupByTo(mutableMapOf()) {
+ getPackageName(it)?.let { pkg ->
+ if (installingPackages.contains(pkg)) {
+ null
+ } else {
+ packageManagerHelper.getAppInstallerPackage(pkg)
+ }
+ }
+ }
+ .apply { remove(null) } as Map<String, List<ItemInfo>>
+
/**
* Add first screen Pending Items from Map to [FirstScreenBroadcastModel] for given installer
*/
private fun FirstScreenBroadcastModel.addPendingItems(
installingItems: Set<String>?,
- firstScreenItems: List<ItemInfo>
+ firstScreenItems: List<ItemInfo>,
) {
if (installingItems == null) return
for (info in firstScreenItems) {
@@ -251,7 +231,7 @@
*/
private fun FirstScreenBroadcastModel.addInstalledItems(
installer: String,
- installedItemInstallerMap: Map<String, Set<ItemInfo>>,
+ installedItemInstallerMap: Map<String, List<ItemInfo>>,
) {
installedItemInstallerMap[installer]?.forEach { info ->
val packageName: String = getPackageName(info) ?: return@forEach
@@ -265,7 +245,7 @@
/** Add Widgets on every screen from Map to [FirstScreenBroadcastModel] for given installer */
private fun FirstScreenBroadcastModel.addAllScreenWidgets(
installer: String,
- allInstalledWidgetsMap: Map<String, Set<LauncherAppWidgetInfo>>
+ allInstalledWidgetsMap: Map<String, List<ItemInfo>>,
) {
allInstalledWidgetsMap[installer]?.forEach { widget ->
val packageName: String = getPackageName(widget) ?: return@forEach
@@ -279,7 +259,7 @@
private fun FirstScreenBroadcastModel.addCollectionItems(
info: ItemInfo,
- installingPackages: Set<String>
+ installingPackages: Set<String>,
) {
if (info !is CollectionInfo) return
pendingCollectionItems.addAll(
@@ -336,7 +316,7 @@
Log.d(
TAG,
"Sending First Screen Broadcast for installer=$installerPackage" +
- ", total packages=${getTotalItemCount()}"
+ ", total packages=${getTotalItemCount()}",
)
pendingCollectionItems.forEach {
Log.d(TAG, "$installerPackage:Pending Collection item:$it")
@@ -361,15 +341,7 @@
}
}
- private fun getPackageName(info: ItemInfo): String? {
- var packageName: String? = null
- if (info is LauncherAppWidgetInfo) {
- info.providerName?.let { packageName = info.providerName.packageName }
- } else if (info.targetComponent != null) {
- packageName = info.targetComponent?.packageName
- }
- return packageName
- }
+ private fun getPackageName(info: ItemInfo): String? = info.targetComponent?.packageName
/**
* Clone the provided list on UI thread. This is used for [FolderInfo.getContents] which is
diff --git a/src/com/android/launcher3/model/GridSizeMigrationDBController.java b/src/com/android/launcher3/model/GridSizeMigrationDBController.java
index 8cbe764..b291421 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationDBController.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationDBController.java
@@ -17,14 +17,13 @@
package com.android.launcher3.model;
import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
-import static com.android.launcher3.Flags.oneGridSpecs;
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.LauncherSettings.Favorites.TMP_TABLE;
import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
import static com.android.launcher3.model.LoaderTask.SMARTSPACE_ON_HOME_SCREEN;
import static com.android.launcher3.provider.LauncherDbUtils.copyTable;
import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
-import static com.android.launcher3.provider.LauncherDbUtils.shiftTableByXCells;
+import static com.android.launcher3.provider.LauncherDbUtils.shiftWorkspaceByXCells;
import android.content.ComponentName;
import android.content.ContentValues;
@@ -39,6 +38,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
+import com.android.launcher3.Flags;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherSettings;
@@ -127,7 +127,7 @@
return true;
}
- boolean shouldMigrateToStrictlyTallerGrid = isDestNewDb
+ boolean shouldMigrateToStrictlyTallerGrid = (Flags.oneGridSpecs() || isDestNewDb)
&& srcDeviceState.getColumns().equals(destDeviceState.getColumns())
&& srcDeviceState.getRows() < destDeviceState.getRows();
if (shouldMigrateToStrictlyTallerGrid) {
@@ -142,8 +142,8 @@
if (shouldMigrateToStrictlyTallerGrid) {
// We want to add the extra row(s) to the top of the screen, so we shift the grid
// down.
- if (oneGridSpecs()) {
- shiftTableByXCells(
+ if (Flags.oneGridSpecs()) {
+ shiftWorkspaceByXCells(
target.getWritableDatabase(),
(destDeviceState.getRows() - srcDeviceState.getRows()),
TABLE_NAME);
@@ -159,8 +159,8 @@
DbReader destReader = new DbReader(t.getDb(), TABLE_NAME, context);
Point targetSize = new Point(destDeviceState.getColumns(), destDeviceState.getRows());
- migrate(target, srcReader, destReader, destDeviceState.getNumHotseat(),
- targetSize, srcDeviceState, destDeviceState);
+ migrate(target, srcReader, destReader, srcDeviceState.getNumHotseat(),
+ destDeviceState.getNumHotseat(), targetSize, srcDeviceState, destDeviceState);
dropTable(t.getDb(), TMP_TABLE);
t.commit();
return true;
@@ -181,19 +181,26 @@
public static boolean migrate(
@NonNull DatabaseHelper helper,
@NonNull final DbReader srcReader, @NonNull final DbReader destReader,
- final int destHotseatSize, @NonNull final Point targetSize,
+ final int srcHotseatSize, final int destHotseatSize, @NonNull final Point targetSize,
@NonNull final DeviceGridState srcDeviceState,
@NonNull final DeviceGridState destDeviceState) {
final List<DbEntry> srcHotseatItems = srcReader.loadHotseatEntries();
final List<DbEntry> srcWorkspaceItems = srcReader.loadAllWorkspaceEntries();
final List<DbEntry> dstHotseatItems = destReader.loadHotseatEntries();
+ // We want to filter out the hotseat items that are placed beyond the size of the source
+ // grid as we always want to keep those extra items from the destination grid.
+ List<DbEntry> filteredDstHotseatItems = dstHotseatItems;
+ if (srcHotseatSize < destHotseatSize) {
+ filteredDstHotseatItems = filteredDstHotseatItems.stream()
+ .filter(entry -> entry.screenId < srcHotseatSize).toList();
+ }
final List<DbEntry> dstWorkspaceItems = destReader.loadAllWorkspaceEntries();
final List<DbEntry> hotseatToBeAdded = new ArrayList<>(1);
final List<DbEntry> workspaceToBeAdded = new ArrayList<>(1);
final IntArray toBeRemoved = new IntArray();
- calcDiff(srcHotseatItems, dstHotseatItems, hotseatToBeAdded, toBeRemoved);
+ calcDiff(srcHotseatItems, filteredDstHotseatItems, hotseatToBeAdded, toBeRemoved);
calcDiff(srcWorkspaceItems, dstWorkspaceItems, workspaceToBeAdded, toBeRemoved);
final int trgX = targetSize.x;
@@ -421,12 +428,13 @@
}
private static void solveHotseatPlacement(
- @NonNull final DatabaseHelper helper, final int hotseatSize,
+ @NonNull final DatabaseHelper helper,
+ final int dstHotseatSize,
@NonNull final DbReader srcReader, @NonNull final DbReader destReader,
@NonNull final List<DbEntry> placedHotseatItems,
@NonNull final List<DbEntry> itemsToPlace, List<Integer> idsInUse) {
- final boolean[] occupied = new boolean[hotseatSize];
+ final boolean[] occupied = new boolean[dstHotseatSize];
for (DbEntry entry : placedHotseatItems) {
occupied[entry.screenId] = true;
}
diff --git a/src/com/android/launcher3/model/GridSizeMigrationLogic.kt b/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
index dfda8e4..9586bf3 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
+++ b/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
@@ -21,7 +21,6 @@
import android.util.Log
import androidx.annotation.VisibleForTesting
import com.android.launcher3.Flags
-import com.android.launcher3.Flags.oneGridSpecs
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.LauncherPrefs.Companion.get
import com.android.launcher3.LauncherPrefs.Companion.getPrefs
@@ -35,7 +34,7 @@
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction
import com.android.launcher3.provider.LauncherDbUtils.copyTable
import com.android.launcher3.provider.LauncherDbUtils.dropTable
-import com.android.launcher3.provider.LauncherDbUtils.shiftTableByXCells
+import com.android.launcher3.provider.LauncherDbUtils.shiftWorkspaceByXCells
import com.android.launcher3.util.CellAndSpan
import com.android.launcher3.util.GridOccupancy
import com.android.launcher3.util.IntArray
@@ -81,8 +80,8 @@
// down.
if (shouldMigrateToStrtictlyTallerGrid) {
Log.d(TAG, "Migrating to strictly taller grid")
- if (oneGridSpecs()) {
- shiftTableByXCells(
+ if (Flags.oneGridSpecs()) {
+ shiftWorkspaceByXCells(
target.writableDatabase,
(destDeviceState.rows - srcDeviceState.rows),
TABLE_NAME,
@@ -107,7 +106,14 @@
val idsInUse = mutableListOf<Int>()
// Migrate hotseat.
- migrateHotseat(destDeviceState.numHotseat, srcReader, destReader, target, idsInUse)
+ migrateHotseat(
+ srcDeviceState.numHotseat,
+ destDeviceState.numHotseat,
+ srcReader,
+ destReader,
+ target,
+ idsInUse,
+ )
// Migrate workspace.
migrateWorkspace(srcReader, destReader, target, targetSize, idsInUse)
@@ -134,6 +140,7 @@
/** Handles hotseat migration. */
@VisibleForTesting
fun migrateHotseat(
+ srcHotseatSize: Int,
destHotseatSize: Int,
srcReader: DbReader,
destReader: DbReader,
@@ -143,17 +150,24 @@
val srcHotseatItems = srcReader.loadHotseatEntries()
val dstHotseatItems = destReader.loadHotseatEntries()
- val hotseatToBeAdded = getItemsToBeAdded(srcHotseatItems, dstHotseatItems)
- val toBeRemoved = IntArray()
- toBeRemoved.addAll(getItemsToBeRemoved(srcHotseatItems, dstHotseatItems))
+ // We want to filter out the hotseat items that are placed beyond the size of the source
+ // grid as we always want to keep those extra items from the destination grid.
+ var filteredDstHotseatItems = dstHotseatItems
+ if (srcHotseatSize < destHotseatSize) {
+ filteredDstHotseatItems =
+ filteredDstHotseatItems.filter { entry -> entry.screenId < srcHotseatSize }
+ }
+
+ val itemsToBeAdded = getItemsToBeAdded(srcHotseatItems, filteredDstHotseatItems)
+ val itemsToBeRemoved = getItemsToBeRemoved(srcHotseatItems, filteredDstHotseatItems)
if (DEBUG) {
Log.d(
TAG,
"""Start hotseat migration:
- |Removing Hotseat Items: [${dstHotseatItems.filter { toBeRemoved.contains(it.id) }
+ |Removing Hotseat Items: [${filteredDstHotseatItems.filter { itemsToBeRemoved.contains(it.id) }
.joinToString(",\n") { it.toString() }}]
- |Adding Hotseat Items: [${hotseatToBeAdded
+ |Adding Hotseat Items: [${itemsToBeAdded
.joinToString(",\n") { it.toString() }}]
|"""
.trimMargin(),
@@ -161,16 +175,16 @@
}
// Removes the items that we need to remove from the destination DB.
- if (!toBeRemoved.isEmpty) {
+ if (!itemsToBeRemoved.isEmpty) {
GridSizeMigrationDBController.removeEntryFromDb(
destReader.mDb,
destReader.mTableName,
- toBeRemoved,
+ itemsToBeRemoved,
)
}
placeHotseatItems(
- hotseatToBeAdded,
+ itemsToBeAdded,
dstHotseatItems,
destHotseatSize,
helper,
@@ -359,7 +373,7 @@
srcDeviceState: DeviceGridState,
destDeviceState: DeviceGridState,
): Boolean {
- return isDestNewDb &&
+ return (Flags.oneGridSpecs() || isDestNewDb) &&
srcDeviceState.columns == destDeviceState.columns &&
srcDeviceState.rows < destDeviceState.rows
}
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 536d4c9..1623881 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -16,6 +16,11 @@
package com.android.launcher3.model;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
@@ -48,6 +53,8 @@
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.CollectionInfo;
+import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.IconRequestInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -84,6 +91,11 @@
private final IntArray mRestoredRows = new IntArray();
private final IntSparseArrayMap<GridOccupancy> mOccupied = new IntSparseArrayMap<>();
+ // CollectionInfo objects, which have not yet been loaded from the DB, but are expected to
+ // found eventually as the loading progresses
+ private final IntSparseArrayMap<CollectionInfo> mPendingCollectionInfo =
+ new IntSparseArrayMap<>();
+
private final int mIconIndex;
public final int mTitleIndex;
@@ -479,8 +491,26 @@
info.cellY = getInt(mCellYIndex);
}
- public void checkAndAddItem(ItemInfo info, BgDataModel dataModel) {
- checkAndAddItem(info, dataModel, null);
+ /**
+ * Return an existing FolderInfo object if we have encountered this ID previously,
+ * or make a new one.
+ */
+ public CollectionInfo findOrMakeFolder(int id, BgDataModel dataModel) {
+ // See if a placeholder was created for us already
+ ItemInfo info = dataModel.itemsIdMap.get(id);
+ if (info instanceof CollectionInfo c) return c;
+
+ CollectionInfo pending = mPendingCollectionInfo.get(id);
+ if (pending != null) return pending;
+
+ // No placeholder -- create a new blank folder instance. At this point, we don't know
+ // if the desired container is supposed to be a folder or an app pair. In the case that
+ // it is an app pair, the blank folder will be replaced by a blank app pair when the app
+ // pair is getting processed, in WorkspaceItemProcessor.processFolderOrAppPair().
+ pending = new FolderInfo();
+ pending.id = id;
+ mPendingCollectionInfo.put(id, pending);
+ return pending;
}
/**
@@ -495,7 +525,21 @@
ShortcutKey.fromItemInfo(info);
}
if (checkItemPlacement(info, dataModel.isFirstPagePinnedItemEnabled)) {
- dataModel.addItem(mContext, info, false, logger);
+ if (logger != null) {
+ logger.addLog(
+ Log.DEBUG,
+ TAG,
+ String.format("Adding item to ID map: %s", info),
+ /* stackTrace= */ null);
+ }
+ dataModel.addItem(mContext, info, false);
+ if ((info.itemType == ITEM_TYPE_APP_PAIR
+ || info.itemType == ITEM_TYPE_DEEP_SHORTCUT
+ || info.itemType == ITEM_TYPE_APPLICATION)
+ && info.container != CONTAINER_DESKTOP
+ && info.container != CONTAINER_HOTSEAT) {
+ findOrMakeFolder(info.container, dataModel).add(info);
+ }
if (mRestoreEventLogger != null) {
mRestoreEventLogger.logSingleFavoritesItemRestored(itemType);
}
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 44b7e8b..fee9696 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -31,7 +31,8 @@
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_WORK_PROFILE_QUIET_MODE_ENABLED;
-import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
+import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER;
+import static com.android.launcher3.model.ModelUtils.currentScreenContentFilter;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
@@ -82,7 +83,6 @@
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.AppPairInfo;
-import com.android.launcher3.model.data.CollectionInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.IconRequestInfo;
import com.android.launcher3.model.data.ItemInfo;
@@ -210,10 +210,10 @@
final int firstScreen = allScreens.get(0);
IntSet firstScreens = IntSet.wrap(firstScreen);
- ArrayList<ItemInfo> allItems = mBgDataModel.getAllWorkspaceItems();
- ArrayList<ItemInfo> firstScreenItems = new ArrayList<>();
- filterCurrentWorkspaceItems(firstScreens, allItems, firstScreenItems,
- new ArrayList<>() /* otherScreenItems are ignored */);
+ List<ItemInfo> firstScreenItems =
+ mBgDataModel.itemsIdMap.stream()
+ .filter(currentScreenContentFilter(firstScreens))
+ .toList();
final int disableArchivingLauncherBroadcast = Settings.Secure.getInt(
mApp.getContext().getContentResolver(),
"disable_launcher_broadcast_installed_apps",
@@ -227,7 +227,7 @@
mPmHelper,
firstScreenItems,
mInstallingPkgsCached,
- mBgDataModel.appWidgets
+ mBgDataModel.itemsIdMap.stream().filter(WIDGET_FILTER).toList()
);
logASplit("Sending first screen broadcast with additional archiving Extras");
FirstScreenBroadcastHelper.sendBroadcastsForModels(mApp.getContext(), broadcastModels);
@@ -523,14 +523,13 @@
* requests high-res icons for the items that are part of an app pair.
*/
private void processAppPairItems() {
- for (CollectionInfo collection : mBgDataModel.collections) {
- if (!(collection instanceof AppPairInfo appPair)) {
- continue;
- }
-
- appPair.getContents().sort(Folder.ITEM_POS_COMPARATOR);
- appPair.fetchHiResIconsIfNeeded(mIconCache);
- }
+ mBgDataModel.itemsIdMap.stream()
+ .filter(item -> item instanceof AppPairInfo)
+ .forEach(item -> {
+ AppPairInfo appPair = (AppPairInfo) item;
+ appPair.getContents().sort(Folder.ITEM_POS_COMPARATOR);
+ appPair.fetchHiResIconsIfNeeded(mIconCache);
+ });
}
/**
@@ -586,8 +585,8 @@
// Sort the folder items, update ranks, and make sure all preview items are high res.
List<FolderGridOrganizer> verifiers = mApp.getInvariantDeviceProfile().supportedProfiles
.stream().map(FolderGridOrganizer::createFolderGridOrganizer).toList();
- for (CollectionInfo collection : mBgDataModel.collections) {
- if (!(collection instanceof FolderInfo folder)) {
+ for (ItemInfo itemInfo : mBgDataModel.itemsIdMap) {
+ if (!(itemInfo instanceof FolderInfo folder)) {
continue;
}
@@ -657,8 +656,6 @@
IntArray deletedFolderIds = mApp.getModel().getModelDbController().deleteEmptyFolders();
synchronized (mBgDataModel) {
for (int folderId : deletedFolderIds) {
- mBgDataModel.workspaceItems.remove(mBgDataModel.collections.get(folderId));
- mBgDataModel.collections.remove(folderId);
mBgDataModel.itemsIdMap.remove(folderId);
}
}
@@ -676,8 +673,6 @@
synchronized (mBgDataModel) {
for (int id : deleted) {
- mBgDataModel.workspaceItems.remove(mBgDataModel.collections.get(id));
- mBgDataModel.collections.remove(id);
mBgDataModel.itemsIdMap.remove(id);
}
}
@@ -819,18 +814,19 @@
private void loadFolderNames() {
FolderNameProvider provider = FolderNameProvider.newInstance(mApp.getContext(),
- mBgAllAppsList.data, mBgDataModel.collections);
+ mBgAllAppsList.data, FolderNameProvider.getCollectionForSuggestions(mBgDataModel));
synchronized (mBgDataModel) {
- for (int i = 0; i < mBgDataModel.collections.size(); i++) {
- FolderNameInfos suggestionInfos = new FolderNameInfos();
- CollectionInfo info = mBgDataModel.collections.valueAt(i);
- if (info instanceof FolderInfo fi && fi.suggestedFolderNames == null) {
- provider.getSuggestedFolderName(mApp.getContext(), fi.getAppContents(),
- suggestionInfos);
- fi.suggestedFolderNames = suggestionInfos;
- }
- }
+ mBgDataModel.itemsIdMap.stream()
+ .filter(item ->
+ item instanceof FolderInfo fi && fi.suggestedFolderNames == null)
+ .forEach(info -> {
+ FolderInfo fi = (FolderInfo) info;
+ FolderNameInfos suggestionInfos = new FolderNameInfos();
+ provider.getSuggestedFolderName(mApp.getContext(), fi.getAppContents(),
+ suggestionInfos);
+ fi.suggestedFolderNames = suggestionInfos;
+ });
}
}
diff --git a/src/com/android/launcher3/model/ModelTaskController.kt b/src/com/android/launcher3/model/ModelTaskController.kt
index fc53343..40ea17d 100644
--- a/src/com/android/launcher3/model/ModelTaskController.kt
+++ b/src/com/android/launcher3/model/ModelTaskController.kt
@@ -22,7 +22,6 @@
import com.android.launcher3.celllayout.CellPosMapper
import com.android.launcher3.model.BgDataModel.FixedContainerItems
import com.android.launcher3.model.data.ItemInfo
-import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.util.PackageUserKey
import com.android.launcher3.widget.model.WidgetsListBaseEntriesBuilder
import java.util.Objects
@@ -51,18 +50,17 @@
*/
fun getModelWriter() = model.getWriter(false /* verifyChanges */, CellPosMapper.DEFAULT, null)
- fun bindUpdatedWorkspaceItems(allUpdates: List<WorkspaceItemInfo>) {
+ fun bindUpdatedWorkspaceItems(allUpdates: Collection<ItemInfo>) {
// Bind workspace items
- val workspaceUpdates =
- allUpdates.stream().filter { info -> info.id != ItemInfo.NO_ID }.toList()
+ val workspaceUpdates = allUpdates.filter { it.id != ItemInfo.NO_ID }.toSet()
if (workspaceUpdates.isNotEmpty()) {
- scheduleCallbackTask { it.bindWorkspaceItemsChanged(workspaceUpdates) }
+ scheduleCallbackTask { it.bindItemsUpdated(workspaceUpdates) }
}
// Bind extra items if any
allUpdates
.stream()
- .mapToInt { info: WorkspaceItemInfo -> info.container }
+ .mapToInt { it.container }
.distinct()
.mapToObj { dataModel.extraItems.get(it) }
.filter { Objects.nonNull(it) }
diff --git a/src/com/android/launcher3/model/ModelUtils.java b/src/com/android/launcher3/model/ModelUtils.java
index 9e72e28..da79982 100644
--- a/src/com/android/launcher3/model/ModelUtils.java
+++ b/src/com/android/launcher3/model/ModelUtils.java
@@ -15,15 +15,15 @@
*/
package com.android.launcher3.model;
-import com.android.launcher3.LauncherSettings;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
+
import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-import java.util.stream.IntStream;
+import java.util.function.Predicate;
/**
* Utils class for {@link com.android.launcher3.LauncherModel}.
@@ -31,54 +31,17 @@
public class ModelUtils {
/**
- * Filters the set of items who are directly or indirectly (via another container) on the
- * specified screen.
+ * Returns a filter for items on hotseat or current screens
*/
- public static <T extends ItemInfo> void filterCurrentWorkspaceItems(
- final IntSet currentScreenIds,
- List<? extends T> allWorkspaceItems,
- List<T> currentScreenItems,
- List<T> otherScreenItems) {
- // Purge any null ItemInfos
- allWorkspaceItems.removeIf(Objects::isNull);
- // Order the set of items by their containers first, this allows use to walk through the
- // list sequentially, build up a list of containers that are in the specified screen,
- // as well as all items in those containers.
- IntSet itemsOnScreen = new IntSet();
- Collections.sort(allWorkspaceItems,
- (lhs, rhs) -> Integer.compare(lhs.container, rhs.container));
- for (T info : allWorkspaceItems) {
- if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
- if (currentScreenIds.contains(info.screenId)) {
- currentScreenItems.add(info);
- itemsOnScreen.add(info.id);
- } else {
- otherScreenItems.add(info);
- }
- } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
- currentScreenItems.add(info);
- itemsOnScreen.add(info.id);
- } else {
- if (itemsOnScreen.contains(info.container)) {
- currentScreenItems.add(info);
- itemsOnScreen.add(info.id);
- } else {
- otherScreenItems.add(info);
- }
- }
- }
+ public static Predicate<ItemInfo> currentScreenContentFilter(IntSet currentScreenIds) {
+ return item -> item.container == CONTAINER_HOTSEAT
+ || (item.container == CONTAINER_DESKTOP
+ && currentScreenIds.contains(item.screenId));
}
/**
- * Iterates though current workspace items and returns available hotseat ranks for prediction.
+ * Returns a filter for widget items
*/
- public static IntArray getMissingHotseatRanks(List<ItemInfo> items, int len) {
- IntSet seen = new IntSet();
- items.stream().filter(
- info -> info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)
- .forEach(i -> seen.add(i.screenId));
- IntArray result = new IntArray(len);
- IntStream.range(0, len).filter(i -> !seen.contains(i)).forEach(result::add);
- return result;
- }
+ public static final Predicate<ItemInfo> WIDGET_FILTER = item ->
+ item.itemType == ITEM_TYPE_APPWIDGET || item.itemType == ITEM_TYPE_CUSTOM_APPWIDGET;
}
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index b477cb1..0332775 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -459,37 +459,14 @@
if (item.container != Favorites.CONTAINER_DESKTOP &&
item.container != Favorites.CONTAINER_HOTSEAT) {
// Item is in a collection, make sure this collection exists
- if (!mBgDataModel.collections.containsKey(item.container)) {
+ if (!(mBgDataModel.itemsIdMap.get(item.container) instanceof CollectionInfo)) {
// An items container is being set to a that of an item which is not in
- // the list of Folders.
+ // the list of collections.
String msg = "item: " + item + " container being set to: " +
item.container + ", not in the list of collections";
Log.e(TAG, msg);
}
}
-
- // Items are added/removed from the corresponding FolderInfo elsewhere, such
- // as in Workspace.onDrop. Here, we just add/remove them from the list of items
- // that are on the desktop, as appropriate
- ItemInfo modelItem = mBgDataModel.itemsIdMap.get(itemId);
- if (modelItem != null &&
- (modelItem.container == Favorites.CONTAINER_DESKTOP ||
- modelItem.container == Favorites.CONTAINER_HOTSEAT)) {
- switch (modelItem.itemType) {
- case Favorites.ITEM_TYPE_APPLICATION:
- case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
- case Favorites.ITEM_TYPE_FOLDER:
- case Favorites.ITEM_TYPE_APP_PAIR:
- if (!mBgDataModel.workspaceItems.contains(modelItem)) {
- mBgDataModel.workspaceItems.add(modelItem);
- }
- break;
- default:
- break;
- }
- } else {
- mBgDataModel.workspaceItems.remove(modelItem);
- }
mVerifier.verifyModel();
}
}
diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
index d238213..a216042 100644
--- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
+++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.model;
+import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER;
+
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -85,16 +87,19 @@
}
});
- for (LauncherAppWidgetInfo widget : dataModel.appWidgets) {
- if (widget.providerName.getPackageName().equals(mInstallInfo.packageName)) {
- widget.installProgress = mInstallInfo.progress;
- updates.add(widget);
- }
- }
+ dataModel.itemsIdMap.stream()
+ .filter(WIDGET_FILTER)
+ .filter(item -> mInstallInfo.user.equals(item.user))
+ .map(item -> (LauncherAppWidgetInfo) item)
+ .filter(widget -> widget.providerName.getPackageName()
+ .equals(mInstallInfo.packageName))
+ .forEach(widget -> {
+ widget.installProgress = mInstallInfo.progress;
+ updates.add(widget);
+ });
if (!updates.isEmpty()) {
- taskController.scheduleCallbackTask(
- callbacks -> callbacks.bindRestoreItemsChange(updates));
+ taskController.bindUpdatedWorkspaceItems(updates);
}
}
}
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index d619965..6bef292 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -18,7 +18,9 @@
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_WORK_PROFILE_QUIET_MODE_ENABLED;
+import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED;
+import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
import static com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
import static com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_RESTORED_ICON;
@@ -212,8 +214,7 @@
// Update shortcut infos
if (mOp == OP_ADD || flagOp != FlagOp.NO_OP) {
- final ArrayList<WorkspaceItemInfo> updatedWorkspaceItems = new ArrayList<>();
- final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<>();
+ final ArrayList<ItemInfo> updatedWorkspaceItems = new ArrayList<>();
// For system apps, package manager send OP_UPDATE when an app is enabled.
final boolean isNewApkAvailable = mOp == OP_ADD || mOp == OP_UPDATE;
@@ -347,24 +348,25 @@
}
});
- for (LauncherAppWidgetInfo widgetInfo : dataModel.appWidgets) {
- if (mUser.equals(widgetInfo.user)
- && widgetInfo.hasRestoreFlag(
- LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
- && packageSet.contains(widgetInfo.providerName.getPackageName())) {
- widgetInfo.restoreStatus &=
- ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY
- & ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
+ dataModel.itemsIdMap.stream()
+ .filter(WIDGET_FILTER)
+ .filter(item -> mUser.equals(item.user))
+ .map(item -> (LauncherAppWidgetInfo) item)
+ .filter(widget -> widget.hasRestoreFlag(FLAG_PROVIDER_NOT_READY)
+ && packageSet.contains(widget.providerName.getPackageName()))
+ .forEach(widgetInfo -> {
+ widgetInfo.restoreStatus &=
+ ~FLAG_PROVIDER_NOT_READY
+ & ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
- // adding this flag ensures that launcher shows 'click to setup'
- // if the widget has a config activity. In case there is no config
- // activity, it will be marked as 'restored' during bind.
- widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
-
- widgets.add(widgetInfo);
- taskController.getModelWriter().updateItemInDatabase(widgetInfo);
- }
- }
+ // adding this flag ensures that launcher shows 'click to setup'
+ // if the widget has a config activity. In case there is no config
+ // activity, it will be marked as 'restored' during bind.
+ widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
+ widgetInfo.installProgress = 100;
+ updatedWorkspaceItems.add(widgetInfo);
+ taskController.getModelWriter().updateItemInDatabase(widgetInfo);
+ });
}
taskController.bindUpdatedWorkspaceItems(updatedWorkspaceItems);
@@ -374,10 +376,6 @@
"removing shortcuts with invalid target components."
+ " ids=" + removedShortcuts);
}
-
- if (!widgets.isEmpty()) {
- taskController.scheduleCallbackTask(c -> c.bindWidgetsRestored(widgets));
- }
}
final HashSet<String> removedPackages = new HashSet<>();
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
deleted file mode 100644
index b5a7382..0000000
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.model;
-
-import android.content.Context;
-import android.content.pm.ShortcutInfo;
-import android.os.UserHandle;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel.ModelUpdateTask;
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.icons.CacheableShortcutInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.shortcuts.ShortcutKey;
-import com.android.launcher3.shortcuts.ShortcutRequest;
-import com.android.launcher3.util.ApplicationInfoWrapper;
-import com.android.launcher3.util.ItemInfoMatcher;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-/**
- * Handles changes due to shortcut manager updates (deep shortcut changes)
- */
-public class ShortcutsChangedTask implements ModelUpdateTask {
-
- @NonNull
- private final String mPackageName;
-
- @NonNull
- private final List<ShortcutInfo> mShortcuts;
-
- @NonNull
- private final UserHandle mUser;
-
- private final boolean mUpdateIdMap;
-
- public ShortcutsChangedTask(@NonNull final String packageName,
- @NonNull final List<ShortcutInfo> shortcuts, @NonNull final UserHandle user,
- final boolean updateIdMap) {
- mPackageName = packageName;
- mShortcuts = shortcuts;
- mUser = user;
- mUpdateIdMap = updateIdMap;
- }
-
- @Override
- public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
- @NonNull AllAppsList apps) {
- final LauncherAppState app = taskController.getApp();
- final Context context = app.getContext();
- // Find WorkspaceItemInfo's that have changed on the workspace.
- ArrayList<WorkspaceItemInfo> matchingWorkspaceItems = new ArrayList<>();
-
- synchronized (dataModel) {
- dataModel.forAllWorkspaceItemInfos(mUser, si -> {
- if ((si.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)
- && mPackageName.equals(si.getIntent().getPackage())) {
- matchingWorkspaceItems.add(si);
- }
- });
- }
-
- if (!matchingWorkspaceItems.isEmpty()) {
- ApplicationInfoWrapper infoWrapper =
- new ApplicationInfoWrapper(context, mPackageName, mUser);
- if (mShortcuts.isEmpty()) {
- // Verify that the app is indeed installed.
- if (!infoWrapper.isInstalled() && !infoWrapper.isArchived()) {
- // App is not installed or archived, ignoring package events
- return;
- }
- }
- // Update the workspace to reflect the changes to updated shortcuts residing on it.
- List<String> allLauncherKnownIds = matchingWorkspaceItems.stream()
- .map(WorkspaceItemInfo::getDeepShortcutId)
- .distinct()
- .collect(Collectors.toList());
- List<ShortcutInfo> shortcuts = new ShortcutRequest(context, mUser)
- .forPackage(mPackageName, allLauncherKnownIds)
- .query(ShortcutRequest.ALL);
-
- Set<String> nonPinnedIds = new HashSet<>(allLauncherKnownIds);
- ArrayList<WorkspaceItemInfo> updatedWorkspaceItemInfos = new ArrayList<>();
- for (ShortcutInfo fullDetails : shortcuts) {
- if (!fullDetails.isPinned()) {
- continue;
- }
- String sid = fullDetails.getId();
- nonPinnedIds.remove(sid);
- matchingWorkspaceItems
- .stream()
- .filter(itemInfo -> sid.equals(itemInfo.getDeepShortcutId()))
- .forEach(workspaceItemInfo -> {
- workspaceItemInfo.updateFromDeepShortcutInfo(fullDetails, context);
- app.getIconCache().getShortcutIcon(workspaceItemInfo,
- new CacheableShortcutInfo(fullDetails, infoWrapper));
- updatedWorkspaceItemInfos.add(workspaceItemInfo);
- });
- }
-
- taskController.bindUpdatedWorkspaceItems(updatedWorkspaceItemInfos);
- if (!nonPinnedIds.isEmpty()) {
- taskController.deleteAndBindComponentsRemoved(ItemInfoMatcher.ofShortcutKeys(
- nonPinnedIds.stream()
- .map(id -> new ShortcutKey(mPackageName, mUser, id))
- .collect(Collectors.toSet())),
- "removed because the shortcut is no longer available in shortcut service");
- }
- }
-
- if (mUpdateIdMap) {
- // Update the deep shortcut map if the list of ids has changed for an activity.
- dataModel.updateDeepShortcutCounts(mPackageName, mUser, mShortcuts);
- taskController.bindDeepShortcuts(dataModel);
- }
- }
-}
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.kt b/src/com/android/launcher3/model/ShortcutsChangedTask.kt
new file mode 100644
index 0000000..2e4f75f
--- /dev/null
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.model
+
+import android.content.pm.ShortcutInfo
+import android.os.UserHandle
+import com.android.launcher3.LauncherModel.ModelUpdateTask
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
+import com.android.launcher3.icons.CacheableShortcutInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.shortcuts.ShortcutKey
+import com.android.launcher3.shortcuts.ShortcutRequest
+import com.android.launcher3.util.ApplicationInfoWrapper
+import com.android.launcher3.util.ItemInfoMatcher
+
+/** Handles changes due to shortcut manager updates (deep shortcut changes) */
+class ShortcutsChangedTask(
+ private val packageName: String,
+ private val shortcuts: List<ShortcutInfo>,
+ private val user: UserHandle,
+ private val shouldUpdateIdMap: Boolean,
+) : ModelUpdateTask {
+
+ override fun execute(
+ taskController: ModelTaskController,
+ dataModel: BgDataModel,
+ apps: AllAppsList,
+ ) {
+ val app = taskController.app
+ val context = app.context
+ // Find WorkspaceItemInfo's that have changed on the workspace.
+ val matchingWorkspaceItems = ArrayList<WorkspaceItemInfo>()
+
+ synchronized(dataModel) {
+ dataModel.forAllWorkspaceItemInfos(user) { wai: WorkspaceItemInfo ->
+ if (
+ (wai.itemType == ITEM_TYPE_DEEP_SHORTCUT) &&
+ packageName == wai.getIntent().getPackage()
+ ) {
+ matchingWorkspaceItems.add(wai)
+ }
+ }
+ }
+
+ if (matchingWorkspaceItems.isNotEmpty()) {
+ val infoWrapper = ApplicationInfoWrapper(context, packageName, user)
+ if (shortcuts.isEmpty()) {
+ // Verify that the app is indeed installed.
+ if (!infoWrapper.isInstalled() && !infoWrapper.isArchived()) {
+ // App is not installed or archived, ignoring package events
+ return
+ }
+ }
+ // Update the workspace to reflect the changes to updated shortcuts residing on it.
+ val allLauncherKnownIds =
+ matchingWorkspaceItems.map { item -> item.deepShortcutId }.distinct()
+ val shortcuts: List<ShortcutInfo> =
+ ShortcutRequest(context, user)
+ .forPackage(packageName, allLauncherKnownIds)
+ .query(ShortcutRequest.ALL)
+
+ val nonPinnedIds: MutableSet<String> = HashSet(allLauncherKnownIds)
+ val updatedWorkspaceItemInfos = ArrayList<WorkspaceItemInfo>()
+ for (fullDetails in shortcuts) {
+ if (!fullDetails.isPinned) {
+ continue
+ }
+ val shortcutId = fullDetails.id
+ nonPinnedIds.remove(shortcutId)
+ matchingWorkspaceItems
+ .filter { itemInfo: WorkspaceItemInfo -> shortcutId == itemInfo.deepShortcutId }
+ .forEach { workspaceItemInfo: WorkspaceItemInfo ->
+ workspaceItemInfo.updateFromDeepShortcutInfo(fullDetails, context)
+ app.iconCache.getShortcutIcon(
+ workspaceItemInfo,
+ CacheableShortcutInfo(fullDetails, infoWrapper),
+ )
+ updatedWorkspaceItemInfos.add(workspaceItemInfo)
+ }
+ }
+
+ taskController.bindUpdatedWorkspaceItems(updatedWorkspaceItemInfos)
+ if (nonPinnedIds.isNotEmpty()) {
+ taskController.deleteAndBindComponentsRemoved(
+ ItemInfoMatcher.ofShortcutKeys(
+ nonPinnedIds
+ .map { id: String? -> ShortcutKey(packageName, user, id) }
+ .toSet()
+ ),
+ "removed because the shortcut is no longer available in shortcut service",
+ )
+ }
+ }
+
+ if (shouldUpdateIdMap) {
+ // Update the deep shortcut map if the list of ids has changed for an activity.
+ dataModel.updateDeepShortcutCounts(packageName, user, shortcuts)
+ taskController.bindDeepShortcuts(dataModel)
+ }
+ }
+}
diff --git a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
index dad78dd..de1df2e 100644
--- a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
+++ b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
@@ -404,18 +404,14 @@
* stored in the BgDataModel.
*/
private fun processFolderOrAppPair() {
- var collection = bgDataModel.findOrMakeFolder(c.id)
+ var collection = c.findOrMakeFolder(c.id, bgDataModel)
// If we generated a placeholder Folder before this point, it may need to be replaced with
// an app pair.
if (c.itemType == Favorites.ITEM_TYPE_APP_PAIR && collection is FolderInfo) {
- val folderInfo: FolderInfo = collection
val newAppPair = AppPairInfo()
// Move the placeholder's contents over to the new app pair.
- folderInfo.getContents().forEach(newAppPair::add)
+ collection.getContents().forEach(newAppPair::add)
collection = newAppPair
- // Remove the placeholder and add the app pair into the data model.
- bgDataModel.collections.remove(c.id)
- bgDataModel.collections.put(c.id, collection)
}
c.applyCommonProperties(collection)
@@ -569,7 +565,7 @@
logWidgetInfo(app.invariantDeviceProfile, lapi)
}
}
- c.checkAndAddItem(appWidgetInfo, bgDataModel)
+ c.checkAndAddItem(appWidgetInfo, bgDataModel, memoryLogger)
}
companion object {
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index c22a8a5..588e759 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -439,8 +439,7 @@
LauncherAtom.ItemInfo.Builder itemBuilder = LauncherAtom.ItemInfo.newBuilder();
itemBuilder.setIsKidsMode(
SettingsCache.INSTANCE.get(context).getValue(NAV_BAR_KIDS_MODE, 0));
- UserCache.INSTANCE.executeIfCreated(cache ->
- itemBuilder.setUserType(getUserType(cache.getUserInfo(user))));
+ itemBuilder.setUserType(getUserType(UserCache.INSTANCE.get(context).getUserInfo(user)));
itemBuilder.setRank(rank);
itemBuilder.addAllItemAttributes(mAttributeList);
return itemBuilder;
diff --git a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
index 772ea7f..7fb0152 100644
--- a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
@@ -16,6 +16,8 @@
package com.android.launcher3.model.data;
+import static com.android.launcher3.icons.BitmapInfo.FLAG_THEMED;
+
import android.content.Context;
import android.content.Intent;
import android.os.Process;
@@ -23,6 +25,7 @@
import androidx.annotation.Nullable;
import com.android.launcher3.Flags;
+import com.android.launcher3.graphics.ThemeManager;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.BitmapInfo.DrawableCreationFlags;
import com.android.launcher3.icons.FastBitmapDrawable;
@@ -320,6 +323,9 @@
* Returns a FastBitmapDrawable with the icon and context theme applied
*/
public FastBitmapDrawable newIcon(Context context, @DrawableCreationFlags int creationFlags) {
+ if (!ThemeManager.INSTANCE.get(context).isMonoThemeEnabled()) {
+ creationFlags &= ~FLAG_THEMED;
+ }
FastBitmapDrawable drawable = bitmap.newIcon(context, creationFlags);
drawable.setIsDisabled(isDisabled());
return drawable;
diff --git a/src/com/android/launcher3/pm/UserCache.java b/src/com/android/launcher3/pm/UserCache.java
index e861961..0b18a87 100644
--- a/src/com/android/launcher3/pm/UserCache.java
+++ b/src/com/android/launcher3/pm/UserCache.java
@@ -32,11 +32,15 @@
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.dagger.LauncherBaseAppComponent;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.UserBadgeDrawable;
import com.android.launcher3.util.ApiWrapper;
+import com.android.launcher3.util.DaggerSingletonObject;
+import com.android.launcher3.util.DaggerSingletonTracker;
import com.android.launcher3.util.FlagOp;
-import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.launcher3.util.UserIconInfo;
@@ -47,10 +51,16 @@
import java.util.Map;
import java.util.function.BiConsumer;
+import javax.inject.Inject;
+
/**
* Class which manages a local cache of user handles to avoid system rpc
*/
-public class UserCache implements SafeCloseable {
+@LauncherAppSingleton
+public class UserCache {
+
+ public static DaggerSingletonObject<UserCache> INSTANCE =
+ new DaggerSingletonObject<>(LauncherBaseAppComponent::getUserCache);
public static final String ACTION_PROFILE_ADDED = ATLEAST_U
? Intent.ACTION_PROFILE_ADDED : Intent.ACTION_MANAGED_PROFILE_ADDED;
@@ -65,9 +75,6 @@
public static final String ACTION_PROFILE_UNAVAILABLE =
"android.intent.action.PROFILE_UNAVAILABLE";
- public static final MainThreadInitializedObject<UserCache> INSTANCE =
- new MainThreadInitializedObject<>(UserCache::new);
-
/** Returns an instance of UserCache bound to the context provided. */
public static UserCache getInstance(Context context) {
return INSTANCE.get(context);
@@ -78,6 +85,7 @@
new SimpleBroadcastReceiver(MODEL_EXECUTOR, this::onUsersChanged);
private final Context mContext;
+ private final ApiWrapper mApiWrapper;
@NonNull
private Map<UserHandle, UserIconInfo> mUserToSerialMap;
@@ -85,15 +93,17 @@
@NonNull
private Map<UserHandle, List<String>> mUserToPreInstallAppMap;
- private UserCache(Context context) {
+ @Inject
+ public UserCache(
+ @ApplicationContext Context context,
+ DaggerSingletonTracker tracker,
+ ApiWrapper apiWrapper
+ ) {
mContext = context;
+ mApiWrapper = apiWrapper;
mUserToSerialMap = Collections.emptyMap();
MODEL_EXECUTOR.execute(this::initAsync);
- }
-
- @Override
- public void close() {
- MODEL_EXECUTOR.execute(() -> mUserChangeReceiver.unregisterReceiverSafely(mContext));
+ tracker.addCloseable(() -> mUserChangeReceiver.unregisterReceiverSafely(mContext));
}
@WorkerThread
@@ -124,7 +134,7 @@
@WorkerThread
private void updateCache() {
- mUserToSerialMap = ApiWrapper.INSTANCE.get(mContext).queryAllUsers();
+ mUserToSerialMap = mApiWrapper.queryAllUsers();
mUserToPreInstallAppMap = fetchPreInstallApps();
}
@@ -134,7 +144,7 @@
mUserToSerialMap.forEach((userHandle, userIconInfo) -> {
// Fetch only for private profile, as other profiles have no usages yet.
List<String> preInstallApp = userIconInfo.isPrivate()
- ? ApiWrapper.INSTANCE.get(mContext).getPreInstalledSystemPackages(userHandle)
+ ? mApiWrapper.getPreInstalledSystemPackages(userHandle)
: new ArrayList<>();
userToPreInstallApp.put(userHandle, preInstallApp);
});
diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.kt b/src/com/android/launcher3/provider/LauncherDbUtils.kt
index 6f1d0dd..c92328d 100644
--- a/src/com/android/launcher3/provider/LauncherDbUtils.kt
+++ b/src/com/android/launcher3/provider/LauncherDbUtils.kt
@@ -28,6 +28,7 @@
import android.text.TextUtils
import com.android.launcher3.LauncherAppState
import com.android.launcher3.LauncherSettings
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
import com.android.launcher3.Utilities
import com.android.launcher3.icons.IconCache
import com.android.launcher3.model.LoaderCursor
@@ -132,8 +133,10 @@
}
@JvmStatic
- fun shiftTableByXCells(db: SQLiteDatabase, x: Int, toTable: String) {
- db.run { execSQL("UPDATE $toTable SET cellY = cellY + $x") }
+ fun shiftWorkspaceByXCells(db: SQLiteDatabase, x: Int, toTable: String) {
+ db.run {
+ execSQL("UPDATE $toTable SET cellY = cellY + $x WHERE container = $CONTAINER_DESKTOP")
+ }
}
/**
diff --git a/src/com/android/launcher3/search/StringMatcherUtility.java b/src/com/android/launcher3/search/StringMatcherUtility.java
index 7446314..20b4d0b 100644
--- a/src/com/android/launcher3/search/StringMatcherUtility.java
+++ b/src/com/android/launcher3/search/StringMatcherUtility.java
@@ -127,17 +127,17 @@
if (query == null || target == null) {
return false;
}
- switch (mCollator.compare(query, target)) {
- case 0:
- return true;
- case -1:
- // The target string can contain a modifier which would make it larger than
- // the query string (even though the length is same). If the query becomes
- // larger after appending a unicode character, it was originally a prefix of
- // the target string and hence should match.
- return mCollator.compare(query + MAX_UNICODE, target) > -1;
- default:
- return false;
+ int compare = mCollator.compare(query, target);
+ if (compare == 0) {
+ return true;
+ } else if (compare < 0) {
+ // The target string can contain a modifier which would make it larger than
+ // the query string (even though the length is same). If the query becomes
+ // larger after appending a unicode character, it was originally a prefix of
+ // the target string and hence should match.
+ return mCollator.compare(query + MAX_UNICODE, target) >= 0;
+ } else {
+ return false;
}
}
diff --git a/src/com/android/launcher3/shapes/IconShapeModel.kt b/src/com/android/launcher3/shapes/IconShapeModel.kt
index 10b7f91..dd6c432 100644
--- a/src/com/android/launcher3/shapes/IconShapeModel.kt
+++ b/src/com/android/launcher3/shapes/IconShapeModel.kt
@@ -16,4 +16,9 @@
package com.android.launcher3.shapes
-data class IconShapeModel(val key: String, val title: String, val pathString: String)
+data class IconShapeModel(
+ val key: String,
+ val title: String,
+ val pathString: String,
+ val folderPathString: String = pathString,
+)
diff --git a/src/com/android/launcher3/shapes/IconShapesProvider.kt b/src/com/android/launcher3/shapes/IconShapesProvider.kt
deleted file mode 100644
index a190e22..0000000
--- a/src/com/android/launcher3/shapes/IconShapesProvider.kt
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.shapes
-
-import com.android.launcher3.Flags as LauncherFlags
-import com.android.systemui.shared.Flags
-
-object IconShapesProvider {
- val shapes =
- if (Flags.newCustomizationPickerUi() && LauncherFlags.enableLauncherIconShapes()) {
- mapOf(
- "arch" to
- IconShapeModel(
- key = "arch",
- title = "arch",
- pathString =
- "M50 0C77.614 0 100 22.386 100 50C100 85.471 100 86.476 99.9 87.321 99.116 93.916 93.916 99.116 87.321 99.9 86.476 100 85.471 100 83.46 100H16.54C14.529 100 13.524 100 12.679 99.9 6.084 99.116.884 93.916.1 87.321 0 86.476 0 85.471 0 83.46L0 50C0 22.386 22.386 0 50 0Z",
- ),
- "4_sided_cookie" to
- IconShapeModel(
- key = "4_sided_cookie",
- title = "4 sided cookie",
- pathString =
- "M39.888,4.517C46.338 7.319 53.662 7.319 60.112 4.517L63.605 3C84.733 -6.176 106.176 15.268 97 36.395L95.483 39.888C92.681 46.338 92.681 53.662 95.483 60.112L97 63.605C106.176 84.732 84.733 106.176 63.605 97L60.112 95.483C53.662 92.681 46.338 92.681 39.888 95.483L36.395 97C15.267 106.176 -6.176 84.732 3 63.605L4.517 60.112C7.319 53.662 7.319 46.338 4.517 39.888L3 36.395C -6.176 15.268 15.267 -6.176 36.395 3Z",
- ),
- "seven_sided_cookie" to
- IconShapeModel(
- key = "seven_sided_cookie",
- title = "7 sided cookie",
- pathString =
- "M35.209 4.878C36.326 3.895 36.884 3.404 37.397 3.006 44.82 -2.742 55.18 -2.742 62.603 3.006 63.116 3.404 63.674 3.895 64.791 4.878 65.164 5.207 65.351 5.371 65.539 5.529 68.167 7.734 71.303 9.248 74.663 9.932 74.902 9.981 75.147 10.025 75.637 10.113 77.1 10.375 77.831 10.506 78.461 10.66 87.573 12.893 94.032 21.011 94.176 30.412 94.186 31.062 94.151 31.805 94.08 33.293 94.057 33.791 94.045 34.04 94.039 34.285 93.958 37.72 94.732 41.121 96.293 44.18 96.404 44.399 96.522 44.618 96.759 45.056 97.467 46.366 97.821 47.021 98.093 47.611 102.032 56.143 99.727 66.266 92.484 72.24 91.983 72.653 91.381 73.089 90.177 73.961 89.774 74.254 89.572 74.4 89.377 74.548 86.647 76.626 84.477 79.353 83.063 82.483 82.962 82.707 82.865 82.936 82.671 83.395 82.091 84.766 81.8 85.451 81.51 86.033 77.31 94.44 67.977 98.945 58.801 96.994 58.166 96.859 57.451 96.659 56.019 96.259 55.54 96.125 55.3 96.058 55.063 95.998 51.74 95.154 48.26 95.154 44.937 95.998 44.699 96.058 44.46 96.125 43.981 96.259 42.549 96.659 41.834 96.859 41.199 96.994 32.023 98.945 22.69 94.44 18.49 86.033 18.2 85.451 17.909 84.766 17.329 83.395 17.135 82.936 17.038 82.707 16.937 82.483 15.523 79.353 13.353 76.626 10.623 74.548 10.428 74.4 10.226 74.254 9.823 73.961 8.619 73.089 8.017 72.653 7.516 72.24.273 66.266 -2.032 56.143 1.907 47.611 2.179 47.021 2.533 46.366 3.241 45.056 3.478 44.618 3.596 44.399 3.707 44.18 5.268 41.121 6.042 37.72 5.961 34.285 5.955 34.04 5.943 33.791 5.92 33.293 5.849 31.805 5.814 31.062 5.824 30.412 5.968 21.011 12.427 12.893 21.539 10.66 22.169 10.506 22.9 10.375 24.363 10.113 24.853 10.025 25.098 9.981 25.337 9.932 28.697 9.248 31.833 7.734 34.461 5.529 34.649 5.371 34.836 5.207 35.209 4.878Z",
- ),
- "sunny" to
- IconShapeModel(
- key = "sunny",
- title = "sunny",
- pathString =
- "M42.846 4.873C46.084 -.531 53.916 -.531 57.154 4.873L60.796 10.951C62.685 14.103 66.414 15.647 69.978 14.754L76.851 13.032C82.962 11.5 88.5 17.038 86.968 23.149L85.246 30.022C84.353 33.586 85.897 37.315 89.049 39.204L95.127 42.846C100.531 46.084 100.531 53.916 95.127 57.154L89.049 60.796C85.897 62.685 84.353 66.414 85.246 69.978L86.968 76.851C88.5 82.962 82.962 88.5 76.851 86.968L69.978 85.246C66.414 84.353 62.685 85.898 60.796 89.049L57.154 95.127C53.916 100.531 46.084 100.531 42.846 95.127L39.204 89.049C37.315 85.898 33.586 84.353 30.022 85.246L23.149 86.968C17.038 88.5 11.5 82.962 13.032 76.851L14.754 69.978C15.647 66.414 14.103 62.685 10.951 60.796L4.873 57.154C -.531 53.916 -.531 46.084 4.873 42.846L10.951 39.204C14.103 37.315 15.647 33.586 14.754 30.022L13.032 23.149C11.5 17.038 17.038 11.5 23.149 13.032L30.022 14.754C33.586 15.647 37.315 14.103 39.204 10.951L42.846 4.873Z",
- ),
- "circle" to
- IconShapeModel(
- key = "circle",
- title = "circle",
- pathString = "M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0",
- ),
- "square" to
- IconShapeModel(
- key = "square",
- title = "square",
- pathString =
- "M53.689 0.82L53.689.82C67.434.82 74.306.82 79.758 2.978 87.649 6.103 93.897 12.351 97.022 20.242 99.18 25.694 99.18 32.566 99.18 46.311V53.689C99.18 67.434 99.18 74.306 97.022 79.758 93.897 87.649 87.649 93.897 79.758 97.022 74.306 99.18 67.434 99.18 53.689 99.18H46.311C32.566 99.18 25.694 99.18 20.242 97.022 12.351 93.897 6.103 87.649 2.978 79.758.82 74.306.82 67.434.82 53.689L.82 46.311C.82 32.566.82 25.694 2.978 20.242 6.103 12.351 12.351 6.103 20.242 2.978 25.694.82 32.566.82 46.311.82Z",
- ),
- )
- } else {
- mapOf(
- "circle" to
- IconShapeModel(
- key = "circle",
- title = "circle",
- pathString = "M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0",
- )
- )
- }
-}
diff --git a/src/com/android/launcher3/shapes/ShapesProvider.kt b/src/com/android/launcher3/shapes/ShapesProvider.kt
new file mode 100644
index 0000000..dfb7793
--- /dev/null
+++ b/src/com/android/launcher3/shapes/ShapesProvider.kt
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.shapes
+
+import com.android.launcher3.Flags as LauncherFlags
+import com.android.systemui.shared.Flags
+
+object ShapesProvider {
+ val folderShapes =
+ if (LauncherFlags.enableLauncherIconShapes()) {
+ mapOf(
+ "clover" to
+ "M 39.616 4" +
+ "C 46.224 6.87 53.727 6.87 60.335 4" +
+ "L 63.884 2.459" +
+ "C 85.178 -6.789 106.789 14.822 97.541 36.116" +
+ "L 96 39.665" +
+ "C 93.13 46.273 93.13 53.776 96 60.384" +
+ "L 97.541 63.934" +
+ "C 106.789 85.227 85.178 106.839 63.884 97.591" +
+ "L 60.335 96.049" +
+ "C 53.727 93.179 46.224 93.179 39.616 96.049" +
+ "L 36.066 97.591" +
+ "C 14.773 106.839 -6.839 85.227 2.409 63.934" +
+ "L 3.951 60.384" +
+ "C 6.821 53.776 6.821 46.273 3.951 39.665" +
+ "L 2.409 36.116" +
+ "C -6.839 14.822 14.773 -6.789 36.066 2.459" +
+ "Z",
+ "complexClover" to
+ "M 49.85 6.764" +
+ "L 50.013 6.971" +
+ "L 50.175 6.764" +
+ "C 53.422 2.635 58.309 0.207 63.538 0.207" +
+ "C 65.872 0.207 68.175 0.692 70.381 1.648" +
+ "L 71.79 2.264" +
+ "L 71.792 2.265" +
+ "A 3.46 3.46 0 0 0 74.515 2.265" +
+ "L 74.517 2.264" +
+ "L 75.926 1.652" +
+ "A 17.1 17.1 0 0 1 82.769 0.207" +
+ "C 88.495 0.207 93.824 3.117 97.022 7.989" +
+ "C 100.21 12.848 100.697 18.712 98.36 24.087" +
+ "L 97.749 25.496" +
+ "V 25.497" +
+ "A 3.45 3.45 0 0 0 97.749 28.222" +
+ "V 28.223" +
+ "L 98.36 29.632" +
+ "C 100.697 35.007 100.207 40.871 97.022 45.73" +
+ "A 17.5 17.5 0 0 1 93.264 49.838" +
+ "L 93.06 50" +
+ "L 93.264 50.162" +
+ "A 17.5 17.5 0 0 1 97.022 54.27" +
+ "C 100.21 59.129 100.697 64.993 98.36 70.368" +
+ "V 71.778" +
+ "A 3.45 3.45 0 0 0 97.749 74.503" +
+ "V 74.504" +
+ "L 98.36 75.913" +
+ "C 100.697 81.288 100.207 87.152 97.022 92.011" +
+ "C 93.824 96.883 88.495 99.793 82.769 99.793" +
+ "C 80.435 99.793 78.132 99.308 75.926 98.348" +
+ "L 74.517 97.736" +
+ "H 74.515" +
+ "A 3.5 3.5 0 0 0 73.153 97.455" +
+ "C 72.682 97.455 72.225 97.552 71.792 97.736" +
+ "H 71.79" +
+ "L 70.381 98.348" +
+ "A 17.1 17.1 0 0 1 63.538 99.793" +
+ "C 58.309 99.793 53.422 97.365 50.175 93.236" +
+ "L 50.013 93.029" +
+ "L 49.85 93.236" +
+ "C 46.603 97.365 41.717 99.793 36.488 99.793" +
+ "C 34.154 99.793 31.851 99.308 29.645 98.348" +
+ "L 28.236 97.736" +
+ "H 28.234" +
+ "A 3.5 3.5 0 0 0 26.872 97.455" +
+ "C 26.401 97.455 25.944 97.552 25.511 97.736" +
+ "H 25.509" +
+ "L 24.1 98.348" +
+ "A 17.1 17.1 0 0 1 17.257 99.793" +
+ "C 11.53 99.793 6.202 96.883 3.004 92.011" +
+ "C -0.181 87.152 -0.671 81.288 1.661 75.913" +
+ "L 2.277 74.504" +
+ "V 74.503" +
+ "A 3.45 3.45 0 0 0 2.277 71.778" +
+ "V 71.777" +
+ "L 1.665 70.368" +
+ "C -0.671 64.993 -0.181 59.129 3.004 54.274" +
+ "A 17.5 17.5 0 0 1 6.761 50.162" +
+ "L 6.965 50" +
+ "L 6.761 49.838" +
+ "A 17.5 17.5 0 0 1 3.004 45.73" +
+ "C -0.181 40.871 -0.671 35.007 1.665 29.632" +
+ "L 2.277 28.223" +
+ "V 28.222" +
+ "A 3.45 3.45 0 0 0 2.277 25.497" +
+ "V 25.496" +
+ "L 1.665 24.087" +
+ "C -0.671 18.712 -0.181 12.848 3.004 7.994" +
+ "V 7.993" +
+ "C 6.202 3.117 11.53 0.207 17.257 0.207" +
+ "C 19.591 0.207 21.894 0.692 24.1 1.652" +
+ "L 25.509 2.264" +
+ "L 25.511 2.265" +
+ "A 3.46 3.46 0 0 0 28.234 2.265" +
+ "L 28.236 2.264" +
+ "L 29.645 1.652" +
+ "A 17.1 17.1 0 0 1 36.488 0.207" +
+ "C 41.717 0.207 46.603 2.635 49.85 6.764" +
+ "Z",
+ "arch" to
+ "M 50 0" +
+ "L 72.5 0" +
+ "A 27.5 27.5 0 0 1 100 27.5" +
+ "L 100 86.67" +
+ "A 13.33 13.33 0 0 1 86.67 100" +
+ "L 13.33 100" +
+ "A 13.33 13.33 0 0 1 0 86.67" +
+ "L 0 27.5" +
+ "A 27.5 27.5 0 0 1 27.5 0" +
+ "Z",
+ "square" to
+ "M 50 0" +
+ "L 83.4 0" +
+ "A 16.6 16.6 0 0 1 100 16.6" +
+ "L 100 83.4" +
+ "A 16.6 16.6 0 0 1 83.4 100" +
+ "L 16.6 100" +
+ "A 16.6 16.6 0 0 1 0 83.4" +
+ "L 0 16.6" +
+ "A 16.6 16.6 0 0 1 16.6 0" +
+ "Z",
+ )
+ } else {
+ mapOf("circle" to "M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0")
+ }
+
+ val iconShapes =
+ if (Flags.newCustomizationPickerUi() && LauncherFlags.enableLauncherIconShapes()) {
+ mapOf(
+ "arch" to
+ IconShapeModel(
+ key = "arch",
+ title = "arch",
+ pathString =
+ "M50 0C77.614 0 100 22.386 100 50C100 85.471 100 86.476 99.9 87.321 99.116 93.916 93.916 99.116 87.321 99.9 86.476 100 85.471 100 83.46 100H16.54C14.529 100 13.524 100 12.679 99.9 6.084 99.116 .884 93.916 .1 87.321 0 86.476 0 85.471 0 83.46L0 50C0 22.386 22.386 0 50 0Z",
+ folderPathString = folderShapes["arch"]!!,
+ ),
+ "four_sided_cookie" to
+ IconShapeModel(
+ key = "four_sided_cookie",
+ title = "4 sided cookie",
+ pathString =
+ "M39.888,4.517C46.338 7.319 53.662 7.319 60.112 4.517L63.605 3C84.733 -6.176 106.176 15.268 97 36.395L95.483 39.888C92.681 46.338 92.681 53.662 95.483 60.112L97 63.605C106.176 84.732 84.733 106.176 63.605 97L60.112 95.483C53.662 92.681 46.338 92.681 39.888 95.483L36.395 97C15.267 106.176 -6.176 84.732 3 63.605L4.517 60.112C7.319 53.662 7.319 46.338 4.517 39.888L3 36.395C -6.176 15.268 15.267 -6.176 36.395 3Z",
+ folderPathString = folderShapes["complexClover"]!!,
+ ),
+ "seven_sided_cookie" to
+ IconShapeModel(
+ key = "seven_sided_cookie",
+ title = "7 sided cookie",
+ pathString =
+ "M35.209 4.878C36.326 3.895 36.884 3.404 37.397 3.006 44.82 -2.742 55.18 -2.742 62.603 3.006 63.116 3.404 63.674 3.895 64.791 4.878 65.164 5.207 65.351 5.371 65.539 5.529 68.167 7.734 71.303 9.248 74.663 9.932 74.902 9.981 75.147 10.025 75.637 10.113 77.1 10.375 77.831 10.506 78.461 10.66 87.573 12.893 94.032 21.011 94.176 30.412 94.186 31.062 94.151 31.805 94.08 33.293 94.057 33.791 94.045 34.04 94.039 34.285 93.958 37.72 94.732 41.121 96.293 44.18 96.404 44.399 96.522 44.618 96.759 45.056 97.467 46.366 97.821 47.021 98.093 47.611 102.032 56.143 99.727 66.266 92.484 72.24 91.983 72.653 91.381 73.089 90.177 73.961 89.774 74.254 89.572 74.4 89.377 74.548 86.647 76.626 84.477 79.353 83.063 82.483 82.962 82.707 82.865 82.936 82.671 83.395 82.091 84.766 81.8 85.451 81.51 86.033 77.31 94.44 67.977 98.945 58.801 96.994 58.166 96.859 57.451 96.659 56.019 96.259 55.54 96.125 55.3 96.058 55.063 95.998 51.74 95.154 48.26 95.154 44.937 95.998 44.699 96.058 44.46 96.125 43.981 96.259 42.549 96.659 41.834 96.859 41.199 96.994 32.023 98.945 22.69 94.44 18.49 86.033 18.2 85.451 17.909 84.766 17.329 83.395 17.135 82.936 17.038 82.707 16.937 82.483 15.523 79.353 13.353 76.626 10.623 74.548 10.428 74.4 10.226 74.254 9.823 73.961 8.619 73.089 8.017 72.653 7.516 72.24 .273 66.266 -2.032 56.143 1.907 47.611 2.179 47.021 2.533 46.366 3.241 45.056 3.478 44.618 3.596 44.399 3.707 44.18 5.268 41.121 6.042 37.72 5.961 34.285 5.955 34.04 5.943 33.791 5.92 33.293 5.849 31.805 5.814 31.062 5.824 30.412 5.968 21.011 12.427 12.893 21.539 10.66 22.169 10.506 22.9 10.375 24.363 10.113 24.853 10.025 25.098 9.981 25.337 9.932 28.697 9.248 31.833 7.734 34.461 5.529 34.649 5.371 34.836 5.207 35.209 4.878Z",
+ folderPathString = folderShapes["clover"]!!,
+ ),
+ "sunny" to
+ IconShapeModel(
+ key = "sunny",
+ title = "sunny",
+ pathString =
+ "M42.846 4.873C46.084 -.531 53.916 -.531 57.154 4.873L60.796 10.951C62.685 14.103 66.414 15.647 69.978 14.754L76.851 13.032C82.962 11.5 88.5 17.038 86.968 23.149L85.246 30.022C84.353 33.586 85.897 37.315 89.049 39.204L95.127 42.846C100.531 46.084 100.531 53.916 95.127 57.154L89.049 60.796C85.897 62.685 84.353 66.414 85.246 69.978L86.968 76.851C88.5 82.962 82.962 88.5 76.851 86.968L69.978 85.246C66.414 84.353 62.685 85.898 60.796 89.049L57.154 95.127C53.916 100.531 46.084 100.531 42.846 95.127L39.204 89.049C37.315 85.898 33.586 84.353 30.022 85.246L23.149 86.968C17.038 88.5 11.5 82.962 13.032 76.851L14.754 69.978C15.647 66.414 14.103 62.685 10.951 60.796L4.873 57.154C -.531 53.916 -.531 46.084 4.873 42.846L10.951 39.204C14.103 37.315 15.647 33.586 14.754 30.022L13.032 23.149C11.5 17.038 17.038 11.5 23.149 13.032L30.022 14.754C33.586 15.647 37.315 14.103 39.204 10.951L42.846 4.873Z",
+ folderPathString = folderShapes["clover"]!!,
+ ),
+ "circle" to
+ IconShapeModel(
+ key = "circle",
+ title = "circle",
+ pathString = "M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0",
+ folderPathString = folderShapes["clover"]!!,
+ ),
+ "square" to
+ IconShapeModel(
+ key = "square",
+ title = "square",
+ pathString =
+ "M53.689 0.82 L53.689 .82 C67.434 .82 74.306 .82 79.758 2.978 87.649 6.103 93.897 12.351 97.022 20.242 99.18 25.694 99.18 32.566 99.18 46.311 V53.689 C99.18 67.434 99.18 74.306 97.022 79.758 93.897 87.649 87.649 93.897 79.758 97.022 74.306 99.18 67.434 99.18 53.689 99.18 H46.311 C32.566 99.18 25.694 99.18 20.242 97.022 12.351 93.897 6.103 87.649 2.978 79.758 .82 74.306 .82 67.434 .82 53.689 L.82 46.311 C.82 32.566 .82 25.694 2.978 20.242 6.103 12.351 12.351 6.103 20.242 2.978 25.694 .82 32.566 .82 46.311 .82Z",
+ folderShapes["square"]!!,
+ ),
+ )
+ } else {
+ mapOf(
+ "default" to
+ IconShapeModel(
+ key = "default",
+ title = "circle",
+ pathString = "M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0",
+ )
+ )
+ }
+}
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 78709b8..381d17a 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -228,10 +228,9 @@
private static void onClickPendingAppItem(View v, Launcher launcher, String packageName,
boolean downloadStarted) {
ItemInfo item = (ItemInfo) v.getTag();
- CompletableFuture<SessionInfo> siFuture;
- siFuture = CompletableFuture.supplyAsync(() ->
- InstallSessionHelper.INSTANCE.get(launcher)
- .getActiveSessionInfo(item.user, packageName),
+ CompletableFuture<SessionInfo> siFuture = CompletableFuture.supplyAsync(() ->
+ InstallSessionHelper.INSTANCE.get(launcher)
+ .getActiveSessionInfo(item.user, packageName),
UI_HELPER_EXECUTOR);
Consumer<SessionInfo> marketLaunchAction = sessionInfo -> {
if (sessionInfo != null) {
@@ -245,8 +244,8 @@
}
}
// Fallback to using custom market intent.
- Intent intent = ApiWrapper.INSTANCE.get(launcher).getAppMarketActivityIntent(
- packageName, Process.myUserHandle());
+ Intent intent = ApiWrapper.INSTANCE.get(launcher).getMarketSearchIntent(
+ packageName, item.user);
launcher.startActivitySafely(v, intent, item);
};
@@ -358,9 +357,7 @@
// Check for abandoned promise
if ((v instanceof BubbleTextView) && shortcut.hasPromiseIconUi()
&& (!Flags.enableSupportForArchiving() || !shortcut.isArchived())) {
- String packageName = shortcut.getIntent().getComponent() != null
- ? shortcut.getIntent().getComponent().getPackageName()
- : shortcut.getIntent().getPackage();
+ String packageName = shortcut.getTargetPackage();
if (!TextUtils.isEmpty(packageName)) {
onClickPendingAppItem(
v,
diff --git a/src/com/android/launcher3/util/ApiWrapper.java b/src/com/android/launcher3/util/ApiWrapper.java
index 467a7ec..48e033a 100644
--- a/src/com/android/launcher3/util/ApiWrapper.java
+++ b/src/com/android/launcher3/util/ApiWrapper.java
@@ -28,6 +28,7 @@
import android.content.pm.ShortcutInfo;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
+import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArrayMap;
@@ -120,6 +121,21 @@
* Activity).
*/
public Intent getAppMarketActivityIntent(String packageName, UserHandle user) {
+ return createMarketIntent(packageName);
+ }
+
+ /**
+ * Returns an intent which can be used to start a search for a package on app market
+ */
+ public Intent getMarketSearchIntent(String packageName, UserHandle user) {
+ // If we are search for the current user, just launch the market directly as the
+ // system won't have the installer details either
+ return (Process.myUserHandle().equals(user))
+ ? createMarketIntent(packageName)
+ : getAppMarketActivityIntent(packageName, user);
+ }
+
+ private static Intent createMarketIntent(String packageName) {
return new Intent(Intent.ACTION_VIEW)
.setData(new Uri.Builder()
.scheme("market")
@@ -171,6 +187,13 @@
return appInfo.sourceDir;
}
+ /**
+ * Returns the round icon resource Id if defined by the app
+ */
+ public int getRoundIconRes(@NonNull ApplicationInfo appInfo) {
+ return 0;
+ }
+
private static class NoopDrawable extends ColorDrawable {
@Override
public int getIntrinsicHeight() {
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 9472f5f..475dc04 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -69,6 +69,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.StringJoiner;
+import java.util.concurrent.CopyOnWriteArrayList;
import javax.inject.Inject;
@@ -114,7 +115,8 @@
// The callback in this listener updates DeviceProfile, which other listeners might depend on
private DisplayInfoChangeListener mPriorityListener;
- private final ArrayList<DisplayInfoChangeListener> mListeners = new ArrayList<>();
+ private final CopyOnWriteArrayList<DisplayInfoChangeListener> mListeners =
+ new CopyOnWriteArrayList<>();
// We will register broadcast receiver on main thread to ensure not missing changes on
// TARGET_OVERLAY_PACKAGE and ACTION_OVERLAY_CHANGED.
diff --git a/src/com/android/launcher3/util/IntSparseArrayMap.java b/src/com/android/launcher3/util/IntSparseArrayMap.java
index 9d5391b..70f74e3 100644
--- a/src/com/android/launcher3/util/IntSparseArrayMap.java
+++ b/src/com/android/launcher3/util/IntSparseArrayMap.java
@@ -19,6 +19,8 @@
import android.util.SparseArray;
import java.util.Iterator;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
/**
* Extension of {@link SparseArray} with some utility methods.
@@ -43,6 +45,10 @@
return new ValueIterator();
}
+ public Stream<E> stream() {
+ return StreamSupport.stream(spliterator(), false);
+ }
+
@Thunk class ValueIterator implements Iterator<E> {
private int mNextIndex = 0;
diff --git a/src/com/android/launcher3/util/LauncherBindableItemsContainer.java b/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
index 02779ce..20e3eaf 100644
--- a/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
+++ b/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
@@ -15,24 +15,20 @@
*/
package com.android.launcher3.util;
-import android.graphics.drawable.Drawable;
import android.view.View;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.graphics.PreloadIconDrawable;
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.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.widget.PendingAppWidgetHostView;
-import java.util.HashSet;
-import java.util.List;
+import java.util.Set;
/**
* Interface representing a container which can bind Launcher items with some utility methods
@@ -41,27 +37,22 @@
/**
* Called to update workspace items as a result of
- * {@link com.android.launcher3.model.BgDataModel.Callbacks#bindWorkspaceItemsChanged(List)}
+ * {@link com.android.launcher3.model.BgDataModel.Callbacks#bindItemsUpdated(Set)}
*/
- default void updateWorkspaceItems(List<WorkspaceItemInfo> shortcuts, ActivityContext context) {
- final HashSet<WorkspaceItemInfo> updates = new HashSet<>(shortcuts);
+ default void updateContainerItems(Set<ItemInfo> updates, ActivityContext context) {
ItemOperator op = (info, v) -> {
- if (v instanceof BubbleTextView && updates.contains(info)) {
- WorkspaceItemInfo si = (WorkspaceItemInfo) info;
- BubbleTextView shortcut = (BubbleTextView) v;
- Drawable oldIcon = shortcut.getIcon();
- boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable)
- && ((PreloadIconDrawable) oldIcon).hasNotCompleted();
- shortcut.applyFromWorkspaceItem(
- si,
- si.isPromise() != oldPromiseState
- && oldIcon instanceof PreloadIconDrawable
- ? (PreloadIconDrawable) oldIcon
- : null);
- } else if (info instanceof FolderInfo && v instanceof FolderIcon) {
- ((FolderIcon) v).updatePreviewItems(updates::contains);
+ if (v instanceof BubbleTextView shortcut
+ && info instanceof WorkspaceItemInfo wii
+ && updates.contains(info)) {
+ shortcut.applyFromWorkspaceItem(wii);
+ } else if (info instanceof FolderInfo && v instanceof FolderIcon folderIcon) {
+ folderIcon.updatePreviewItems(updates::contains);
} else if (info instanceof AppPairInfo && v instanceof AppPairIcon appPairIcon) {
appPairIcon.maybeRedrawForWorkspaceUpdate(updates::contains);
+ } else if (v instanceof PendingAppWidgetHostView pendingView
+ && updates.contains(info)) {
+ pendingView.applyState();
+ pendingView.postProviderAvailabilityCheck();
}
// Iterate all items
@@ -76,35 +67,6 @@
}
/**
- * Called to update restored items as a result of
- * {@link com.android.launcher3.model.BgDataModel.Callbacks#bindRestoreItemsChange(HashSet)}}
- */
- default void updateRestoreItems(final HashSet<ItemInfo> updates, ActivityContext context) {
- ItemOperator op = (info, v) -> {
- if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView
- && updates.contains(info)) {
- ((BubbleTextView) v).applyLoadingState(null);
- } else if (v instanceof PendingAppWidgetHostView
- && info instanceof LauncherAppWidgetInfo
- && updates.contains(info)) {
- ((PendingAppWidgetHostView) v).applyState();
- } else if (v instanceof FolderIcon && info instanceof FolderInfo) {
- ((FolderIcon) v).updatePreviewItems(updates::contains);
- } else if (info instanceof AppPairInfo && v instanceof AppPairIcon appPairIcon) {
- appPairIcon.maybeRedrawForWorkspaceUpdate(updates::contains);
- }
- // process all the shortcuts
- return false;
- };
-
- mapOverItems(op);
- Folder folder = Folder.getOpen(context);
- if (folder != null) {
- folder.iterateOverItems(op);
- }
- }
-
- /**
* Map the operator over the shortcuts and widgets.
*
* @param op the operator to map over the shortcuts
diff --git a/src/com/android/launcher3/util/LayoutImportExportHelper.kt b/src/com/android/launcher3/util/LayoutImportExportHelper.kt
index 0df9dae..8559f3b 100644
--- a/src/com/android/launcher3/util/LayoutImportExportHelper.kt
+++ b/src/com/android/launcher3/util/LayoutImportExportHelper.kt
@@ -56,7 +56,7 @@
model.enqueueModelUpdateTask { _, dataModel, _ ->
val builder = LauncherLayoutBuilder()
- dataModel.workspaceItems.forEach { info ->
+ dataModel.itemsIdMap.forEach { info ->
val loc =
when (info.container) {
CONTAINER_DESKTOP ->
@@ -67,9 +67,6 @@
}
loc.addItem(context, info)
}
- dataModel.appWidgets.forEach { info ->
- builder.atWorkspace(info.cellX, info.cellY, info.screenId).addItem(context, info)
- }
val layoutXml = builder.build()
callback(layoutXml)
diff --git a/src/com/android/launcher3/util/MSDLPlayerWrapper.java b/src/com/android/launcher3/util/MSDLPlayerWrapper.java
index 8a1d923..fc3fa72 100644
--- a/src/com/android/launcher3/util/MSDLPlayerWrapper.java
+++ b/src/com/android/launcher3/util/MSDLPlayerWrapper.java
@@ -16,8 +16,6 @@
package com.android.launcher3.util;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-
import android.content.Context;
import android.os.Vibrator;
@@ -50,7 +48,9 @@
@Inject
public MSDLPlayerWrapper(@ApplicationContext Context context) {
Vibrator vibrator = context.getSystemService(Vibrator.class);
- mMSDLPlayer = MSDLPlayer.Companion.createPlayer(vibrator, UI_HELPER_EXECUTOR, null);
+ mMSDLPlayer = MSDLPlayer.Companion.createPlayer(vibrator,
+ java.util.concurrent.Executors.newSingleThreadExecutor(),
+ null /* useHapticFeedbackForToken */);
}
/** Perform MSDL feedback for a token with interaction properties */
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index 9c9b80d..cd8e457 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -21,7 +21,7 @@
import static android.graphics.Paint.FILTER_BITMAP_FLAG;
import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
-import static com.android.launcher3.icons.FastBitmapDrawable.getDisabledColorFilter;
+import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.appwidget.AppWidgetProviderInfo;
@@ -37,6 +37,9 @@
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
@@ -60,8 +63,10 @@
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.PackageItemInfo;
+import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.Themes;
+import com.android.launcher3.widget.LauncherWidgetHolder.ProviderChangedListener;
import java.util.List;
@@ -81,6 +86,8 @@
private final Matrix mMatrix = new Matrix();
private final RectF mPreviewBitmapRect = new RectF();
private final RectF mCanvasRect = new RectF();
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final RunnableList mOnDetachCleanup = new RunnableList();
private final LauncherWidgetHolder mWidgetHolder;
private final LauncherAppWidgetProviderInfo mAppwidget;
@@ -90,7 +97,6 @@
private final CharSequence mLabel;
private OnClickListener mClickListener;
- private SafeCloseable mOnDetachCleanup;
private int mDragFlags;
@@ -210,16 +216,15 @@
protected void onAttachedToWindow() {
super.onAttachedToWindow();
+ mOnDetachCleanup.executeAllAndClear();
if ((mAppwidget != null)
&& !mInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)
&& mInfo.restoreStatus != LauncherAppWidgetInfo.RESTORE_COMPLETED) {
// If the widget is not completely restored, but has a valid ID, then listen of
// updates from provider app for potential restore complete.
- if (mOnDetachCleanup != null) {
- mOnDetachCleanup.close();
- }
- mOnDetachCleanup = mWidgetHolder.addOnUpdateListener(
+ SafeCloseable updateCleanup = mWidgetHolder.addOnUpdateListener(
mInfo.appWidgetId, mAppwidget, this::checkIfRestored);
+ mOnDetachCleanup.add(updateCleanup::close);
checkIfRestored();
}
}
@@ -227,10 +232,7 @@
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- if (mOnDetachCleanup != null) {
- mOnDetachCleanup.close();
- mOnDetachCleanup = null;
- }
+ mOnDetachCleanup.executeAllAndClear();
}
/**
@@ -295,43 +297,30 @@
mCenterDrawable.setCallback(null);
mCenterDrawable = null;
}
- mDragFlags = 0;
- if (info.bitmap.icon != null) {
- mDragFlags = FLAG_DRAW_ICON;
+ mDragFlags = FLAG_DRAW_ICON;
- Drawable widgetCategoryIcon = getWidgetCategoryIcon();
- // The view displays three modes,
- // 1) App icon in the center
- // 2) Preload icon in the center
- // 3) App icon in the center with a setup icon on the top left corner.
- if (mDisabledForSafeMode) {
- if (widgetCategoryIcon == null) {
- FastBitmapDrawable disabledIcon = info.newIcon(getContext());
- disabledIcon.setIsDisabled(true);
- mCenterDrawable = disabledIcon;
- } else {
- widgetCategoryIcon.setColorFilter(getDisabledColorFilter());
- mCenterDrawable = widgetCategoryIcon;
- }
- mSettingIconDrawable = null;
- } else if (isReadyForClickSetup()) {
- mCenterDrawable = widgetCategoryIcon == null
- ? info.newIcon(getContext())
- : widgetCategoryIcon;
- mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate();
- updateSettingColor(info.bitmap.color);
+ // The view displays three modes,
+ // 1) App icon in the center
+ // 2) Preload icon in the center
+ // 3) App icon in the center with a setup icon on the top left corner.
+ if (mDisabledForSafeMode) {
+ FastBitmapDrawable disabledIcon = info.newIcon(getContext());
+ disabledIcon.setIsDisabled(true);
+ mCenterDrawable = disabledIcon;
+ mSettingIconDrawable = null;
+ } else if (isReadyForClickSetup()) {
+ mCenterDrawable = info.newIcon(getContext());
+ mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate();
+ updateSettingColor(info.bitmap.color);
- mDragFlags |= FLAG_DRAW_SETTINGS | FLAG_DRAW_LABEL;
- } else {
- mCenterDrawable = widgetCategoryIcon == null
- ? newPendingIcon(getContext(), info)
- : widgetCategoryIcon;
- mSettingIconDrawable = null;
- applyState();
- }
- mCenterDrawable.setCallback(this);
- mDrawableSizeChanged = true;
+ mDragFlags |= FLAG_DRAW_SETTINGS | FLAG_DRAW_LABEL;
+ } else {
+ mCenterDrawable = newPendingIcon(getContext(), info);
+ mSettingIconDrawable = null;
+ applyState();
}
+ mCenterDrawable.setCallback(this);
+ mDrawableSizeChanged = true;
invalidate();
}
@@ -350,6 +339,11 @@
}
public void applyState() {
+ if (mCenterDrawable instanceof FastBitmapDrawable fb
+ && mInfo.pendingItemInfo != null
+ && !fb.isSameInfo(mInfo.pendingItemInfo.bitmap)) {
+ reapplyItemInfo(mInfo.pendingItemInfo);
+ }
if (mCenterDrawable != null) {
mCenterDrawable.setLevel(Math.max(mInfo.installProgress, 0));
}
@@ -486,16 +480,72 @@
}
/**
- * Returns the widget category icon for {@link #mInfo}.
- *
- * <p>If {@link #mInfo}'s category is {@code PackageItemInfo#NO_CATEGORY} or unknown, returns
- * {@code null}.
+ * Creates a runnable runnable which tries to refresh the widget if it is restored
*/
- @Nullable
- private Drawable getWidgetCategoryIcon() {
- if (mInfo.pendingItemInfo.widgetCategory == WidgetSections.NO_CATEGORY) {
- return null;
+ public void postProviderAvailabilityCheck() {
+ if (!mInfo.hasRestoreFlag(FLAG_PROVIDER_NOT_READY) && getAppWidgetInfo() == null) {
+ // If the info state suggests that the provider is ready, but there is no
+ // provider info attached on this pending view, recreate when the provider is available
+ DeferredWidgetRefresh restoreRunnable = new DeferredWidgetRefresh();
+ mOnDetachCleanup.add(restoreRunnable::cleanup);
+ mHandler.post(restoreRunnable::notifyWidgetProvidersChanged);
}
- return mInfo.pendingItemInfo.newIcon(getContext());
+ }
+
+ /**
+ * Used as a workaround to ensure that the AppWidgetService receives the
+ * PACKAGE_ADDED broadcast before updating widgets.
+ *
+ * This class will periodically check for the availability of the WidgetProvider as a result
+ * of providerChanged callback from the host. When the provider is available or a timeout of
+ * 10-sec is reached, it reinflates the pending-widget which in-turn goes through the process
+ * of re-evaluating the pending state of the widget,
+ */
+ private class DeferredWidgetRefresh implements Runnable, ProviderChangedListener {
+ private boolean mRefreshPending = true;
+
+ DeferredWidgetRefresh() {
+ mWidgetHolder.addProviderChangeListener(this);
+ // Force refresh after 10 seconds, if we don't get the provider changed event.
+ // This could happen when the provider is no longer available in the app.
+ Message msg = Message.obtain(getHandler(), this);
+ msg.obj = DeferredWidgetRefresh.class;
+ mHandler.sendMessageDelayed(msg, 10000);
+ }
+
+ /**
+ * Reinflate the widget if it is still attached.
+ */
+ @Override
+ public void run() {
+ cleanup();
+ if (mRefreshPending) {
+ reInflate();
+ mRefreshPending = false;
+ }
+ }
+
+ @Override
+ public void notifyWidgetProvidersChanged() {
+ final AppWidgetProviderInfo widgetInfo;
+ WidgetManagerHelper widgetHelper = new WidgetManagerHelper(getContext());
+ if (mInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
+ widgetInfo = widgetHelper.findProvider(mInfo.providerName, mInfo.user);
+ } else {
+ widgetInfo = widgetHelper.getLauncherAppWidgetInfo(mInfo.appWidgetId,
+ mInfo.getTargetComponent());
+ }
+ if (widgetInfo != null) {
+ run();
+ }
+ }
+
+ /**
+ * Removes any scheduled callbacks and change listeners, no-op if nothing is scheduled
+ */
+ public void cleanup() {
+ mWidgetHolder.removeProviderChangeListener(this);
+ mHandler.removeCallbacks(this);
+ }
}
}
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait.txt
index 4c366c3..a13d63b 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait.txt
@@ -55,7 +55,7 @@
bottomSheetCloseDuration: 267
bottomSheetWorkspaceScale: 1.0
bottomSheetDepth: 0.0
- allAppsShiftRange: 788.0px (300.1905dp)
+ allAppsShiftRange: 2400.0px (914.2857dp)
allAppsOpenDuration: 600
allAppsCloseDuration: 300
allAppsIconSizePx: 147.0px (56.0dp)
@@ -66,7 +66,7 @@
allAppsBorderSpacePxX: 42.0px (16.0dp)
allAppsBorderSpacePxY: 42.0px (16.0dp)
numShownAllAppsColumns: 5
- allAppsPadding.top: 0.0px (0.0dp)
+ allAppsPadding.top: 118.0px (44.95238dp)
allAppsPadding.left: 0.0px (0.0dp)
allAppsPadding.right: 0.0px (0.0dp)
allAppsLeftRightMargin: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait3Button.txt
index 6db9534..3c24885 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait3Button.txt
@@ -55,7 +55,7 @@
bottomSheetCloseDuration: 267
bottomSheetWorkspaceScale: 1.0
bottomSheetDepth: 0.0
- allAppsShiftRange: 788.0px (300.1905dp)
+ allAppsShiftRange: 2400.0px (914.2857dp)
allAppsOpenDuration: 600
allAppsCloseDuration: 300
allAppsIconSizePx: 147.0px (56.0dp)
@@ -66,7 +66,7 @@
allAppsBorderSpacePxX: 42.0px (16.0dp)
allAppsBorderSpacePxY: 42.0px (16.0dp)
numShownAllAppsColumns: 5
- allAppsPadding.top: 0.0px (0.0dp)
+ allAppsPadding.top: 118.0px (44.95238dp)
allAppsPadding.left: 0.0px (0.0dp)
allAppsPadding.right: 0.0px (0.0dp)
allAppsLeftRightMargin: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar.txt
index 6e76b13..5e06513 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar.txt
@@ -55,7 +55,7 @@
bottomSheetCloseDuration: 267
bottomSheetWorkspaceScale: 1.0
bottomSheetDepth: 0.0
- allAppsShiftRange: 788.0px (300.1905dp)
+ allAppsShiftRange: 1080.0px (411.42856dp)
allAppsOpenDuration: 600
allAppsCloseDuration: 300
allAppsIconSizePx: 147.0px (56.0dp)
@@ -66,7 +66,7 @@
allAppsBorderSpacePxX: 42.0px (16.0dp)
allAppsBorderSpacePxY: 42.0px (16.0dp)
numShownAllAppsColumns: 5
- allAppsPadding.top: 0.0px (0.0dp)
+ allAppsPadding.top: 74.0px (28.190475dp)
allAppsPadding.left: 0.0px (0.0dp)
allAppsPadding.right: 0.0px (0.0dp)
allAppsLeftRightMargin: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar3Button.txt
index 1af9215..d107988 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar3Button.txt
@@ -55,7 +55,7 @@
bottomSheetCloseDuration: 267
bottomSheetWorkspaceScale: 1.0
bottomSheetDepth: 0.0
- allAppsShiftRange: 788.0px (300.1905dp)
+ allAppsShiftRange: 1080.0px (411.42856dp)
allAppsOpenDuration: 600
allAppsCloseDuration: 300
allAppsIconSizePx: 147.0px (56.0dp)
@@ -66,7 +66,7 @@
allAppsBorderSpacePxX: 42.0px (16.0dp)
allAppsBorderSpacePxY: 42.0px (16.0dp)
numShownAllAppsColumns: 5
- allAppsPadding.top: 0.0px (0.0dp)
+ allAppsPadding.top: 74.0px (28.190475dp)
allAppsPadding.left: 0.0px (0.0dp)
allAppsPadding.right: 0.0px (0.0dp)
allAppsLeftRightMargin: 0.0px (0.0dp)
diff --git a/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt b/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
index 553d08c..15accbd 100644
--- a/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
@@ -17,11 +17,14 @@
package com.android.launcher3.folder
import android.R
-import android.graphics.Bitmap
import android.os.Process
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherPrefs
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
import com.android.launcher3.graphics.PreloadIconDrawable
import com.android.launcher3.graphics.ThemeManager
import com.android.launcher3.icons.BitmapInfo
@@ -30,13 +33,14 @@
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver
import com.android.launcher3.icons.PlaceHolderIconDrawable
import com.android.launcher3.icons.UserBadgeDrawable
-import com.android.launcher3.icons.mono.MonoThemedBitmap
import com.android.launcher3.model.data.FolderInfo
import com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED
import com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE
import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.util.ActivityContextWrapper
+import com.android.launcher3.util.AllModulesForTest
import com.android.launcher3.util.Executors
+import com.android.launcher3.util.FakePrefsModule
import com.android.launcher3.util.FlagOp
import com.android.launcher3.util.LauncherLayoutBuilder
import com.android.launcher3.util.LauncherModelHelper
@@ -44,10 +48,19 @@
import com.android.launcher3.util.TestUtil
import com.android.launcher3.util.UserIconInfo
import com.google.common.truth.Truth.assertThat
+import dagger.Component
+import kotlin.annotation.AnnotationRetention.RUNTIME
+import kotlin.annotation.AnnotationTarget.FUNCTION
+import kotlin.annotation.AnnotationTarget.PROPERTY_GETTER
+import kotlin.annotation.AnnotationTarget.PROPERTY_SETTER
import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
+import org.junit.rules.TestRule
+import org.junit.runner.Description
import org.junit.runner.RunWith
+import org.junit.runners.model.Statement
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doReturn
@@ -61,6 +74,8 @@
@RunWith(AndroidJUnit4::class)
class PreviewItemManagerTest {
+ @get:Rule val theseStateRule = ThemeStateRule()
+
private lateinit var previewItemManager: PreviewItemManager
private lateinit var context: SandboxModelContext
private lateinit var folderItems: ArrayList<WorkspaceItemInfo>
@@ -68,15 +83,14 @@
private lateinit var folderIcon: FolderIcon
private lateinit var iconCache: IconCache
- private var defaultThemedIcons = false
-
- private val themeManager: ThemeManager
- get() = ThemeManager.INSTANCE.get(context)
-
@Before
fun setup() {
modelHelper = LauncherModelHelper()
context = modelHelper.sandboxContext
+ context.initDaggerComponent(DaggerPreviewItemManagerTestComponent.builder())
+ theseStateRule.themeState?.let {
+ LauncherPrefs.get(context).putSync(ThemeManager.THEMED_ICONS.to(it))
+ }
folderIcon = FolderIcon(ActivityContextWrapper(context))
val app = spy(LauncherAppState.getInstance(context))
@@ -99,27 +113,16 @@
)
.loadModelSync()
+ folderIcon.mInfo =
+ modelHelper.bgDataModel.itemsIdMap.find { it.itemType == ITEM_TYPE_FOLDER }
+ as FolderInfo
// Use getAppContents() to "cast" contents to WorkspaceItemInfo so we can set bitmaps
- folderItems = modelHelper.bgDataModel.collections.valueAt(0).getAppContents()
- folderIcon.mInfo = modelHelper.bgDataModel.collections.valueAt(0) as FolderInfo
- folderIcon.mInfo.getContents().addAll(folderItems)
-
- // Set first icon to be themed.
- folderItems[0].bitmap.themedBitmap =
- MonoThemedBitmap(
- folderItems[0].bitmap.icon,
- Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888),
- )
+ folderItems = folderIcon.mInfo.getAppContents()
// Set second icon to be non-themed.
folderItems[1].bitmap.themedBitmap = null
// Set third icon to be themed with badge.
- folderItems[2].bitmap.themedBitmap =
- MonoThemedBitmap(
- folderItems[2].bitmap.icon,
- Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888),
- )
folderItems[2].bitmap =
folderItems[2].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
@@ -127,20 +130,17 @@
folderItems[3].bitmap =
folderItems[3].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
folderItems[3].bitmap.themedBitmap = null
-
- defaultThemedIcons = themeManager.isMonoThemeEnabled
}
@After
@Throws(Exception::class)
fun tearDown() {
- themeManager.isMonoThemeEnabled = defaultThemedIcons
modelHelper.destroy()
}
@Test
+ @MonoThemeEnabled(true)
fun checkThemedIconWithThemingOn_iconShouldBeThemed() {
- themeManager.isMonoThemeEnabled = true
val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
previewItemManager.setDrawable(drawingParams, folderItems[0])
@@ -149,8 +149,8 @@
}
@Test
+ @MonoThemeEnabled(false)
fun checkThemedIconWithThemingOff_iconShouldNotBeThemed() {
- themeManager.isMonoThemeEnabled = false
val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
previewItemManager.setDrawable(drawingParams, folderItems[0])
@@ -159,8 +159,8 @@
}
@Test
+ @MonoThemeEnabled(true)
fun checkUnthemedIconWithThemingOn_iconShouldNotBeThemed() {
- themeManager.isMonoThemeEnabled = true
val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
previewItemManager.setDrawable(drawingParams, folderItems[1])
@@ -169,8 +169,8 @@
}
@Test
+ @MonoThemeEnabled(false)
fun checkUnthemedIconWithThemingOff_iconShouldNotBeThemed() {
- themeManager.isMonoThemeEnabled = false
val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
previewItemManager.setDrawable(drawingParams, folderItems[1])
@@ -179,8 +179,8 @@
}
@Test
+ @MonoThemeEnabled(true)
fun checkThemedIconWithBadgeWithThemingOn_iconAndBadgeShouldBeThemed() {
- themeManager.isMonoThemeEnabled = true
val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
previewItemManager.setDrawable(drawingParams, folderItems[2])
@@ -192,8 +192,8 @@
}
@Test
+ @MonoThemeEnabled(true)
fun checkUnthemedIconWithBadgeWithThemingOn_badgeShouldBeThemed() {
- themeManager.isMonoThemeEnabled = true
val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
previewItemManager.setDrawable(drawingParams, folderItems[3])
@@ -205,8 +205,8 @@
}
@Test
+ @MonoThemeEnabled(false)
fun checkUnthemedIconWithBadgeWithThemingOff_iconAndBadgeShouldNotBeThemed() {
- themeManager.isMonoThemeEnabled = false
val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
previewItemManager.setDrawable(drawingParams, folderItems[3])
@@ -278,3 +278,28 @@
private fun profileFlagOp(type: Int) =
UserIconInfo(Process.myUserHandle(), type).applyBitmapInfoFlags(FlagOp.NO_OP)
}
+
+class ThemeStateRule : TestRule {
+
+ var themeState: Boolean? = null
+
+ override fun apply(base: Statement, description: Description): Statement {
+ themeState = description.getAnnotation(MonoThemeEnabled::class.java)?.value
+ return base
+ }
+}
+
+// Annotation for tests that need to be run with quickstep enabled and disabled.
+@Retention(RUNTIME)
+@Target(FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER)
+annotation class MonoThemeEnabled(val value: Boolean = false)
+
+@LauncherAppSingleton
+@Component(modules = [AllModulesForTest::class, FakePrefsModule::class])
+interface PreviewItemManagerTestComponent : LauncherAppComponent {
+
+ @Component.Builder
+ interface Builder : LauncherAppComponent.Builder {
+ override fun build(): PreviewItemManagerTestComponent
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java b/tests/multivalentTests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
index 1e2431f..0ae4d00 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
@@ -16,6 +16,8 @@
package com.android.launcher3.model;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY;
import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
@@ -42,6 +44,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.List;
+
/**
* Tests for layout parser for remote layout
*/
@@ -63,14 +67,23 @@
mModelHelper.destroy();
}
+ private List<ItemInfo> getWorkspaceItems() {
+ return mModelHelper
+ .getBgDataModel()
+ .itemsIdMap
+ .stream()
+ .filter(i -> i.container == CONTAINER_DESKTOP || i.container == CONTAINER_HOTSEAT)
+ .toList();
+ }
+
@Test
public void testCustomProfileLoaded_with_icon_on_hotseat() throws Exception {
writeLayoutAndLoad(new LauncherLayoutBuilder().atHotseat(0)
.putApp(TEST_PACKAGE, TEST_ACTIVITY));
// Verify one item in hotseat
- assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
- ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0);
+ assertEquals(1, getWorkspaceItems().size());
+ ItemInfo info = getWorkspaceItems().get(0);
assertEquals(LauncherSettings.Favorites.CONTAINER_HOTSEAT, info.container);
assertEquals(LauncherSettings.Favorites.ITEM_TYPE_APPLICATION, info.itemType);
}
@@ -84,8 +97,8 @@
.build());
// Verify folder
- assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
- ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0);
+ assertEquals(1, getWorkspaceItems().size());
+ ItemInfo info = getWorkspaceItems().get(0);
assertEquals(LauncherSettings.Favorites.ITEM_TYPE_FOLDER, info.itemType);
assertEquals(3, ((FolderInfo) info).getContents().size());
}
@@ -99,8 +112,8 @@
.build());
// Verify folder
- assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
- ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0);
+ assertEquals(1, getWorkspaceItems().size());
+ ItemInfo info = getWorkspaceItems().get(0);
assertEquals(LauncherSettings.Favorites.ITEM_TYPE_FOLDER, info.itemType);
assertEquals(3, ((FolderInfo) info).getContents().size());
assertEquals("CustomFolder", info.title.toString());
@@ -124,8 +137,8 @@
.putWidget(pendingAppPkg, "PlaceholderWidget", 2, 2));
// Verify widget
- assertEquals(1, mModelHelper.getBgDataModel().appWidgets.size());
- ItemInfo info = mModelHelper.getBgDataModel().appWidgets.get(0);
+ assertEquals(1, getWorkspaceItems().size());
+ ItemInfo info = getWorkspaceItems().get(0);
assertEquals(LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET, info.itemType);
assertEquals(2, info.spanX);
assertEquals(2, info.spanY);
@@ -138,8 +151,8 @@
.putShortcut(TEST_PACKAGE, "shortcut2"));
// Verify one item in hotseat
- assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
- ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0);
+ assertEquals(1, getWorkspaceItems().size());
+ ItemInfo info = getWorkspaceItems().get(0);
assertEquals(LauncherSettings.Favorites.CONTAINER_HOTSEAT, info.container);
assertEquals(LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT, info.itemType);
}
@@ -154,8 +167,8 @@
.build());
// Verify folder
- assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
- FolderInfo info = (FolderInfo) mModelHelper.getBgDataModel().workspaceItems.get(0);
+ assertEquals(1, getWorkspaceItems().size());
+ FolderInfo info = (FolderInfo) getWorkspaceItems().get(0);
assertEquals(3, info.getContents().size());
// Verify last icon
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt
index e8f778f..f357487 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt
@@ -18,8 +18,10 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER
import com.android.launcher3.icons.BitmapInfo
import com.android.launcher3.icons.waitForUpdateHandlerToFinish
+import com.android.launcher3.model.data.FolderInfo
import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.util.Executors
import com.android.launcher3.util.LauncherLayoutBuilder
@@ -149,11 +151,13 @@
// Reload again with correct icon state
app.model.forceReload()
modelHelper.loadModelSync()
- val collections = modelHelper.getBgDataModel().collections
-
- assertThat(collections.size()).isEqualTo(1)
- assertThat(collections.valueAt(0).getAppContents().size).isEqualTo(itemCount)
- return collections.valueAt(0).getAppContents()
+ val collections =
+ modelHelper.bgDataModel.itemsIdMap
+ .filter { it.itemType == ITEM_TYPE_FOLDER }
+ .map { it as FolderInfo }
+ assertThat(collections.size).isEqualTo(1)
+ assertThat(collections[0].getAppContents().size).isEqualTo(itemCount)
+ return collections[0].getAppContents()
}
private fun verifyHighRes(items: ArrayList<WorkspaceItemInfo>, vararg indices: Int) {
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/GridSizeMigrationTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/GridSizeMigrationTest.kt
index eee6191..adf38fe 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/GridSizeMigrationTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/GridSizeMigrationTest.kt
@@ -134,6 +134,7 @@
var gridSizeMigrationLogic = GridSizeMigrationLogic()
val idsInUse = mutableListOf<Int>()
gridSizeMigrationLogic.migrateHotseat(
+ 5,
idp.numDatabaseHotseatIcons,
srcReader,
destReader,
@@ -152,6 +153,7 @@
dbHelper,
srcReader,
destReader,
+ 5,
idp.numDatabaseHotseatIcons,
Point(idp.numColumns, idp.numRows),
DeviceGridState(context),
@@ -277,6 +279,7 @@
var gridSizeMigrationLogic = GridSizeMigrationLogic()
val idsInUse = mutableListOf<Int>()
gridSizeMigrationLogic.migrateHotseat(
+ 5,
idp.numDatabaseHotseatIcons,
readerGridA,
readerGridB,
@@ -295,6 +298,7 @@
dbHelper,
readerGridA,
readerGridB,
+ 5,
idp.numDatabaseHotseatIcons,
Point(idp.numColumns, idp.numRows),
DeviceGridState(context),
@@ -317,8 +321,8 @@
// 2 1 3 4
verifyHotseat(
c,
- idp,
mutableListOf(testPackage2, testPackage1, testPackage3, testPackage4).toList(),
+ 4,
)
// Check workspace items in grid B
@@ -348,7 +352,7 @@
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 0, 2, testPackage9)
// migrate from B -> A
- migrateGrid(dbHelper, readerGridB, readerGridA, 5, 5, 5)
+ migrateGrid(dbHelper, readerGridB, readerGridA, 4, 5, 5, 5)
// Check hotseat items in grid A
c =
@@ -362,11 +366,12 @@
null,
) ?: throw IllegalStateException()
// Expected hotseat items in grid A
- // 1 2 _ 3 4
+ // 1 2 4 3 4
verifyHotseat(
c,
- idp,
- mutableListOf(testPackage1, testPackage2, null, testPackage3, testPackage4).toList(),
+ mutableListOf(testPackage1, testPackage2, testPackage4, testPackage3, testPackage4)
+ .toList(),
+ 5,
)
// Check workspace items in grid A
@@ -404,6 +409,7 @@
dbHelper,
readerGridA,
readerGridB,
+ 5,
idp.numDatabaseHotseatIcons,
idp.numColumns,
idp.numRows,
@@ -424,8 +430,8 @@
// 2 1 3 4
verifyHotseat(
c,
- idp,
mutableListOf(testPackage2, testPackage1, testPackage3, testPackage4).toList(),
+ 4,
)
// Check workspace items in grid B
@@ -452,10 +458,150 @@
assertThat(locMap[testPackage9]).isEqualTo(Triple(0, 0, 2))
}
+ @Test
+ @Throws(Exception::class)
+ @EnableFlags(Flags.FLAG_GRID_MIGRATION_REFACTOR)
+ fun testHotseatMigrationToSmallerGridBackAndForthFlagOn() {
+ testHotseatMigrationToSmallerGridBackAndForth()
+ }
+
+ @Test
+ @Throws(Exception::class)
+ @DisableFlags(Flags.FLAG_GRID_MIGRATION_REFACTOR)
+ fun testHotseatMigrationToSmallerGridBackAndForthFlagOff() {
+ testHotseatMigrationToSmallerGridBackAndForth()
+ }
+
+ /** Old migration logic, should be modified once is not needed anymore */
+ @Throws(Exception::class)
+ fun testHotseatMigrationToSmallerGridBackAndForth() {
+ // Hotseat items in grid A
+ // 1 2 3 4 5
+ addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_HOTSEAT, 0, 0, testPackage1, 1, TMP_TABLE)
+ addItem(ITEM_TYPE_DEEP_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE)
+ addItem(ITEM_TYPE_DEEP_SHORTCUT, 2, CONTAINER_HOTSEAT, 0, 0, testPackage3, 3, TMP_TABLE)
+ addItem(ITEM_TYPE_APPLICATION, 3, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE)
+ addItem(ITEM_TYPE_APPLICATION, 4, CONTAINER_HOTSEAT, 0, 0, testPackage5, 5, TMP_TABLE)
+
+ // Hotseat items in grid B
+ // 2 _ _ _
+ addItem(ITEM_TYPE_DEEP_SHORTCUT, 0, CONTAINER_HOTSEAT, 0, 0, testPackage2)
+
+ idp.numDatabaseHotseatIcons = 4
+ idp.numColumns = 4
+ idp.numRows = 4
+ val readerGridA = DbReader(db, TMP_TABLE, context)
+ val readerGridB = DbReader(db, TABLE_NAME, context)
+ // migrate from A -> B
+ if (Flags.gridMigrationRefactor()) {
+ var gridSizeMigrationLogic = GridSizeMigrationLogic()
+ val idsInUse = mutableListOf<Int>()
+ gridSizeMigrationLogic.migrateHotseat(
+ 5,
+ idp.numDatabaseHotseatIcons,
+ readerGridA,
+ readerGridB,
+ dbHelper,
+ idsInUse,
+ )
+ gridSizeMigrationLogic.migrateWorkspace(
+ readerGridA,
+ readerGridB,
+ dbHelper,
+ Point(idp.numColumns, idp.numRows),
+ idsInUse,
+ )
+ } else {
+ GridSizeMigrationDBController.migrate(
+ dbHelper,
+ readerGridA,
+ readerGridB,
+ 5,
+ idp.numDatabaseHotseatIcons,
+ Point(idp.numColumns, idp.numRows),
+ DeviceGridState(context),
+ DeviceGridState(idp),
+ )
+ }
+
+ // Check hotseat items in grid B
+ var c =
+ db.query(
+ TABLE_NAME,
+ arrayOf(SCREEN, INTENT),
+ "container=$CONTAINER_HOTSEAT",
+ null,
+ SCREEN,
+ null,
+ null,
+ ) ?: throw IllegalStateException()
+ // Expected hotseat items in grid B
+ // 2 1 3 4
+ verifyHotseat(
+ c,
+ mutableListOf(testPackage2, testPackage1, testPackage3, testPackage4).toList(),
+ 4,
+ )
+
+ // migrate from B -> A
+ migrateGrid(dbHelper, readerGridB, readerGridA, idp.numDatabaseHotseatIcons, 5, 5, 5)
+
+ // Check hotseat items in grid A
+ c =
+ db.query(
+ TMP_TABLE,
+ arrayOf(SCREEN, INTENT),
+ "container=$CONTAINER_HOTSEAT",
+ null,
+ SCREEN,
+ null,
+ null,
+ ) ?: throw IllegalStateException()
+ // Expected hotseat items in grid A
+ // 1 2 3 4 5
+ verifyHotseat(
+ c,
+ mutableListOf(testPackage1, testPackage2, testPackage3, testPackage4, testPackage5)
+ .toList(),
+ 5,
+ )
+
+ // migrate from A -> B
+ migrateGrid(
+ dbHelper,
+ readerGridA,
+ readerGridB,
+ 5,
+ idp.numDatabaseHotseatIcons,
+ idp.numColumns,
+ idp.numRows,
+ )
+
+ // Check hotseat items in grid B
+ c =
+ db.query(
+ TABLE_NAME,
+ arrayOf(SCREEN, INTENT),
+ "container=$CONTAINER_HOTSEAT",
+ null,
+ SCREEN,
+ null,
+ null,
+ ) ?: throw IllegalStateException()
+ // Expected hotseat items in grid B
+ // 2 1 3 4
+ verifyHotseat(
+ c,
+ mutableListOf(testPackage2, testPackage1, testPackage3, testPackage4).toList(),
+ 4,
+ )
+ }
+
private fun migrateGrid(
dbHelper: DatabaseHelper,
srcReader: DbReader,
destReader: DbReader,
+ srcHotseatSize: Int,
destHotseatSize: Int,
pointX: Int,
pointY: Int,
@@ -464,7 +610,8 @@
var gridSizeMigrationLogic = GridSizeMigrationLogic()
val idsInUse = mutableListOf<Int>()
gridSizeMigrationLogic.migrateHotseat(
- idp.numDatabaseHotseatIcons,
+ srcHotseatSize,
+ destHotseatSize,
srcReader,
destReader,
dbHelper,
@@ -482,6 +629,7 @@
dbHelper,
srcReader,
destReader,
+ srcHotseatSize,
destHotseatSize,
Point(pointX, pointY),
DeviceGridState(idp),
@@ -490,8 +638,8 @@
}
}
- private fun verifyHotseat(c: Cursor, idp: InvariantDeviceProfile, expected: List<String?>) {
- assertThat(c.count).isEqualTo(idp.numDatabaseHotseatIcons)
+ private fun verifyHotseat(c: Cursor, expected: List<String?>, expectedCount: Int) {
+ assertThat(c.count).isEqualTo(expectedCount)
val screenIndex = c.getColumnIndex(SCREEN)
val intentIndex = c.getColumnIndex(INTENT)
expected.forEachIndexed { idx, pkg ->
@@ -584,6 +732,7 @@
dbHelper,
srcReader,
destReader,
+ 4,
idp.numDatabaseHotseatIcons,
idp.numColumns,
idp.numRows,
@@ -651,6 +800,7 @@
dbHelper,
srcReader,
destReader,
+ 6,
idp.numDatabaseHotseatIcons,
idp.numColumns,
idp.numRows,
@@ -729,6 +879,7 @@
dbHelper,
srcReader,
destReader,
+ 2,
idp.numDatabaseHotseatIcons,
idp.numColumns,
idp.numRows,
@@ -801,6 +952,7 @@
dbHelper,
srcReader,
destReader,
+ 5,
idp.numDatabaseHotseatIcons,
idp.numColumns,
idp.numRows,
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/ShortcutsChangedTaskTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/ShortcutsChangedTaskTest.kt
new file mode 100644
index 0000000..fb6d038
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/model/ShortcutsChangedTaskTest.kt
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model
+
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.ApplicationInfo.FLAG_INSTALLED
+import android.content.pm.LauncherApps
+import android.content.pm.ShortcutInfo
+import android.os.Process.myUserHandle
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
+import com.android.launcher3.icons.BitmapInfo
+import com.android.launcher3.icons.CacheableShortcutInfo
+import com.android.launcher3.icons.IconCache
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.shortcuts.ShortcutKey
+import com.android.launcher3.util.ComponentKey
+import com.android.launcher3.util.IntSparseArrayMap
+import com.android.launcher3.util.LauncherModelHelper
+import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
+import com.google.common.truth.Truth.assertThat
+import java.util.function.Predicate
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShortcutsChangedTaskTest {
+ private lateinit var shortcutsChangedTask: ShortcutsChangedTask
+ private lateinit var modelHelper: LauncherModelHelper
+ private lateinit var context: SandboxModelContext
+ private lateinit var launcherApps: LauncherApps
+ private var shortcuts: List<ShortcutInfo> = emptyList()
+
+ private val expectedPackage: String = "expected"
+ private val expectedShortcutId: String = "shortcut_id"
+ private val user: UserHandle = myUserHandle()
+ private val mockTaskController: ModelTaskController = mock()
+ private val mockAllApps: AllAppsList = mock()
+ private val mockAppState: LauncherAppState = mock()
+ private val mockIconCache: IconCache = mock()
+
+ private val expectedWai =
+ WorkspaceItemInfo().apply {
+ id = 1
+ itemType = ITEM_TYPE_DEEP_SHORTCUT
+ intent =
+ Intent().apply {
+ `package` = expectedPackage
+ putExtra(ShortcutKey.EXTRA_SHORTCUT_ID, expectedShortcutId)
+ }
+ }
+
+ @Before
+ fun setup() {
+ modelHelper = LauncherModelHelper()
+ modelHelper.loadModelSync()
+ context = modelHelper.sandboxContext
+ launcherApps = context.spyService(LauncherApps::class.java)
+ whenever(mockTaskController.app).thenReturn(mockAppState)
+ whenever(mockAppState.context).thenReturn(context)
+ whenever(mockAppState.iconCache).thenReturn(mockIconCache)
+ whenever(mockIconCache.getShortcutIcon(eq(expectedWai), any<CacheableShortcutInfo>()))
+ .then { _ -> { expectedWai.bitmap = BitmapInfo.LOW_RES_INFO } }
+ shortcuts = emptyList()
+ shortcutsChangedTask = ShortcutsChangedTask(expectedPackage, shortcuts, user, false)
+ }
+
+ @After
+ fun teardown() {
+ modelHelper.destroy()
+ }
+
+ @Test
+ fun `When installed pinned shortcut is found then keep in workspace`() {
+ // Given
+ shortcuts =
+ listOf(
+ mock<ShortcutInfo>().apply {
+ whenever(isPinned).thenReturn(true)
+ whenever(id).thenReturn(expectedShortcutId)
+ }
+ )
+ val items: IntSparseArrayMap<ItemInfo> = modelHelper.bgDataModel.itemsIdMap
+ items.put(expectedWai.id, expectedWai)
+ doReturn(
+ ApplicationInfo().apply {
+ enabled = true
+ flags = flags or FLAG_INSTALLED
+ isArchived = false
+ }
+ )
+ .whenever(launcherApps)
+ .getApplicationInfo(eq(expectedPackage), any(), eq(user))
+ doReturn(shortcuts).whenever(launcherApps).getShortcuts(any(), eq(user))
+ // When
+ shortcutsChangedTask.execute(mockTaskController, modelHelper.bgDataModel, mockAllApps)
+ // Then
+ verify(mockAppState.iconCache)
+ .getShortcutIcon(eq(expectedWai), any<CacheableShortcutInfo>())
+ verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWai))
+ }
+
+ @Test
+ fun `When installed unpinned shortcut is found then remove from workspace`() {
+ // Given
+ shortcuts =
+ listOf(
+ mock<ShortcutInfo>().apply {
+ whenever(isPinned).thenReturn(false)
+ whenever(id).thenReturn(expectedShortcutId)
+ }
+ )
+ val items: IntSparseArrayMap<ItemInfo> = modelHelper.bgDataModel.itemsIdMap
+ items.put(expectedWai.id, expectedWai)
+ doReturn(
+ ApplicationInfo().apply {
+ enabled = true
+ flags = flags or FLAG_INSTALLED
+ isArchived = false
+ }
+ )
+ .whenever(launcherApps)
+ .getApplicationInfo(eq(expectedPackage), any(), eq(user))
+ doReturn(shortcuts).whenever(launcherApps).getShortcuts(any(), eq(user))
+ // When
+ shortcutsChangedTask.execute(mockTaskController, modelHelper.bgDataModel, mockAllApps)
+ // Then
+ verify(mockTaskController)
+ .deleteAndBindComponentsRemoved(
+ any<Predicate<ItemInfo?>>(),
+ eq("removed because the shortcut is no longer available in shortcut service"),
+ )
+ }
+
+ @Test
+ fun `When shortcut app is uninstalled then skip handling`() {
+ // Given
+ shortcuts =
+ listOf(
+ mock<ShortcutInfo>().apply {
+ whenever(isPinned).thenReturn(true)
+ whenever(id).thenReturn(expectedShortcutId)
+ }
+ )
+ val items: IntSparseArrayMap<ItemInfo> = modelHelper.bgDataModel.itemsIdMap
+ items.put(expectedWai.id, expectedWai)
+ doReturn(
+ ApplicationInfo().apply {
+ enabled = true
+ flags = flags and FLAG_INSTALLED.inv()
+ isArchived = false
+ }
+ )
+ .whenever(launcherApps)
+ .getApplicationInfo(eq(expectedPackage), any(), eq(user))
+ doReturn(shortcuts).whenever(launcherApps).getShortcuts(any(), eq(user))
+ // When
+ shortcutsChangedTask.execute(mockTaskController, modelHelper.bgDataModel, mockAllApps)
+ // Then
+ verify(mockTaskController, times(0)).deleteAndBindComponentsRemoved(any(), any())
+ verify(mockTaskController, times(0)).bindUpdatedWorkspaceItems(any())
+ }
+
+ @Test
+ fun `When archived pinned shortcut is found then keep in workspace`() {
+ // Given
+ shortcuts =
+ listOf(
+ mock<ShortcutInfo>().apply {
+ whenever(isPinned).thenReturn(true)
+ whenever(id).thenReturn(expectedShortcutId)
+ }
+ )
+ val items: IntSparseArrayMap<ItemInfo> = modelHelper.bgDataModel.itemsIdMap
+ items.put(expectedWai.id, expectedWai)
+ doReturn(
+ ApplicationInfo().apply {
+ enabled = true
+ flags = flags or FLAG_INSTALLED
+ isArchived = true
+ }
+ )
+ .whenever(launcherApps)
+ .getApplicationInfo(eq(expectedPackage), any(), eq(user))
+ doReturn(shortcuts).whenever(launcherApps).getShortcuts(any(), eq(user))
+ // When
+ shortcutsChangedTask.execute(mockTaskController, modelHelper.bgDataModel, mockAllApps)
+ // Then
+ verify(mockAppState.iconCache)
+ .getShortcutIcon(eq(expectedWai), any<CacheableShortcutInfo>())
+ verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWai))
+ }
+
+ @Test
+ fun `When archived unpinned shortcut is found then keep in workspace`() {
+ // Given
+ shortcuts =
+ listOf(
+ mock<ShortcutInfo>().apply {
+ whenever(isPinned).thenReturn(true)
+ whenever(id).thenReturn(expectedShortcutId)
+ }
+ )
+ val items: IntSparseArrayMap<ItemInfo> = modelHelper.bgDataModel.itemsIdMap
+ items.put(expectedWai.id, expectedWai)
+ doReturn(
+ ApplicationInfo().apply {
+ enabled = true
+ flags = flags or FLAG_INSTALLED
+ isArchived = true
+ }
+ )
+ .whenever(launcherApps)
+ .getApplicationInfo(eq(expectedPackage), any(), eq(user))
+ doReturn(shortcuts).whenever(launcherApps).getShortcuts(any(), eq(user))
+ // When
+ shortcutsChangedTask.execute(mockTaskController, modelHelper.bgDataModel, mockAllApps)
+ // Then
+ verify(mockAppState.iconCache)
+ .getShortcutIcon(eq(expectedWai), any<CacheableShortcutInfo>())
+ verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWai))
+ }
+
+ @Test
+ fun `When updateIdMap true then trigger deep shortcut binding`() {
+ // Given
+ val expectedShortcut =
+ mock<ShortcutInfo>().apply {
+ whenever(isEnabled).thenReturn(true)
+ whenever(isDeclaredInManifest).thenReturn(true)
+ whenever(activity).thenReturn(ComponentName(expectedPackage, "expectedClass"))
+ whenever(id).thenReturn(expectedShortcutId)
+ whenever(userHandle).thenReturn(user)
+ }
+ shortcuts = listOf(expectedShortcut)
+ val expectedKey = ComponentKey(expectedShortcut.activity, expectedShortcut.userHandle)
+ doReturn(ApplicationInfo())
+ .whenever(launcherApps)
+ .getApplicationInfo(eq(expectedPackage), any(), eq(user))
+ shortcutsChangedTask =
+ ShortcutsChangedTask(
+ packageName = expectedPackage,
+ shortcuts = shortcuts,
+ user = user,
+ shouldUpdateIdMap = true,
+ )
+ // When
+ shortcutsChangedTask.execute(mockTaskController, modelHelper.bgDataModel, mockAllApps)
+ // Then
+ assertThat(modelHelper.bgDataModel.deepShortcutMap).containsEntry(expectedKey, 1)
+ verify(mockTaskController).bindDeepShortcuts(eq(modelHelper.bgDataModel))
+ }
+
+ @Test
+ fun `When updateIdMap false then do not trigger deep shortcut binding`() {
+ // Given
+ val expectedShortcut =
+ mock<ShortcutInfo>().apply {
+ whenever(isEnabled).thenReturn(true)
+ whenever(isDeclaredInManifest).thenReturn(true)
+ whenever(activity).thenReturn(ComponentName(expectedPackage, "expectedClass"))
+ whenever(id).thenReturn(expectedShortcutId)
+ whenever(userHandle).thenReturn(user)
+ }
+ shortcuts = listOf(expectedShortcut)
+ val expectedKey = ComponentKey(expectedShortcut.activity, expectedShortcut.userHandle)
+ doReturn(ApplicationInfo())
+ .whenever(launcherApps)
+ .getApplicationInfo(eq(expectedPackage), any(), eq(user))
+ shortcutsChangedTask =
+ ShortcutsChangedTask(
+ packageName = expectedPackage,
+ shortcuts = shortcuts,
+ user = user,
+ shouldUpdateIdMap = false,
+ )
+ // When
+ shortcutsChangedTask.execute(mockTaskController, modelHelper.bgDataModel, mockAllApps)
+ // Then
+ assertThat(modelHelper.bgDataModel.deepShortcutMap).doesNotContainKey(expectedKey)
+ verify(mockTaskController, times(0)).bindDeepShortcuts(eq(modelHelper.bgDataModel))
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
index d699eee..da87dfc 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
@@ -494,8 +494,7 @@
@Test
fun `When processing Folder then create FolderInfo and mark restored`() {
val actualFolderInfo = FolderInfo()
- mockBgDataModel =
- mock<BgDataModel>().apply { whenever(findOrMakeFolder(1)).thenReturn(actualFolderInfo) }
+ mockBgDataModel = mock<BgDataModel>()
mockCursor =
mock<LoaderCursor>().apply {
user = UserHandle(0)
@@ -509,6 +508,7 @@
whenever(getColumnIndex(Favorites.TITLE)).thenReturn(4)
whenever(getString(4)).thenReturn("title")
whenever(options).thenReturn(5)
+ whenever(findOrMakeFolder(eq(1), any())).thenReturn(actualFolderInfo)
}
val expectedFolderInfo =
FolderInfo().apply {
@@ -600,7 +600,8 @@
// Then
val widgetInfoCaptor = ArgumentCaptor.forClass(LauncherAppWidgetInfo::class.java)
- verify(mockCursor).checkAndAddItem(widgetInfoCaptor.capture(), eq(mockBgDataModel))
+ verify(mockCursor)
+ .checkAndAddItem(widgetInfoCaptor.capture(), eq(mockBgDataModel), anyOrNull())
val actualWidgetInfo = widgetInfoCaptor.value
with(actualWidgetInfo) {
assertThat(providerName).isEqualTo(expectedWidgetInfo.providerName)
@@ -655,7 +656,7 @@
itemProcessorUnderTest.processItem()
// Then
- verify(mockCursor).checkAndAddItem(any(), any())
+ verify(mockCursor).checkAndAddItem(any(), any(), anyOrNull())
}
@Test
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/gridmigration/ValidGridMigrationUnitTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/gridmigration/ValidGridMigrationUnitTest.kt
index b96dbcd..1d1e7eb 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/gridmigration/ValidGridMigrationUnitTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/gridmigration/ValidGridMigrationUnitTest.kt
@@ -138,6 +138,7 @@
val gridSizeMigrationLogic = GridSizeMigrationLogic()
val idsInUse = mutableListOf<Int>()
gridSizeMigrationLogic.migrateHotseat(
+ srcGrid.size.x,
dstGrid.size.x,
GridSizeMigrationDBController.DbReader(it.db, srcGrid.tableName, context),
GridSizeMigrationDBController.DbReader(it.db, dstGrid.tableName, context),
@@ -156,6 +157,7 @@
dbHelper,
GridSizeMigrationDBController.DbReader(it.db, srcGrid.tableName, context),
GridSizeMigrationDBController.DbReader(it.db, dstGrid.tableName, context),
+ srcGrid.size.x,
dstGrid.size.x,
dstGrid.size,
srcGrid.toGridState(),
diff --git a/tests/multivalentTests/src/com/android/launcher3/shapes/ShapesProviderTest.kt b/tests/multivalentTests/src/com/android/launcher3/shapes/ShapesProviderTest.kt
new file mode 100644
index 0000000..2b8896e
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/shapes/ShapesProviderTest.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.shapes
+
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.core.graphics.PathParser
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.Flags.FLAG_ENABLE_LAUNCHER_ICON_SHAPES
+import com.android.launcher3.graphics.IconShape.GenericPathShape
+import com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShapesProviderTest {
+
+ @get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule()
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+ fun `verify valid path arch`() {
+ ShapesProvider.iconShapes["arch"]?.apply {
+ GenericPathShape(pathString)
+ PathParser.createPathFromPathData(pathString)
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+ fun `verify valid path 4_sided_cookie`() {
+ ShapesProvider.iconShapes["4_sided_cookie"]?.apply {
+ GenericPathShape(pathString)
+ PathParser.createPathFromPathData(pathString)
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+ fun `verify valid path seven_sided_cookie`() {
+ ShapesProvider.iconShapes["seven_sided_cookie"]?.apply {
+ GenericPathShape(pathString)
+ PathParser.createPathFromPathData(pathString)
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+ fun `verify valid path sunny`() {
+ ShapesProvider.iconShapes["sunny"]?.apply {
+ GenericPathShape(pathString)
+ PathParser.createPathFromPathData(pathString)
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+ fun `verify valid path circle`() {
+ ShapesProvider.iconShapes["circle"]?.apply {
+ GenericPathShape(pathString)
+ PathParser.createPathFromPathData(pathString)
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+ fun `verify valid path square`() {
+ ShapesProvider.iconShapes["square"]?.apply {
+ GenericPathShape(pathString)
+ PathParser.createPathFromPathData(pathString)
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+ fun `verify valid folder path clover`() {
+ ShapesProvider.folderShapes["clover"]?.let { pathString ->
+ GenericPathShape(pathString)
+ PathParser.createPathFromPathData(pathString)
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+ fun `verify valid folder path complexClover`() {
+ ShapesProvider.folderShapes["complexClover"]?.let { pathString ->
+ GenericPathShape(pathString)
+ PathParser.createPathFromPathData(pathString)
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+ fun `verify valid folder path arch`() {
+ ShapesProvider.folderShapes["arch"]?.let { pathString ->
+ GenericPathShape(pathString)
+ PathParser.createPathFromPathData(pathString)
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+ fun `verify valid folder path square`() {
+ ShapesProvider.folderShapes["square"]?.let { pathString ->
+ GenericPathShape(pathString)
+ PathParser.createPathFromPathData(pathString)
+ }
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java b/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java
index f51871b..5c326f9 100644
--- a/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java
@@ -28,6 +28,7 @@
import static com.android.launcher3.Flags.FLAG_USE_NEW_ICON_FOR_ARCHIVED_APPS;
import static com.android.launcher3.LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.google.common.truth.Truth.assertThat;
@@ -39,6 +40,8 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
import android.graphics.Typeface;
import android.os.Build;
import android.os.UserHandle;
@@ -57,13 +60,17 @@
import com.android.launcher3.Flags;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.Utilities;
+import com.android.launcher3.graphics.PreloadIconDrawable;
+import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.pm.PackageInstallInfo;
import com.android.launcher3.search.StringMatcherUtility;
import com.android.launcher3.util.ActivityContextWrapper;
import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext;
+import com.android.launcher3.util.TestUtil;
import com.android.launcher3.views.BaseDragLayer;
import org.junit.After;
@@ -485,4 +492,38 @@
assertThat(mBubbleTextView.getIcon().hasBadge()).isEqualTo(true);
}
+
+ @Test
+ public void applyingPendingIcon_preserves_last_icon() throws Exception {
+ mItemInfoWithIcon.bitmap =
+ BitmapInfo.fromBitmap(Bitmap.createBitmap(100, 100, Config.ARGB_8888));
+ mItemInfoWithIcon.setProgressLevel(30, PackageInstallInfo.STATUS_INSTALLING);
+
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR,
+ () -> mBubbleTextView.applyIconAndLabel(mItemInfoWithIcon));
+ assertThat(mBubbleTextView.getIcon()).isInstanceOf(PreloadIconDrawable.class);
+ assertThat(mBubbleTextView.getIcon().getLevel()).isEqualTo(30);
+ PreloadIconDrawable oldIcon = (PreloadIconDrawable) mBubbleTextView.getIcon();
+
+ // Same icon is used when progress changes
+ mItemInfoWithIcon.setProgressLevel(50, PackageInstallInfo.STATUS_INSTALLING);
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR,
+ () -> mBubbleTextView.applyIconAndLabel(mItemInfoWithIcon));
+ assertThat(mBubbleTextView.getIcon()).isSameInstanceAs(oldIcon);
+ assertThat(mBubbleTextView.getIcon().getLevel()).isEqualTo(50);
+
+ // Icon is replaced with a non pending icon when download finishes
+ mItemInfoWithIcon.setProgressLevel(100, PackageInstallInfo.STATUS_INSTALLED);
+
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR, () -> {
+ mBubbleTextView.applyIconAndLabel(mItemInfoWithIcon);
+ assertThat(mBubbleTextView.getIcon()).isSameInstanceAs(oldIcon);
+ assertThat(oldIcon.getActiveAnimation()).isNotNull();
+ oldIcon.getActiveAnimation().end();
+ });
+
+ // Assert that the icon is replaced with a non-pending icon
+ assertThat(mBubbleTextView.getIcon()).isNotInstanceOf(PreloadIconDrawable.class);
+ }
+
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/LauncherBindableItemsContainerTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/LauncherBindableItemsContainerTest.kt
new file mode 100644
index 0000000..93be5f5
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/LauncherBindableItemsContainerTest.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.content.ComponentName
+import android.content.pm.LauncherApps
+import android.graphics.Bitmap
+import android.graphics.Bitmap.Config.ARGB_8888
+import android.os.Process.myUserHandle
+import android.platform.uiautomatorhelpers.DeviceHelpers.context
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.BubbleTextView
+import com.android.launcher3.graphics.PreloadIconDrawable
+import com.android.launcher3.icons.BitmapInfo
+import com.android.launcher3.icons.FastBitmapDrawable
+import com.android.launcher3.icons.PlaceHolderIconDrawable
+import com.android.launcher3.model.data.AppInfo
+import com.android.launcher3.model.data.AppInfo.makeLaunchIntent
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.pm.PackageInstallInfo
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator
+import com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY
+import com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY2
+import com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY3
+import com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LauncherBindableItemsContainerTest {
+
+ private val icon1 by lazy { getLAI(TEST_ACTIVITY) }
+ private val icon2 by lazy { getLAI(TEST_ACTIVITY2) }
+ private val icon3 by lazy { getLAI(TEST_ACTIVITY3) }
+
+ private val container = TestContainer()
+
+ @Test
+ fun `icon bitmap is updated`() {
+ container.addIcon(icon1)
+ container.addIcon(icon2)
+ container.addIcon(icon3)
+
+ assertThat(container.getAppIcon(icon1).icon)
+ .isInstanceOf(PlaceHolderIconDrawable::class.java)
+ assertThat(container.getAppIcon(icon2).icon)
+ .isInstanceOf(PlaceHolderIconDrawable::class.java)
+ assertThat(container.getAppIcon(icon3).icon)
+ .isInstanceOf(PlaceHolderIconDrawable::class.java)
+
+ icon2.bitmap = BitmapInfo.fromBitmap(Bitmap.createBitmap(200, 200, ARGB_8888))
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {
+ container.updateContainerItems(setOf(icon2), container)
+ }
+
+ assertThat(container.getAppIcon(icon1).icon)
+ .isInstanceOf(PlaceHolderIconDrawable::class.java)
+ assertThat(container.getAppIcon(icon3).icon)
+ .isInstanceOf(PlaceHolderIconDrawable::class.java)
+ assertThat(container.getAppIcon(icon2).icon)
+ .isNotInstanceOf(PlaceHolderIconDrawable::class.java)
+ assertThat(container.getAppIcon(icon2).icon).isInstanceOf(FastBitmapDrawable::class.java)
+ }
+
+ @Test
+ fun `icon download progress updated`() {
+ container.addIcon(icon1)
+ container.addIcon(icon2)
+ assertThat(container.getAppIcon(icon1).icon)
+ .isInstanceOf(PlaceHolderIconDrawable::class.java)
+ assertThat(container.getAppIcon(icon2).icon)
+ .isInstanceOf(PlaceHolderIconDrawable::class.java)
+
+ icon1.status = WorkspaceItemInfo.FLAG_RESTORED_ICON
+ icon1.bitmap = BitmapInfo.fromBitmap(Bitmap.createBitmap(200, 200, ARGB_8888))
+ icon1.setProgressLevel(30, PackageInstallInfo.STATUS_INSTALLING)
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {
+ container.updateContainerItems(setOf(icon1), container)
+ }
+
+ assertThat(container.getAppIcon(icon2).icon)
+ .isInstanceOf(PlaceHolderIconDrawable::class.java)
+ assertThat(container.getAppIcon(icon1).icon).isInstanceOf(PreloadIconDrawable::class.java)
+ val oldIcon = container.getAppIcon(icon1).icon as PreloadIconDrawable
+ assertThat(oldIcon.level).isEqualTo(30)
+ }
+
+ private fun getLAI(className: String): WorkspaceItemInfo =
+ AppInfo(
+ context,
+ context
+ .getSystemService(LauncherApps::class.java)!!
+ .resolveActivity(
+ makeLaunchIntent(ComponentName(TEST_PACKAGE, className)),
+ myUserHandle(),
+ )!!,
+ myUserHandle(),
+ )
+ .makeWorkspaceItem(context)
+
+ class TestContainer : ActivityContextWrapper(context), LauncherBindableItemsContainer {
+
+ val items = mutableMapOf<ItemInfo, View>()
+
+ override fun mapOverItems(op: ItemOperator) {
+ items.forEach { (item, view) -> if (op.evaluate(item, view)) return@forEach }
+ }
+
+ fun addIcon(info: WorkspaceItemInfo) {
+ val btv = BubbleTextView(this)
+ btv.applyFromWorkspaceItem(info)
+ items[info] = btv
+ }
+
+ fun getAppIcon(info: WorkspaceItemInfo) = items[info] as BubbleTextView
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/TestSandboxModelContextWrapper.java b/tests/multivalentTests/src/com/android/launcher3/util/TestSandboxModelContextWrapper.java
index 71637f1..393282f 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/TestSandboxModelContextWrapper.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/TestSandboxModelContextWrapper.java
@@ -64,7 +64,7 @@
public TestSandboxModelContextWrapper(SandboxContext base) {
super(base);
- mUserCache = base.getObject(UserCache.INSTANCE);
+ mUserCache = UserCache.getInstance(base);
InstrumentationRegistry.getInstrumentation().runOnMainSync(() ->
mAppsView = new ActivityAllAppsContainerView<>(this));
mAppsList = mAppsView.getPersonalAppList();
diff --git a/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
index f6aa31a..f490bd6 100644
--- a/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
+++ b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
@@ -36,7 +36,6 @@
import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
import com.android.launcher3.util.TestUtil;
import com.android.launcher3.util.Wait;
-import com.android.launcher3.util.rule.ScreenRecordRule;
import org.junit.Test;
@@ -128,7 +127,6 @@
*/
@Test
@PlatinumTest(focusArea = "launcher")
- @ScreenRecordRule.ScreenRecord // b/319501259
public void uninstallWorkspaceIcon() throws IOException {
Point[] gridPositions = TestUtil.getCornersAndCenterPositions(mLauncher);
StringBuilder sb = new StringBuilder();
diff --git a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
index d2229c4..dba7603 100644
--- a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
+++ b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
@@ -19,20 +19,27 @@
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.LauncherPrefs.Companion.IS_FIRST_LOAD_AFTER_RESTORE
import com.android.launcher3.LauncherPrefs.Companion.RESTORE_DEVICE
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
import com.android.launcher3.icons.IconCache
import com.android.launcher3.icons.cache.CachingLogic
import com.android.launcher3.icons.cache.IconCacheUpdateHandler
import com.android.launcher3.pm.UserCache
import com.android.launcher3.provider.RestoreDbTask
import com.android.launcher3.ui.TestViewHelpers
+import com.android.launcher3.util.AllModulesForTest
import com.android.launcher3.util.Executors.MODEL_EXECUTOR
import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
import com.android.launcher3.util.LooperIdleLock
import com.android.launcher3.util.TestUtil
import com.android.launcher3.util.UserIconInfo
import com.google.common.truth.Truth
-import java.util.concurrent.CountDownLatch
-import java.util.function.Predicate
+import dagger.BindsInstance
+import dagger.Component
import junit.framework.Assert.assertEquals
import org.junit.After
import org.junit.Before
@@ -55,6 +62,8 @@
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
+import java.util.concurrent.CountDownLatch
+import java.util.function.Predicate
private const val INSERTION_STATEMENT_FILE = "databases/workspace_items.sql"
@@ -126,8 +135,9 @@
`when`(idleLock.awaitLocked(1000)).thenReturn(false)
`when`(iconCache.getUpdateHandler()).thenReturn(iconCacheUpdateHandler)
`when`(widgetsFilterDataProvider.getDefaultWidgetsFilter()).thenReturn(Predicate { true })
- context.putObject(UserCache.INSTANCE, userCache)
-
+ context.initDaggerComponent(
+ DaggerLoaderTaskTest_TestComponent.builder().bindUserCache(userCache)
+ )
TestUtil.grantWriteSecurePermission()
}
@@ -155,9 +165,24 @@
widgetsFilterDataProvider,
)
.runSyncOnBackgroundThread()
- Truth.assertThat(workspaceItems.size).isAtLeast(25)
- Truth.assertThat(appWidgets.size).isAtLeast(7)
- Truth.assertThat(collections.size()).isAtLeast(8)
+ Truth.assertThat(
+ itemsIdMap
+ .filter {
+ it.container == CONTAINER_DESKTOP || it.container == CONTAINER_HOTSEAT
+ }
+ .size
+ )
+ .isAtLeast(32)
+ Truth.assertThat(itemsIdMap.filter { ModelUtils.WIDGET_FILTER.test(it) }.size)
+ .isAtLeast(7)
+ Truth.assertThat(
+ itemsIdMap
+ .filter {
+ it.itemType == ITEM_TYPE_FOLDER || it.itemType == ITEM_TYPE_APP_PAIR
+ }
+ .size
+ )
+ .isAtLeast(8)
Truth.assertThat(itemsIdMap.size()).isAtLeast(40)
Truth.assertThat(widgetsModel.defaultWidgetsFilter).isNotNull()
}
@@ -454,6 +479,17 @@
// Then
verify(spyContext, times(0)).sendBroadcast(any())
}
+
+ @LauncherAppSingleton
+ @Component(modules = [AllModulesForTest::class])
+ interface TestComponent : LauncherAppComponent {
+ @Component.Builder
+ interface Builder : LauncherAppComponent.Builder {
+ @BindsInstance fun bindUserCache(userCache: UserCache): Builder
+
+ override fun build(): TestComponent
+ }
+ }
}
private fun LoaderTask.runSyncOnBackgroundThread() {
diff --git a/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt b/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt
index d553f47..8db049c 100644
--- a/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt
+++ b/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt
@@ -58,6 +58,7 @@
import org.mockito.Mockito.RETURNS_DEEP_STUBS
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
@@ -220,7 +221,8 @@
)
.commit()
val widgetInfoCaptor = ArgumentCaptor.forClass(LauncherAppWidgetInfo::class.java)
- verify(mockCursor).checkAndAddItem(widgetInfoCaptor.capture(), eq(mockBgDataModel))
+ verify(mockCursor)
+ .checkAndAddItem(widgetInfoCaptor.capture(), eq(mockBgDataModel), anyOrNull())
val actualWidgetInfo = widgetInfoCaptor.value
with(actualWidgetInfo) {
assertThat(providerName).isEqualTo(expectedWidgetInfo.providerName)
@@ -271,7 +273,7 @@
itemProcessorUnderTest.processItem()
// Then
- verify(mockCursor).checkAndAddItem(any(), any())
+ verify(mockCursor).checkAndAddItem(any(), any(), anyOrNull())
}
private fun createWorkspaceItemProcessorUnderTest(
diff --git a/tests/src/com/android/launcher3/popup/SystemShortcutTest.java b/tests/src/com/android/launcher3/popup/SystemShortcutTest.java
index 075f667..2531f6b 100644
--- a/tests/src/com/android/launcher3/popup/SystemShortcutTest.java
+++ b/tests/src/com/android/launcher3/popup/SystemShortcutTest.java
@@ -62,12 +62,15 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.R;
import com.android.launcher3.allapps.PrivateProfileManager;
+import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherAppSingleton;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.util.AllModulesForTest;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext;
import com.android.launcher3.util.LauncherMultivalentJUnit;
@@ -88,6 +91,9 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import dagger.BindsInstance;
+import dagger.Component;
+
@SmallTest
@RunWith(LauncherMultivalentJUnit.class)
public class SystemShortcutTest {
@@ -113,7 +119,9 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mSandboxContext.putObject(UserCache.INSTANCE, mUserCache);
+ mSandboxContext.initDaggerComponent(
+ DaggerSystemShortcutTest_TestComponent.builder().bindUserCache(mUserCache)
+ );
mTestContext = new TestSandboxModelContextWrapper(mSandboxContext) {
@Override
public StatsLogManager getStatsLogManager() {
@@ -402,4 +410,15 @@
systemShortcut.onClick(mView);
verify(mSandboxContext).startActivity(any());
}
+
+ @LauncherAppSingleton
+ @Component(modules = { AllModulesForTest.class })
+ interface TestComponent extends LauncherAppComponent {
+ @Component.Builder
+ interface Builder extends LauncherAppComponent.Builder {
+ @BindsInstance
+ SystemShortcutTest.TestComponent.Builder bindUserCache(UserCache userCache);
+ @Override LauncherAppComponent build();
+ }
+ }
}
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java
index 342eedf..1338e60 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java
@@ -21,7 +21,6 @@
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
import com.android.launcher3.Launcher;
import org.junit.Test;
@@ -31,9 +30,8 @@
@RunWith(AndroidJUnit4.class)
public class TaplTestsLauncher3Test extends AbstractLauncherUiTest<Launcher> {
- @ScreenRecord // b/322823478
@Test
- public void testDevicePressMenu() throws Exception {
+ public void testDevicePressMenu() {
mDevice.pressMenu();
mDevice.waitForIdle();
executeOnLauncher(
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index edca6dc..abf46e7 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -211,6 +211,8 @@
private TrackpadGestureType mTrackpadGestureType = TrackpadGestureType.NONE;
private int mPointerCount = 0;
+ private boolean mWaitingForMotionUpEvent;
+
private static Pattern getKeyEventPattern(String action, String keyCode) {
return Pattern.compile("Key event: KeyEvent.*action=" + action + ".*keyCode=" + keyCode);
}
@@ -752,7 +754,29 @@
}
}
+ private void cleanUpInputStream() {
+ if (!mWaitingForMotionUpEvent) {
+ return;
+ }
+ long downTime = SystemClock.uptimeMillis();
+ MotionEvent cleanUpEvent = getMotionEvent(
+ downTime,
+ downTime,
+ MotionEvent.ACTION_UP,
+ 0,
+ 0,
+ InputDevice.SOURCE_TOUCHSCREEN,
+ Configurator.getInstance().getToolType());
+ log("Test failed while a ACTION_UP event was still pending. "
+ + "Cleaning up the input stream by sending an ACTION_UP event forcefully: "
+ + "event= " + cleanUpEvent);
+
+ injectEventUnchecked(cleanUpEvent);
+ mWaitingForMotionUpEvent = false;
+ }
+
void fail(String message) {
+ cleanUpInputStream();
checkForAnomaly();
if (mOnFailure != null) mOnFailure.run();
Assert.fail(formatSystemHealthMessage(formatErrorWithEvents(
@@ -2022,8 +2046,38 @@
}
private void injectEvent(InputEvent event) {
- assertTrue("injectInputEvent failed: event=" + event,
- mInstrumentation.getUiAutomation().injectInputEvent(event, true, false));
+ if (event instanceof MotionEvent motionEvent) {
+ switch (motionEvent.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ assertTrue("Attempting to inject a second ACTION_DOWN event before a "
+ + "ACTION_UP event: " + event,
+ !mWaitingForMotionUpEvent);
+ mWaitingForMotionUpEvent = true;
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ assertTrue("Attempting to inject an unexpected ACTION_UP event: " + event,
+ mWaitingForMotionUpEvent);
+ mWaitingForMotionUpEvent = false;
+
+ break;
+ default:
+ // Nothing to do
+ break;
+ }
+ }
+ assertTrue("injectInputEvent failed: event=" + event, injectEventUnchecked(event));
+ }
+
+ private boolean injectEventUnchecked(InputEvent event) {
+ boolean result = mInstrumentation.getUiAutomation().injectInputEvent(event, true, false);
+
+ // Only MotionEvents need to be recycled.
+ if (event instanceof MotionEvent motionEvent) {
+ motionEvent.recycle();
+ }
+
+ return result;
}
public void sendPointer(long downTime, long currentTime, int action, Point point,