Merge "Remove back button and enable back gesture on SUW" 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/OWNERS b/OWNERS
index bed2acd..3f7a780 100644
--- a/OWNERS
+++ b/OWNERS
@@ -8,7 +8,6 @@
hyunyoungs@google.com
vadimt@google.com
winsonc@google.com
-awickham@google.com
agvard@google.com
# Launcher workspace eng team
@@ -21,6 +20,7 @@
pinyaoting@google.com
andonian@google.com
sihua@google.com
+abegovic@google.com
# Multitasking eng team
tracyzhou@google.com
@@ -55,11 +55,25 @@
twickham@google.com
victortulias@google.com
+## Note: some of the below overlap and also work on other integrations like Circle to Search.
+
+# All Apps / QSB team
+awickham@google.com
+brdayauon@google.com
+ganjam@google.com
+kylim@google.com
+
+# Smartspace team
+xilei@google.com
+davidct@google.com
+iamiam@google.com
+jiuyu@google.com
+
per-file FeatureFlags.java, globs = set noparent
-per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com, captaincole@google.com
+per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com, captaincole@google.com, abegovic@google.com
per-file DeviceConfigWrapper.java, globs = set noparent
-per-file DeviceConfigWrapper.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com
+per-file DeviceConfigWrapper.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com, abegovic@google.com
# Predictive Back
per-file LauncherBackAnimationController.java = shanh@google.com, gallmann@google.com
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 06809d7..9fa2f50 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."
@@ -564,3 +557,47 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "restore_archived_shortcuts"
+ namespace: "launcher"
+ description: "Makes sure pre-archived pinned shortcuts also get restored"
+ bug: "375414891"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "restore_archived_app_icons_from_db"
+ namespace: "launcher"
+ description: "Restores pre-archived icons from db when available, mimicing promise icons"
+ bug: "391913214"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "enable_mouse_interaction_changes"
+ namespace: "launcher"
+ description: "Changes mouse interaction behavior"
+ bug: "388897603"
+}
+
+flag {
+ name: "enable_alt_tab_kqs_on_connected_displays"
+ namespace: "lse_desktop_experience"
+ description: "Enable Alt + Tab KQS support on connected displays"
+ bug: "394007677"
+}
+
+flag {
+ name: "expressive_theme_in_taskbar_and_navigation"
+ namespace: "launcher"
+ description: "Enables the expressive theme and GSF font styles for Taskbar and Gesture Navigation"
+ bug: "394613212"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/aconfig/launcher_accessibility.aconfig b/aconfig/launcher_accessibility.aconfig
new file mode 100644
index 0000000..afee8fe
--- /dev/null
+++ b/aconfig/launcher_accessibility.aconfig
@@ -0,0 +1,12 @@
+package: "com.android.launcher3"
+container: "system"
+
+flag {
+ name: "remove_exclude_from_screen_magnification_flag_usage"
+ namespace: "accessibility"
+ description: "Remove the WindowManager PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION flag usage in Launcher"
+ bug: "369019568"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
\ No newline at end of file
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/dagger/com/android/launcher3/dagger/AppModule.kt b/quickstep/dagger/com/android/launcher3/dagger/AppModule.kt
new file mode 100644
index 0000000..29586c4
--- /dev/null
+++ b/quickstep/dagger/com/android/launcher3/dagger/AppModule.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.dagger
+
+import dagger.Module
+
+/**
+ * Module containing bindings for the final derivative app, an implementation of this module should
+ * be included in the final app code.
+ */
+@Module abstract class AppModule {}
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..c71308f 100644
--- a/quickstep/res/values-af/strings.xml
+++ b/quickstep/res/values-af/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Vormvry"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Rekenaar"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Skuif na eksterne skerm"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Werkskerm"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Geen onlangse items nie"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Programgebruikinstellings"</string>
@@ -46,8 +48,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 +133,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..a7b9957 100644
--- a/quickstep/res/values-am/strings.xml
+++ b/quickstep/res/values-am/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"ነፃ ቅጽ"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ዴስክቶፕ"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"ወደ ውጫዊ ማሳያ አንቀሳቅስ"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"ዴስክቶፕ"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"ምንም የቅርብ ጊዜ ንጥሎች የሉም"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"የመተግበሪያ አጠቃቀም ቅንብሮች"</string>
@@ -46,8 +48,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 +133,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..5b61fa7 100644
--- a/quickstep/res/values-ar/strings.xml
+++ b/quickstep/res/values-ar/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"شكل مجاني"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"الكمبيوتر المكتبي"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"نقل التطبيق إلى شاشة خارجية"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"إغلاق"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"كمبيوتر مكتبي"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"ما مِن عناصر تم استخدامها مؤخرًا"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"إعدادات استخدام التطبيق"</string>
@@ -46,8 +47,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 +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..d7cb836 100644
--- a/quickstep/res/values-as/strings.xml
+++ b/quickstep/res/values-as/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ডেস্কটপ"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"বাহ্যিক ডিছপ্লে’লৈ নিয়ক"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"বন্ধ কৰক"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"ডেস্কটপ"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"কোনো শেহতীয়া বস্তু নাই"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"এপে ব্যৱহাৰ কৰা ডেটাৰ ছেটিং"</string>
@@ -46,8 +47,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 +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-az/strings.xml b/quickstep/res/values-az/strings.xml
index 0e0181a..ab57177 100644
--- a/quickstep/res/values-az/strings.xml
+++ b/quickstep/res/values-az/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Sərbəst rejim"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Masaüstü"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Xarici displeyə köçürün"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Masaüstü"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Son elementlər yoxdur"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Tətbiq istifadə ayarları"</string>
@@ -46,8 +48,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 +133,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..58a9c6b 100644
--- a/quickstep/res/values-b+sr+Latn/strings.xml
+++ b/quickstep/res/values-b+sr+Latn/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Slobodni oblik"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Računar"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Premestite na spoljni ekran"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Zatvori"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Računari"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nema nedavnih stavki"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Podešavanja korišćenja aplikacije"</string>
@@ -46,8 +47,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 +132,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..08d855c 100644
--- a/quickstep/res/values-be/strings.xml
+++ b/quickstep/res/values-be/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Адвольная форма"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Працоўны стол"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Перамясціць на знешні дысплэй"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Працоўны стол"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Няма новых элементаў"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Налады выкарыстання праграмы"</string>
@@ -46,8 +48,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 +133,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..f67e8e4 100644
--- a/quickstep/res/values-bg/strings.xml
+++ b/quickstep/res/values-bg/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Свободна форма"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"За компютър"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Преместване към външния екран"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Настолен компютър"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Няма скорошни елементи"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Настройки за използването на приложенията"</string>
@@ -46,8 +48,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 +89,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 +133,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..8ff5d76 100644
--- a/quickstep/res/values-bn/strings.xml
+++ b/quickstep/res/values-bn/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"ফ্রি-ফর্ম"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ডেস্কটপ"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"এক্সটার্নাল ডিসপ্লেতে সরিয়ে নিয়ে যান"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"ডেস্কটপ"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"কোনও সাম্প্রতিক আইটেম নেই"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"অ্যাপ ব্যবহারের সেটিংস"</string>
@@ -46,8 +48,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 +133,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..9553e28 100644
--- a/quickstep/res/values-bs/strings.xml
+++ b/quickstep/res/values-bs/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Slobodan oblik"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Radna površina"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Premještanje na vanjski ekran"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Zatvori"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Radna površina"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nema nedavnih stavki"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Postavke korištenja aplikacije"</string>
@@ -46,8 +47,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 +132,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..44ef6c3 100644
--- a/quickstep/res/values-ca/strings.xml
+++ b/quickstep/res/values-ca/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Format lliure"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Escriptori"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Mou a la pantalla externa"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Escriptori"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"No hi ha cap element recent"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Configuració d\'ús d\'aplicacions"</string>
@@ -46,8 +48,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 +133,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..d809cbe 100644
--- a/quickstep/res/values-cs/strings.xml
+++ b/quickstep/res/values-cs/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Neomezený režim"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Počítač"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Přesunout na externí displej"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Zavřít"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Počítač"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Žádné položky z nedávné doby"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Nastavení využití aplikací"</string>
@@ -46,8 +47,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 +132,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..224ac11 100644
--- a/quickstep/res/values-da/strings.xml
+++ b/quickstep/res/values-da/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Frit format"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Computertilstand"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Flyt til ekstern skærm"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Computertilstand"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Ingen nye elementer"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Indstillinger for appforbrug"</string>
@@ -46,8 +48,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 +133,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..40c1cce 100644
--- a/quickstep/res/values-de/strings.xml
+++ b/quickstep/res/values-de/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform-Modus"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktopmodus"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Auf externes Display verschieben"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktopmodus"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Keine kürzlich verwendeten Elemente"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Einstellungen zur App-Nutzung"</string>
@@ -46,8 +48,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 +93,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>
@@ -107,7 +108,7 @@
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Tutorial zur Bedienung überspringen?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Du findest es später auch in der <xliff:g id="NAME">%1$s</xliff:g> App"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Abbrechen"</string>
- <string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Überspringen"</string>
+ <string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Überspringen"</string>
<string name="accessibility_rotate_button" msgid="4771825231336502943">"Bildschirm drehen"</string>
<string name="taskbar_edu_a11y_title" msgid="5417986057866415355">"Informationen zur Taskleiste"</string>
<string name="taskbar_edu_splitscreen" msgid="5605512479258053350">"App zur Seite ziehen, um zwei Apps gleichzeitig zu nutzen"</string>
@@ -132,8 +133,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..046e047 100644
--- a/quickstep/res/values-el/strings.xml
+++ b/quickstep/res/values-el/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Ελεύθερη μορφή"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Υπολογιστής"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Μετακίνηση σε εξωτερική οθόνη"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Υπολογιστής"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Δεν υπάρχουν πρόσφατα στοιχεία"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Ρυθμίσεις χρήσης εφαρμογής"</string>
@@ -46,8 +48,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 +133,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..0c8384c 100644
--- a/quickstep/res/values-en-rAU/strings.xml
+++ b/quickstep/res/values-en-rAU/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Move to external display"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"No recent items"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"App usage settings"</string>
@@ -46,8 +48,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 +133,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..4efd369 100644
--- a/quickstep/res/values-en-rCA/strings.xml
+++ b/quickstep/res/values-en-rCA/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Move to external display"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Close"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"No recent items"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"App usage settings"</string>
@@ -46,8 +47,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 +132,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..0c8384c 100644
--- a/quickstep/res/values-en-rGB/strings.xml
+++ b/quickstep/res/values-en-rGB/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Move to external display"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"No recent items"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"App usage settings"</string>
@@ -46,8 +48,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 +133,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..0c8384c 100644
--- a/quickstep/res/values-en-rIN/strings.xml
+++ b/quickstep/res/values-en-rIN/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Move to external display"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"No recent items"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"App usage settings"</string>
@@ -46,8 +48,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 +133,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..5ba3212 100644
--- a/quickstep/res/values-es-rUS/strings.xml
+++ b/quickstep/res/values-es-rUS/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Formato libre"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Escritorio"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Mover a pantalla externa"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Cerrar"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Computadoras"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"No hay elementos recientes"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Configuración de uso de la app"</string>
@@ -46,8 +47,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>
@@ -90,7 +90,7 @@
<string name="allset_title" msgid="5021126669778966707">"Todo listo"</string>
<string name="allset_hint" msgid="459504134589971527">"Desliza el dedo hacia arriba para ir a la página principal"</string>
<string name="allset_button_hint" msgid="2395219947744706291">"Presiona el botón de inicio para ir a la pantalla principal"</string>
- <string name="allset_description_generic" msgid="5385500062202019855">"Ya puedes comenzar a usar tu <xliff:g id="DEVICE">%1$s</xliff:g>."</string>
+ <string name="allset_description_generic" msgid="5385500062202019855">"Ya puedes comenzar a usar tu <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">"Configuración de navegación del sistema"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Compartir"</string>
@@ -132,8 +132,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..6c412f7 100644
--- a/quickstep/res/values-es/strings.xml
+++ b/quickstep/res/values-es/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Formato libre"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Escritorio"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Mover a pantalla externa"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Ordenador"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"No hay nada reciente"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Ajustes de uso de la aplicación"</string>
@@ -46,8 +48,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 +133,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..d4e041a 100644
--- a/quickstep/res/values-et/strings.xml
+++ b/quickstep/res/values-et/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Vabavorm"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Lauaarvuti režiim"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Liikuge välisele ekraanile"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Sule"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Töölaud"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Hiljutisi üksusi pole"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Rakenduse kasutuse seaded"</string>
@@ -46,8 +47,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>
@@ -90,7 +90,7 @@
<string name="allset_title" msgid="5021126669778966707">"Valmis!"</string>
<string name="allset_hint" msgid="459504134589971527">"Avalehele liikumiseks pühkige üles"</string>
<string name="allset_button_hint" msgid="2395219947744706291">"Avakuvale liikumiseks puudutage avakuva nuppu"</string>
- <string name="allset_description_generic" msgid="5385500062202019855">"<xliff:g id="DEVICE">%1$s</xliff:g> on nüüd kasutamiseks valmis"</string>
+ <string name="allset_description_generic" msgid="5385500062202019855">"Teie <xliff:g id="DEVICE">%1$s</xliff:g> on nüüd kasutamiseks valmis."</string>
<string name="default_device_name" msgid="6660656727127422487">"seade"</string>
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Süsteemi navigeerimisseaded"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Jaga"</string>
@@ -132,8 +132,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..892089d 100644
--- a/quickstep/res/values-eu/strings.xml
+++ b/quickstep/res/values-eu/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Modu librea"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Ordenagailua"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Eraman kanpoko pantailara"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Mahaigaina"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Ez dago azkenaldi honetako ezer"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Aplikazioen erabileraren ezarpenak"</string>
@@ -46,8 +48,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 +133,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..7dcdb7f 100644
--- a/quickstep/res/values-fa/strings.xml
+++ b/quickstep/res/values-fa/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"حالت رایانه"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"انتقال به نمایشگر خارجی"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"بستن"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"رایانه"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"چیز جدیدی اینجا نیست"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"تنظیمات استفاده از برنامه"</string>
@@ -46,8 +47,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 +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-fi/strings.xml b/quickstep/res/values-fi/strings.xml
index 6907e49..0e360d0 100644
--- a/quickstep/res/values-fi/strings.xml
+++ b/quickstep/res/values-fi/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Vapaamuotoinen"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Tietokone"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Siirrä ulkoiselle näytölle"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Tietokone"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Ei viimeaikaisia kohteita"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Sovelluksen käyttöasetukset"</string>
@@ -46,8 +48,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 +133,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..e7619c2 100644
--- a/quickstep/res/values-fr-rCA/strings.xml
+++ b/quickstep/res/values-fr-rCA/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Forme libre"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Bureau"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Passer à un écran externe"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Fermer"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Bureau"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Aucun élément récent"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Paramètres d\'utilisation de l\'appli"</string>
@@ -46,8 +47,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 +132,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..9982b25 100644
--- a/quickstep/res/values-fr/strings.xml
+++ b/quickstep/res/values-fr/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Format libre"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Mode ordinateur"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Déplacer vers l\'écran externe"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Fermer"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Mode ordinateur"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Aucun élément récent"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Paramètres de consommation de l\'application"</string>
@@ -46,8 +47,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 +132,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..f4260f3 100644
--- a/quickstep/res/values-gl/strings.xml
+++ b/quickstep/res/values-gl/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Forma libre"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Escritorio"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Mover á pantalla externa"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Ordenador"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Non hai elementos recentes"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Configuración do uso de aplicacións"</string>
@@ -46,8 +48,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 +133,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..2797f82 100644
--- a/quickstep/res/values-gu/strings.xml
+++ b/quickstep/res/values-gu/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"ફ્રિફોર્મ"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ડેસ્કટૉપ"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"બાહ્ય ડિસ્પ્લે પર ખસેડો"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"ડેસ્કટૉપ"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"તાજેતરની કોઈ આઇટમ નથી"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ઍપ વપરાશનું સેટિંગ"</string>
@@ -46,8 +48,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 +133,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..5ed6ef5 100644
--- a/quickstep/res/values-hi/strings.xml
+++ b/quickstep/res/values-hi/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"फ़्रीफ़ॉर्म"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"डेस्कटॉप"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"बाहरी डिसप्ले पर जाएं"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"डेस्कटॉप"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"हाल ही का कोई आइटम नहीं है"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ऐप्लिकेशन इस्तेमाल की सेटिंग"</string>
@@ -46,8 +48,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 +133,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..67fe240 100644
--- a/quickstep/res/values-hr/strings.xml
+++ b/quickstep/res/values-hr/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Slobodni oblik"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Računalo"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Premještanje na vanjski zaslon"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Zatvori"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Radna površina"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nema nedavnih stavki"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Postavke upotrebe aplikacija"</string>
@@ -46,8 +47,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 +132,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..4f15c7d 100644
--- a/quickstep/res/values-hu/strings.xml
+++ b/quickstep/res/values-hu/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Szabad forma"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Asztali"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Áthelyezés külső kijelzőre"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Asztali"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nincsenek mostanában használt elemek"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Alkalmazáshasználati beállítások"</string>
@@ -46,8 +48,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 +133,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..edaa5bd 100644
--- a/quickstep/res/values-hy/strings.xml
+++ b/quickstep/res/values-hy/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Կամայական ձև"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Համակարգիչ"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Տեղափոխել արտաքին էկրան"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Համակարգիչ"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Այստեղ դեռ ոչինչ չկա"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Հավելվածի օգտագործման կարգավորումներ"</string>
@@ -46,8 +48,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 +133,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..4b2ffd7 100644
--- a/quickstep/res/values-in/strings.xml
+++ b/quickstep/res/values-in/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Format bebas"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Pindahkan ke layar eksternal"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Tidak ada item yang baru dibuka"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Setelan penggunaan aplikasi"</string>
@@ -46,8 +48,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 +133,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..3619a2d 100644
--- a/quickstep/res/values-is/strings.xml
+++ b/quickstep/res/values-is/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Frjálst snið"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Tölva"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Færa í annað tæki"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Tölva"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Engin nýleg atriði"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Notkunarstillingar forrits"</string>
@@ -46,8 +48,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 +133,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..17aefa4 100644
--- a/quickstep/res/values-it/strings.xml
+++ b/quickstep/res/values-it/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Forma libera"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Sposta sul display esterno"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nessun elemento recente"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Impostazioni di utilizzo delle app"</string>
@@ -46,8 +48,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 +93,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 +133,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..1cba30f 100644
--- a/quickstep/res/values-iw/strings.xml
+++ b/quickstep/res/values-iw/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"מצב חופשי"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"במחשב"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"העברה למסך חיצוני"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"מחשב"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"אין פריטים אחרונים"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"הגדרות שימוש באפליקציה"</string>
@@ -46,8 +48,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 +133,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..ded498a 100644
--- a/quickstep/res/values-ja/strings.xml
+++ b/quickstep/res/values-ja/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"フリーフォーム"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"デスクトップ"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"外部ディスプレイに移動する"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"閉じる"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"デスクトップ"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"最近のアイテムはありません"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"アプリの使用状況の設定"</string>
@@ -46,8 +47,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 +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-ka/strings.xml b/quickstep/res/values-ka/strings.xml
index 01becd7..1604495 100644
--- a/quickstep/res/values-ka/strings.xml
+++ b/quickstep/res/values-ka/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"თავისუფალი ფორმა"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"დესკტოპი"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"გარე ეკრანზე გადასვლა"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"დახურვა"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"დესკტოპი"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"ბოლოს გამოყენებული ერთეულები არ არის"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"აპების გამოყენების პარამეტრები"</string>
@@ -46,8 +47,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 +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-kk/strings.xml b/quickstep/res/values-kk/strings.xml
index 3280908..b95e708 100644
--- a/quickstep/res/values-kk/strings.xml
+++ b/quickstep/res/values-kk/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Еркін форма"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Компьютер"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Сыртқы дисплейге ауыстыру"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Жұмыс үстелі"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Соңғы элементтер жоқ"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Қолданбаны пайдалану параметрлері"</string>
@@ -46,8 +48,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 +133,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..1fdcde9 100644
--- a/quickstep/res/values-km/strings.xml
+++ b/quickstep/res/values-km/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"មុខងារទម្រង់សេរី"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ដែសថប"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"ផ្លាស់ទីទៅផ្ទាំងអេក្រង់ខាងក្រៅ"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"បិទ"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"អេក្រង់ដើម"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"មិនមានធាតុថ្មីៗទេ"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ការកំណត់ការប្រើប្រាស់កម្មវិធី"</string>
@@ -46,8 +47,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 +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-kn/strings.xml b/quickstep/res/values-kn/strings.xml
index 3240b2a..19d3ec9 100644
--- a/quickstep/res/values-kn/strings.xml
+++ b/quickstep/res/values-kn/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"ಮುಕ್ತಸ್ವರೂಪ"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ಡೆಸ್ಕ್ಟಾಪ್"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"ಬಾಹ್ಯ ಡಿಸ್ಪ್ಲೇಗೆ ಸರಿಸಿ"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"ಡೆಸ್ಕ್ಟಾಪ್"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"ಯಾವುದೇ ಇತ್ತೀಚಿನ ಐಟಂಗಳಿಲ್ಲ"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ಆ್ಯಪ್ ಬಳಕೆಯ ಸೆಟ್ಟಿಂಗ್ಗಳು"</string>
@@ -46,8 +48,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 +133,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..2e89174 100644
--- a/quickstep/res/values-ko/strings.xml
+++ b/quickstep/res/values-ko/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"자유 형식"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"데스크톱"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"외부 디스플레이로 이동"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"데스크톱"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"최근 항목이 없습니다."</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"앱 사용 설정"</string>
@@ -46,8 +48,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 +133,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..ac2b06d 100644
--- a/quickstep/res/values-ky/strings.xml
+++ b/quickstep/res/values-ky/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Эркин форма режими"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Компьютер"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Тышкы экранга жылдыруу"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Компьютер"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Акыркы колдонмолор жок"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Колдонмону пайдалануу параметрлери"</string>
@@ -46,8 +48,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 +133,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..2ebaa7a 100644
--- a/quickstep/res/values-lo/strings.xml
+++ b/quickstep/res/values-lo/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"ຮູບແບບອິດສະຫລະ"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ເດັສທັອບ"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"ຍ້າຍໄປຫາຈໍສະແດງຜົນພາຍນອກ"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"ປິດ"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"ເດັສທັອບ"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"ບໍ່ມີລາຍການຫຼ້າສຸດ"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ການຕັ້ງຄ່າການນຳໃຊ້ແອັບ"</string>
@@ -46,8 +47,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 +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-lt/strings.xml b/quickstep/res/values-lt/strings.xml
index 7334b0a..b57fee4 100644
--- a/quickstep/res/values-lt/strings.xml
+++ b/quickstep/res/values-lt/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Laisva forma"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Stalinis kompiuteris"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Perkelkite į išorinį ekraną"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Stalinis kompiuteris"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nėra jokių naujausių elementų"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Programos naudojimo nustatymai"</string>
@@ -46,8 +48,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 +91,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 +133,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..07e9e1a 100644
--- a/quickstep/res/values-lv/strings.xml
+++ b/quickstep/res/values-lv/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Brīva forma"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Dators"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Pārvietošana uz ārējo displeju"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Darbvirsma"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nav nesenu vienumu."</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Lietotņu izmantošanas iestatījumi"</string>
@@ -46,8 +48,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>
@@ -90,7 +91,7 @@
<string name="allset_title" msgid="5021126669778966707">"Gatavs!"</string>
<string name="allset_hint" msgid="459504134589971527">"Velciet augšup, lai pārietu uz sākuma ekrānu"</string>
<string name="allset_button_hint" msgid="2395219947744706291">"Pieskarieties pogai Sākums, lai dotos uz sākuma ekrānu"</string>
- <string name="allset_description_generic" msgid="5385500062202019855">"Varat sākt izmantot savu ierīci (<xliff:g id="DEVICE">%1$s</xliff:g>)"</string>
+ <string name="allset_description_generic" msgid="5385500062202019855">"Jūsu <xliff:g id="DEVICE">%1$s</xliff:g> ir gatava lietošanai"</string>
<string name="default_device_name" msgid="6660656727127422487">"ierīce"</string>
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Sistēmas navigācijas iestatījumi"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Kopīgot"</string>
@@ -132,8 +133,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..0a133a3 100644
--- a/quickstep/res/values-mk/strings.xml
+++ b/quickstep/res/values-mk/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Работна површина"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Префрлете се на надворешниот екран"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"За компјутер"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Нема неодамнешни ставки"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Поставки за користење на апликациите"</string>
@@ -46,8 +48,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 +133,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..849315b 100644
--- a/quickstep/res/values-ml/strings.xml
+++ b/quickstep/res/values-ml/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"ഫ്രീഫോം"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ഡെസ്ക്ടോപ്പ്"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"ബാഹ്യ ഡിസ്പ്ലേയിലേക്ക് നീക്കുക"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"അടയ്ക്കുക"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"ഡെസ്ക്ടോപ്പ്"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"സമീപകാല ഇനങ്ങൾ ഒന്നുമില്ല"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ആപ്പ് ഉപയോഗ ക്രമീകരണം"</string>
@@ -46,8 +47,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 +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-mn/strings.xml b/quickstep/res/values-mn/strings.xml
index 2b335a9..c50e87a 100644
--- a/quickstep/res/values-mn/strings.xml
+++ b/quickstep/res/values-mn/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Чөлөөтэй хувьсах"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Компьютер"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Гадаад дэлгэц рүү зөөх"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Дэлгэц"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Сүүлийн үеийн зүйл алга"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Апп ашиглалтын тохиргоо"</string>
@@ -46,8 +48,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 +133,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..63f4317 100644
--- a/quickstep/res/values-mr/strings.xml
+++ b/quickstep/res/values-mr/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"फ्रीफॉर्म"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"डेस्कटॉप"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"बाह्य डिस्प्लेवर हलवा"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"डेस्कटॉप"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"कोणतेही अलीकडील आयटम नाहीत"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"अॅप वापर सेटिंग्ज"</string>
@@ -46,8 +48,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 +133,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..b039666 100644
--- a/quickstep/res/values-ms/strings.xml
+++ b/quickstep/res/values-ms/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Bentuk bebas"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Alihkan kepada paparan luaran"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Tutup"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Tiada item terbaharu"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Tetapan penggunaan apl"</string>
@@ -46,8 +47,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 +132,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..9df3d66 100644
--- a/quickstep/res/values-my/strings.xml
+++ b/quickstep/res/values-my/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"အလွတ်ပုံစံ"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ဒက်စ်တော့"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"ပြင်ပဖန်သားပြင်သို့ ရွှေ့ရန်"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"ဒက်စ်တော့"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"မကြာမီကဖွင့်ထားသည်များ မရှိပါ"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"အက်ပ်အသုံးပြုမှု ဆက်တင်များ"</string>
@@ -46,8 +48,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>
@@ -90,7 +91,7 @@
<string name="allset_title" msgid="5021126669778966707">"အားလုံး အဆင်သင့်ပါ။"</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="allset_description_generic" msgid="5385500062202019855">"<xliff:g id="DEVICE">%1$s</xliff:g> စသုံးရန် အသင့်ဖြစ်ပါပြီ"</string>
<string name="default_device_name" msgid="6660656727127422487">"စက်"</string>
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"စနစ် လမ်းညွှန် ဆက်တင်များ"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"မျှဝေရန်"</string>
@@ -132,8 +133,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..d05884f 100644
--- a/quickstep/res/values-nb/strings.xml
+++ b/quickstep/res/values-nb/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Fritt format"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Skrivebord"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Flytt til ekstern skjerm"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Skrivebord"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Ingen nylige elementer"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Innstillinger for appbruk"</string>
@@ -46,8 +48,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 +133,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..6efdca9 100644
--- a/quickstep/res/values-ne/strings.xml
+++ b/quickstep/res/values-ne/strings.xml
@@ -23,8 +23,10 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"फ्रिफर्म"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"डेस्कटप"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"सारेर बाह्य डिस्प्लेमा लैजानुहोस्"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"डेस्कटप"</string>
- <string name="recents_empty_message" msgid="7040467240571714191">"हालसालैको कुनै पनि वस्तु छैन"</string>
+ <string name="recents_empty_message" msgid="7040467240571714191">"हालसालैको कुनै पनि सामग्री छैन"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"एपको उपयोगका सेटिङहरू"</string>
<string name="recents_clear_all" msgid="5328176793634888831">"सबै मेटाउनुहोस्"</string>
<string name="accessibility_recent_apps" msgid="4058661986695117371">"हालसालैका एपहरू"</string>
@@ -46,8 +48,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>
@@ -126,14 +127,12 @@
<string name="taskbar_button_back" msgid="8558862226461164514">"पछाडि जानुहोस्"</string>
<string name="taskbar_button_ime_switcher" msgid="1730244360907588541">"IME स्विचर"</string>
<string name="taskbar_button_recents" msgid="7273376136216613134">"हालसालैका बटनहरू"</string>
- <string name="taskbar_button_notifications" msgid="7471740351507357318">"सूचनाहरू"</string>
+ <string name="taskbar_button_notifications" msgid="7471740351507357318">"नोटिफिकेसनहरू"</string>
<string name="taskbar_button_quick_settings" msgid="227662894293189391">"द्रुत सेटिङ"</string>
<string name="taskbar_a11y_title" msgid="6432169809852243110">"टास्कबार"</string>
<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..6cf9683 100644
--- a/quickstep/res/values-nl/strings.xml
+++ b/quickstep/res/values-nl/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Vrije vorm"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Verplaatsen naar extern scherm"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Sluiten"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Geen recente items"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Instellingen voor app-gebruik"</string>
@@ -46,8 +47,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 +132,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..66cbcdd 100644
--- a/quickstep/res/values-or/strings.xml
+++ b/quickstep/res/values-or/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"ଫ୍ରିଫର୍ମ"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ଡେସ୍କଟପ"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"ଏକ୍ସଟର୍ନଲ ଡିସପ୍ଲେକୁ ମୁଭ କରନ୍ତୁ"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"ଡେସ୍କଟପ"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"ବର୍ତ୍ତମାନର କୌଣସି ଆଇଟମ ନାହିଁ"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ଆପ ବ୍ୟବହାର ସେଟିଂସ"</string>
@@ -46,8 +48,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 +133,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..8489fd1 100644
--- a/quickstep/res/values-pa/strings.xml
+++ b/quickstep/res/values-pa/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"ਫ੍ਰੀਫਾਰਮ"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ਡੈਸਕਟਾਪ"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"ਬਾਹਰੀ ਡਿਸਪਲੇ \'ਤੇ ਜਾਓ"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"ਬੰਦ ਕਰੋ"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"ਡੈਸਕਟਾਪ"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"ਕੋਈ ਹਾਲੀਆ ਆਈਟਮ ਨਹੀਂ"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ਐਪ ਵਰਤੋਂ ਦੀਆਂ ਸੈਟਿੰਗਾਂ"</string>
@@ -46,8 +47,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>
@@ -90,7 +90,7 @@
<string name="allset_title" msgid="5021126669778966707">"ਪੂਰੀ ਤਰ੍ਹਾਂ ਤਿਆਰ!"</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="allset_description_generic" msgid="5385500062202019855">"ਹੁਣ ਤੁਹਾਡਾ <xliff:g id="DEVICE">%1$s</xliff:g> ਵਰਤੋਂ ਲਈ ਤਿਆਰ ਹੈ"</string>
<string name="default_device_name" msgid="6660656727127422487">"ਡੀਵਾਈਸ"</string>
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"ਸਿਸਟਮ ਨੈਵੀਗੇਸ਼ਨ ਸੈਟਿੰਗਾਂ"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"ਸਾਂਝਾ ਕਰੋ"</string>
@@ -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-pl/strings.xml b/quickstep/res/values-pl/strings.xml
index 74aec66..b571ba2 100644
--- a/quickstep/res/values-pl/strings.xml
+++ b/quickstep/res/values-pl/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Tryb dowolny"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Pulpit"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Przenieś na wyświetlacz zewnętrzny"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Pulpit"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Brak ostatnich elementów"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Ustawienia użycia aplikacji"</string>
@@ -46,8 +48,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 +133,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..84a4f58 100644
--- a/quickstep/res/values-pt-rPT/strings.xml
+++ b/quickstep/res/values-pt-rPT/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Forma livre"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Computador"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Mover para o ecrã externo"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Fechar"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Computador"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nenhum item recente"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Definições de utilização de aplicações"</string>
@@ -46,8 +47,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 +132,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..bb7d97d 100644
--- a/quickstep/res/values-pt/strings.xml
+++ b/quickstep/res/values-pt/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Forma livre"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Modo área de trabalho"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Mover para a tela externa"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Computador"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nenhum item recente"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Configurações de uso do app"</string>
@@ -46,8 +48,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 +133,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..856bef7 100644
--- a/quickstep/res/values-ro/strings.xml
+++ b/quickstep/res/values-ro/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Formă liberă"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Mută pe ecranul extern"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Computer"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Niciun element recent"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Setări de utilizare a aplicației"</string>
@@ -46,8 +48,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 +133,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..9ebbc02 100644
--- a/quickstep/res/values-ru/strings.xml
+++ b/quickstep/res/values-ru/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Произвольная форма"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Мультиоконный режим"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Перенести на внешний дисплей"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Закрыть"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Мультиоконный режим"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Здесь пока ничего нет."</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Настройки использования приложения"</string>
@@ -46,8 +47,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 +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-si/strings.xml b/quickstep/res/values-si/strings.xml
index 2b1f147..3b1cada 100644
--- a/quickstep/res/values-si/strings.xml
+++ b/quickstep/res/values-si/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ඩෙස්ක්ටොපය"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"බාහිර සංදර්ශකය වෙත ගෙන යන්න"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"ඩෙස්ක්ටොපය"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"මෑත අයිතම නැත"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"යෙදුම් භාවිත සැකසීම්"</string>
@@ -46,8 +48,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 +133,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..bf9adff 100644
--- a/quickstep/res/values-sk/strings.xml
+++ b/quickstep/res/values-sk/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Voľný režim"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Počítač"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Presunúť na externú obrazovku"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Zavrieť"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Počítač"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Žiadne nedávne položky"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Nastavenia využívania aplikácie"</string>
@@ -46,8 +47,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 +132,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..440c0f1 100644
--- a/quickstep/res/values-sl/strings.xml
+++ b/quickstep/res/values-sl/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Prosta oblika"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Namizni računalnik"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Premik v zunanji zaslon"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Zapri"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Namizni način"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Ni nedavnih elementov"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Nastavitve uporabe aplikacij"</string>
@@ -46,8 +47,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 +132,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..8f39f65 100644
--- a/quickstep/res/values-sq/strings.xml
+++ b/quickstep/res/values-sq/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Formë e lirë"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktopi"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Zhvendose tek ekrani i jashtëm"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktopi"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nuk ka asnjë artikull të fundit"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Cilësimet e përdorimit të aplikacionit"</string>
@@ -46,8 +48,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 +133,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..e810d5a 100644
--- a/quickstep/res/values-sr/strings.xml
+++ b/quickstep/res/values-sr/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Слободни облик"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Рачунар"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Преместите на спољни екран"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Затвори"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Рачунари"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Нема недавних ставки"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Подешавања коришћења апликације"</string>
@@ -46,8 +47,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 +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-sv/strings.xml b/quickstep/res/values-sv/strings.xml
index 7c826cd..c33de8a 100644
--- a/quickstep/res/values-sv/strings.xml
+++ b/quickstep/res/values-sv/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Fritt format"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Skrivbordsläge"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Flytta till extern skärm"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Skrivbordsläge"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Listan är tom"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Inställningar för appanvändning"</string>
@@ -46,8 +48,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 +133,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..543663f 100644
--- a/quickstep/res/values-sw/strings.xml
+++ b/quickstep/res/values-sw/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Muundo huru"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Kompyuta ya mezani"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Hamishia programu kwenye skrini ya nje"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Kompyuta ya Mezani"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Hakuna vipengee vya hivi karibuni"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Mipangilio ya matumizi ya programu"</string>
@@ -46,10 +48,9 @@
<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 Miguso"</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="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Tafadhali zungusha kifaa chako ili ukamilishe mafunzo ya usogezaji kwa kutumia miguso"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Hakikisha unatelezesha kidole kutoka ukingo wa kulia au kushoto kabisa"</string>
<string name="back_gesture_feedback_cancelled" msgid="762621530959111290">"Hakikisha unatelezesha kidole kutoka ukingo wa kulia au kushoto hadi katikati ya skrini na uachilie"</string>
<string name="back_gesture_feedback_complete_with_overview_follow_up" msgid="9176400654037014471">"Umejifunza jinsi ya kutelezesha kidole kuanzia kulia ili kurudi nyuma. Sasa jifunze jinsi ya kubadilisha programu."</string>
@@ -132,8 +133,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..1e6cb9b 100644
--- a/quickstep/res/values-ta/strings.xml
+++ b/quickstep/res/values-ta/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"குறிப்பிட்ட வடிவமில்லாத பயன்முறை"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"டெஸ்க்டாப்"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"வெளிப்புற டிஸ்ப்ளேவிற்கு நகர்த்துதல்"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"டெஸ்க்டாப்"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"சமீபத்தியவை எதுவுமில்லை"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ஆப்ஸ் உபயோக அமைப்புகள்"</string>
@@ -46,8 +48,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 +133,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..74e59ba 100644
--- a/quickstep/res/values-te/strings.xml
+++ b/quickstep/res/values-te/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"సంప్రదాయేతర"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"డెస్క్టాప్"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"ఎక్స్టర్నల్ డిస్ప్లేకు తరలించండి"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"డెస్క్టాప్"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"ఇటీవలి ఐటెమ్లు ఏవీ లేవు"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"యాప్ వినియోగ సెట్టింగ్లు"</string>
@@ -46,8 +48,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 +133,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..03852ff 100644
--- a/quickstep/res/values-th/strings.xml
+++ b/quickstep/res/values-th/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"รูปแบบอิสระ"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"เดสก์ท็อป"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"ย้ายไปยังจอแสดงผลภายนอก"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"ปิด"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"เดสก์ท็อป"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"ไม่มีรายการล่าสุด"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"การตั้งค่าการใช้แอป"</string>
@@ -46,8 +47,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 +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-tl/strings.xml b/quickstep/res/values-tl/strings.xml
index 1bc4a3d..1b3696e 100644
--- a/quickstep/res/values-tl/strings.xml
+++ b/quickstep/res/values-tl/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Ilipat sa external na display"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Isara"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Walang kamakailang item"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Mga setting ng paggamit ng app"</string>
@@ -46,8 +47,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 +132,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..ac06369 100644
--- a/quickstep/res/values-tr/strings.xml
+++ b/quickstep/res/values-tr/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Serbest çalışma"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Masaüstü"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Harici ekrana taşı"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Masaüstü"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Yeni öğe yok"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Uygulama kullanım ayarları"</string>
@@ -46,8 +48,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 +133,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..2d27bb9 100644
--- a/quickstep/res/values-uk/strings.xml
+++ b/quickstep/res/values-uk/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Довільна форма"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Робочий стіл"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Перемістити на зовнішній екран"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Комп’ютер"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Немає нещодавніх додатків"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Налаштування використання додатка"</string>
@@ -46,8 +48,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 +133,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..0a3e122 100644
--- a/quickstep/res/values-ur/strings.xml
+++ b/quickstep/res/values-ur/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"فری فارم"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"ڈیسک ٹاپ"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"بیرونی ڈسپلے پر متقل کریں"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"بند کریں"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"ڈیسک ٹاپ"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"کوئی حالیہ آئٹم نہیں"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ایپ کے استعمال کی ترتیبات"</string>
@@ -46,8 +47,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 +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-uz/strings.xml b/quickstep/res/values-uz/strings.xml
index 7de3648..1cd2e87 100644
--- a/quickstep/res/values-uz/strings.xml
+++ b/quickstep/res/values-uz/strings.xml
@@ -23,6 +23,7 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Erkin shakl"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Tashqi displeyga olish"</string>
+ <string name="recent_task_option_close" msgid="942942499021777264">"Yopish"</string>
<string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Yaqinda ishlatilgan ilovalar yo‘q"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Ilovadan foydalanish sozlamalari"</string>
@@ -46,8 +47,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 +132,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..e49f7e3 100644
--- a/quickstep/res/values-vi/strings.xml
+++ b/quickstep/res/values-vi/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Dạng tự do"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Máy tính"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Chuyển sang màn hình ngoài"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Máy tính"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Không có mục gần đây nào"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Cài đặt mức sử dụng ứng dụng"</string>
@@ -46,8 +48,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 +133,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..c1cf2fe 100644
--- a/quickstep/res/values-zh-rCN/strings.xml
+++ b/quickstep/res/values-zh-rCN/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"自由窗口"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"桌面"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"移至外接显示屏"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"桌面设备"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"近期没有任何内容"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"应用使用设置"</string>
@@ -46,8 +48,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 +133,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..393565a 100644
--- a/quickstep/res/values-zh-rHK/strings.xml
+++ b/quickstep/res/values-zh-rHK/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"自由形式"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"桌面"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"移至外部顯示屏"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"桌面"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"最近沒有任何項目"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"應用程式使用情況設定"</string>
@@ -46,8 +48,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 +133,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..fbd465d 100644
--- a/quickstep/res/values-zh-rTW/strings.xml
+++ b/quickstep/res/values-zh-rTW/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"自由形式"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"電腦模式"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"移至外接螢幕"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"電腦模式"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"最近沒有任何項目"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"應用程式使用情況設定"</string>
@@ -46,8 +48,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 +133,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..39b4873 100644
--- a/quickstep/res/values-zu/strings.xml
+++ b/quickstep/res/values-zu/strings.xml
@@ -23,6 +23,8 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"I-Freeform"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Ideskithophu"</string>
<string name="recent_task_option_external_display" msgid="4533840664313389484">"Hambisa esibonisini sangaphandle"</string>
+ <!-- no translation found for recent_task_option_close (942942499021777264) -->
+ <skip />
<string name="recent_task_desktop" msgid="8081113562549637334">"Ideskithophu"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Azikho izinto zakamuva"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Izilungiselelo zokusetshenziswa kohlelo lokusebenza"</string>
@@ -46,8 +48,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 +133,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/config.xml b/quickstep/res/values/config.xml
index 1f33e08..a530325 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -34,7 +34,6 @@
<string name="launcher_restore_event_logger_class" translatable="false">com.android.quickstep.LauncherRestoreEventLoggerImpl</string>
<string name="taskbar_edu_tooltip_controller_class" translatable="false">com.android.launcher3.taskbar.TaskbarEduTooltipController</string>
<string name="nav_handle_long_press_handler_class" translatable="false"></string>
- <string name="contextual_search_state_manager_class" translatable="false"></string>
<!-- The number of thumbnails and icons to keep in the cache. The thumbnail cache size also
determines how many thumbnails will be fetched in the background. -->
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/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
index dc0f899..1cf7dda 100644
--- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
+++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
@@ -23,6 +23,8 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static java.lang.Math.max;
+import static java.lang.Math.min;
import static java.util.Collections.emptyList;
import android.appwidget.AppWidgetManager;
@@ -53,6 +55,7 @@
import com.android.launcher3.widget.WidgetCell;
import com.android.launcher3.widget.model.WidgetsListBaseEntriesBuilder;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+import com.android.launcher3.widget.picker.WidgetCategoryFilter;
import com.android.launcher3.widget.picker.WidgetsFullSheet;
import com.android.launcher3.widget.picker.model.WidgetPickerDataProvider;
import com.android.systemui.animation.back.FlingOnBackAnimationCallback;
@@ -81,6 +84,10 @@
// the intent, then widgets will not be filtered for size.
private static final String EXTRA_DESIRED_WIDGET_WIDTH = "desired_widget_width";
private static final String EXTRA_DESIRED_WIDGET_HEIGHT = "desired_widget_height";
+ // Unlike the AppWidgetManager.EXTRA_CATEGORY_FILTER, this filter removes certain categories.
+ // Filter is ignore if it is not a negative value.
+ // Example usage: WIDGET_CATEGORY_HOME_SCREEN.inv() and WIDGET_CATEGORY_NOT_KEYGUARD.inv()
+ private static final String EXTRA_CATEGORY_EXCLUSION_FILTER = "category_exclusion_filter";
/**
* Widgets currently added by the user in the UI surface.
* <p>This allows widget picker to exclude existing widgets from suggestions.</p>
@@ -120,7 +127,8 @@
private int mDesiredWidgetWidth;
private int mDesiredWidgetHeight;
- private int mWidgetCategoryFilter;
+ private WidgetCategoryFilter mWidgetCategoryInclusionFilter;
+ private WidgetCategoryFilter mWidgetCategoryExclusionFilter;
@Nullable
private String mUiSurface;
// Widgets existing on the host surface.
@@ -194,8 +202,19 @@
getIntent().getIntExtra(EXTRA_DESIRED_WIDGET_HEIGHT, 0);
// Defaults to '0' to indicate that there isn't a category filter.
- mWidgetCategoryFilter =
- getIntent().getIntExtra(AppWidgetManager.EXTRA_CATEGORY_FILTER, 0);
+ // Negative value indicates it's an exclusion filter (e.g. NOT_KEYGUARD_CATEGORY.inv())
+ // Positive value indicates it's inclusion filter (e.g. HOME_SCREEN or KEYGUARD)
+ // Note: A filter can either be inclusion or exclusion filter; not both.
+ int inclusionFilter = getIntent().getIntExtra(AppWidgetManager.EXTRA_CATEGORY_FILTER, 0);
+ if (inclusionFilter < 0) {
+ Log.w(TAG, "Invalid EXTRA_CATEGORY_FILTER: " + inclusionFilter);
+ }
+ mWidgetCategoryInclusionFilter = new WidgetCategoryFilter(max(0, inclusionFilter));
+ int exclusionFilter = getIntent().getIntExtra(EXTRA_CATEGORY_EXCLUSION_FILTER, 0);
+ if (exclusionFilter > 0) {
+ Log.w(TAG, "Invalid EXTRA_CATEGORY_EXCLUSION_FILTER: " + exclusionFilter);
+ }
+ mWidgetCategoryExclusionFilter = new WidgetCategoryFilter(min(0 , exclusionFilter));
String uiSurfaceParam = getIntent().getStringExtra(EXTRA_UI_SURFACE);
if (uiSurfaceParam != null && UI_SURFACE_PATTERN.matcher(uiSurfaceParam).matches()) {
@@ -436,11 +455,13 @@
widget.user.getIdentifier());
}
- if (mWidgetCategoryFilter > 0 && (info.widgetCategory & mWidgetCategoryFilter) == 0) {
+ if (!mWidgetCategoryInclusionFilter.matches(info.widgetCategory)
+ || !mWidgetCategoryExclusionFilter.matches(info.widgetCategory)) {
return rejectWidget(
widget,
- "doesn't match category filter [filter=%d, widget=%d]",
- mWidgetCategoryFilter,
+ "doesn't match category filter [inclusion=%d, exclusion=%d, widget=%d]",
+ mWidgetCategoryInclusionFilter.getCategoryMask(),
+ mWidgetCategoryExclusionFilter.getCategoryMask(),
info.widgetCategory);
}
@@ -463,7 +484,7 @@
mDesiredWidgetWidth);
}
- final int minWidth = Math.min(info.minResizeWidth, info.minWidth);
+ final int minWidth = min(info.minResizeWidth, info.minWidth);
if (minWidth > mDesiredWidgetWidth) {
return rejectWidget(
widget,
@@ -487,7 +508,7 @@
mDesiredWidgetHeight);
}
- final int minHeight = Math.min(info.minResizeHeight, info.minHeight);
+ final int minHeight = min(info.minResizeHeight, info.minHeight);
if (minHeight > mDesiredWidgetHeight) {
return rejectWidget(
widget,
diff --git a/quickstep/src/com/android/launcher3/dagger/Modules.kt b/quickstep/src/com/android/launcher3/dagger/Modules.kt
index 04c1d5e..52be413 100644
--- a/quickstep/src/com/android/launcher3/dagger/Modules.kt
+++ b/quickstep/src/com/android/launcher3/dagger/Modules.kt
@@ -21,9 +21,11 @@
import com.android.launcher3.util.ApiWrapper
import com.android.launcher3.util.PluginManagerWrapper
import com.android.launcher3.util.window.WindowManagerProxy
+import com.android.quickstep.util.GestureExclusionManager
import com.android.quickstep.util.SystemWindowManagerProxy
import dagger.Binds
import dagger.Module
+import dagger.Provides
private object Modules {}
@@ -42,3 +44,11 @@
@Binds
abstract fun bindPluginManagerWrapper(impl: PluginManagerWrapperImpl): PluginManagerWrapper
}
+
+@Module
+object StaticObjectModule {
+
+ @Provides
+ @JvmStatic
+ fun provideGestureExclusionManager(): GestureExclusionManager = GestureExclusionManager.INSTANCE
+}
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/model/data/TaskViewItemInfo.kt b/quickstep/src/com/android/launcher3/model/data/TaskViewItemInfo.kt
index ee28d7a..c201ab1 100644
--- a/quickstep/src/com/android/launcher3/model/data/TaskViewItemInfo.kt
+++ b/quickstep/src/com/android/launcher3/model/data/TaskViewItemInfo.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.content.Intent
+import android.os.Process
import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.Companion.PRIVATE
import com.android.launcher3.Flags.privateSpaceRestrictAccessibilityDrag
@@ -26,34 +27,40 @@
import com.android.launcher3.pm.UserCache
import com.android.quickstep.TaskUtils
import com.android.quickstep.views.TaskContainer
+import com.android.quickstep.views.TaskView
-class TaskViewItemInfo(taskContainer: TaskContainer) : WorkspaceItemInfo() {
+class TaskViewItemInfo(taskView: TaskView, taskContainer: TaskContainer?) : WorkspaceItemInfo() {
@VisibleForTesting(otherwise = PRIVATE) val taskViewAtom: LauncherAtom.TaskView
init {
itemType = LauncherSettings.Favorites.ITEM_TYPE_TASK
container = LauncherSettings.Favorites.CONTAINER_TASKSWITCHER
- val componentKey = TaskUtils.getLaunchComponentKeyForTask(taskContainer.task.key)
- user = componentKey.user
- intent = Intent().setComponent(componentKey.componentName)
- title = taskContainer.task.title
- if (privateSpaceRestrictAccessibilityDrag()) {
- if (
- UserCache.getInstance(taskContainer.taskView.context)
- .getUserInfo(componentKey.user)
- .isPrivate
- ) {
- runtimeStatusFlags = runtimeStatusFlags or ItemInfoWithIcon.FLAG_NOT_PINNABLE
+ val componentName: String
+ if (taskContainer != null) {
+ val componentKey = TaskUtils.getLaunchComponentKeyForTask(taskContainer.task.key)
+ user = componentKey.user
+ intent = Intent().setComponent(componentKey.componentName)
+ title = taskContainer.task.title
+ if (privateSpaceRestrictAccessibilityDrag()) {
+ if (
+ UserCache.getInstance(taskView.context).getUserInfo(componentKey.user).isPrivate
+ ) {
+ runtimeStatusFlags = runtimeStatusFlags or ItemInfoWithIcon.FLAG_NOT_PINNABLE
+ }
}
+ componentName = componentKey.componentName.flattenToShortString()
+ } else {
+ user = Process.myUserHandle()
+ intent = Intent()
+ componentName = ""
}
taskViewAtom =
createTaskViewAtom(
- type = taskContainer.taskView.type.ordinal,
- index =
- taskContainer.taskView.recentsView?.indexOfChild(taskContainer.taskView) ?: -1,
- componentName = componentKey.componentName.flattenToShortString(),
- cardinality = taskContainer.taskView.taskContainers.size,
+ type = taskView.type.ordinal,
+ index = taskView.recentsView?.indexOfChild(taskView) ?: -1,
+ componentName,
+ cardinality = taskView.taskContainers.size,
)
}
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.kt b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.kt
index 0703a61..3c4bc91 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.kt
+++ b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.kt
@@ -33,6 +33,7 @@
import com.android.quickstep.GestureState.GestureEndTarget
import com.android.quickstep.SystemUiProxy
import com.android.quickstep.fallback.RecentsState
+import com.android.wm.shell.desktopmode.DisplayDeskState
import com.android.wm.shell.desktopmode.IDesktopTaskListener.Stub
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import java.io.PrintWriter
@@ -365,6 +366,11 @@
) : Stub() {
private val controller = WeakReference(controller)
+ // TODO: b/392986431 - Implement the new desks APIs.
+ override fun onListenerConnected(
+ displayDeskStates: Array<DisplayDeskState>,
+ ) {}
+
override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {
if (displayId != this.displayId) return
Executors.MAIN_EXECUTOR.execute {
@@ -398,6 +404,15 @@
override fun onEnterDesktopModeTransitionStarted(transitionDuration: Int) {}
override fun onExitDesktopModeTransitionStarted(transitionDuration: Int) {}
+
+ // TODO: b/392986431 - Implement all the below new desks APIs.
+ override fun onCanCreateDesksChanged(displayId: Int, canCreateDesks: Boolean) {}
+
+ override fun onDeskAdded(displayId: Int, deskId: Int) {}
+
+ override fun onDeskRemoved(displayId: Int, deskId: Int) {}
+
+ override fun onActiveDeskChanged(displayId: Int, newActiveDesk: Int, oldActiveDesk: Int) {}
}
/** A listener for Taskbar in Desktop Mode. */
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
index cb16345..23065b5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
@@ -28,10 +28,12 @@
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayDragLayer;
import com.android.launcher3.util.TouchController;
+import com.android.quickstep.RecentsFilterState;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.util.DesktopTask;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.util.SingleTask;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -153,6 +155,8 @@
// Skip the task reload if the list is not changed.
if (!mModel.isTaskListValid(mTaskListChangeId) || !taskIdsToExclude.equals(
mExcludedTaskIds)) {
+ final boolean shouldShowDesktopTasks = mControllers.taskbarDesktopModeController
+ .shouldShowDesktopTasksInTaskbar();
mExcludedTaskIds = taskIdsToExclude;
mTaskListChangeId = mModel.getTasks((tasks) -> {
processLoadedTasks(tasks, taskIdsToExclude);
@@ -162,7 +166,8 @@
currentFocusIndexOverride,
mHasDesktopTask,
mWasDesktopTaskFilteredOut);
- });
+ }, shouldShowDesktopTasks ? RecentsFilterState.EMPTY_FILTER
+ : RecentsFilterState.getEmptyDesktopTaskFilter());
}
mQuickSwitchViewController.updateLayoutForSurface(wasOpenedFromTaskbar,
@@ -188,8 +193,8 @@
mQuickSwitchViewController = new KeyboardQuickSwitchViewController(
mControllers, mOverlayContext, keyboardQuickSwitchView, mControllerCallbacks);
- final boolean onDesktop = mControllers.taskbarDesktopModeController
- .getAreDesktopTasksVisibleAndNotInOverview();
+ final boolean shouldShowDesktopTasks = mControllers.taskbarDesktopModeController
+ .shouldShowDesktopTasksInTaskbar();
if (mModel.isTaskListValid(mTaskListChangeId)
&& taskIdsToExclude.equals(mExcludedTaskIds)) {
@@ -201,7 +206,7 @@
/* updateTasks= */ false,
currentFocusedIndex == -1 && !mControllerCallbacks.isFirstTaskRunning()
? 0 : currentFocusedIndex,
- onDesktop,
+ shouldShowDesktopTasks,
mHasDesktopTask,
mWasDesktopTaskFilteredOut,
wasOpenedFromTaskbar);
@@ -219,21 +224,23 @@
/* updateTasks= */ true,
currentFocusedIndex == -1 && !mControllerCallbacks.isFirstTaskRunning()
? 0 : currentFocusedIndex,
- onDesktop,
+ shouldShowDesktopTasks,
mHasDesktopTask,
mWasDesktopTaskFilteredOut,
wasOpenedFromTaskbar);
- });
+ }, shouldShowDesktopTasks ? RecentsFilterState.EMPTY_FILTER
+ : RecentsFilterState.getEmptyDesktopTaskFilter());
}
private boolean shouldExcludeTask(GroupTask task, Set<Integer> taskIdsToExclude) {
- return Flags.taskbarOverflow() && taskIdsToExclude.contains(task.task1.key.id);
+ return Flags.taskbarOverflow() && task.getTasks().stream().anyMatch(
+ t -> taskIdsToExclude.contains(t.key.id));
}
private void processLoadedTasks(List<GroupTask> tasks, Set<Integer> taskIdsToExclude) {
mHasDesktopTask = false;
mWasDesktopTaskFilteredOut = false;
- if (mControllers.taskbarDesktopModeController.getAreDesktopTasksVisibleAndNotInOverview()) {
+ if (mControllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar()) {
processLoadedTasksOnDesktop(tasks, taskIdsToExclude);
} else {
processLoadedTasksOutsideDesktop(tasks, taskIdsToExclude);
@@ -270,7 +277,7 @@
if (desktopTask != null) {
mTasks = desktopTask.getTasks().stream()
- .map(GroupTask::new)
+ .map(SingleTask::new)
.filter(task -> !shouldExcludeTask(task, taskIdsToExclude))
.collect(Collectors.toList());
// All other tasks, apart from the grouped desktop task, are hidden
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
index 306443e..4581119 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
@@ -51,6 +51,9 @@
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SingleTask;
+import com.android.quickstep.util.SplitTask;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import java.util.HashMap;
@@ -255,17 +258,24 @@
layoutInflater,
previousTaskView);
- final boolean firstTaskIsLeftTopTask =
- groupTask.mSplitBounds == null
- || groupTask.mSplitBounds.leftTopTaskId == groupTask.task1.key.id
- || groupTask.task2 == null;
+ Task task1;
+ Task task2;
+ if (groupTask instanceof SplitTask splitTask) {
+ task1 = splitTask.getTopLeftTask();
+ task2 = splitTask.getBottomRightTask();
+ } else if (groupTask instanceof SingleTask singleTask) {
+ task1 = singleTask.getTask();
+ task2 = null;
+ } else {
+ continue;
+ }
currentTaskView.setThumbnailsForSplitTasks(
- firstTaskIsLeftTopTask ? groupTask.task1 : groupTask.task2,
- firstTaskIsLeftTopTask ? groupTask.task2 : groupTask.task1,
+ task1,
+ task2,
updateTasks ? mViewCallbacks::updateThumbnailInBackground : null,
updateTasks ? mViewCallbacks::updateIconInBackground : null,
- groupTask.mSplitBounds);
+ groupTask instanceof SplitTask splitTask ? splitTask.getSplitBounds() : null);
previousTaskView = currentTaskView;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
index 8cb43d2..5af7ff8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
@@ -44,6 +44,7 @@
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SingleTask;
import com.android.quickstep.util.SlideInRemoteTransition;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -281,9 +282,10 @@
return -1;
}
RemoteTransition remoteTransition = slideInTransition;
- if (mOnDesktop
- && mControllers.taskbarActivityContext.canUnminimizeDesktopTask(task.task1.key.id)
- ) {
+ boolean canUnminimizeDesktopTask = task instanceof SingleTask singleTask
+ && mControllers.taskbarActivityContext.canUnminimizeDesktopTask(
+ singleTask.getTask().key.id);
+ if (mOnDesktop && canUnminimizeDesktopTask) {
// This app is being unminimized - use our own transition runner.
remoteTransition = new RemoteTransition(
new DesktopAppLaunchTransition(
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 5a8fba6..4143157 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -49,7 +49,7 @@
import com.android.quickstep.HomeVisibilityState;
import com.android.quickstep.RecentsAnimationCallbacks;
import com.android.quickstep.SystemUiProxy;
-import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SplitTask;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
@@ -480,8 +480,8 @@
@Override
public void launchSplitTasks(
- @NonNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition) {
- mLauncher.launchSplitTasks(groupTask, remoteTransition);
+ @NonNull SplitTask splitTask, @Nullable RemoteTransition remoteTransition) {
+ mLauncher.launchSplitTasks(splitTask, remoteTransition);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 368a036..ee5b8d1 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_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;
@@ -130,9 +131,18 @@
private final Rect mTempRect = new Rect();
- private static final int FLAG_SWITCHER_SHOWING = 1 << 0;
+ /** Whether the IME Switcher button is visible. */
+ 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;
@@ -278,13 +288,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()) {
@@ -305,9 +316,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_SWITCHER_SHOWING) != 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(
@@ -321,7 +333,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));
}
@@ -336,10 +348,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,
@@ -367,9 +379,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));
@@ -396,7 +408,7 @@
R.bool.floating_rotation_button_position_left);
mControllers.rotationButtonController.setRotationButton(mFloatingRotationButton,
mRotationButtonListener);
- if (mContext.isPhoneMode()) {
+ if (isPhoneMode) {
mTaskbarTransitions.init();
}
@@ -452,7 +464,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)
@@ -501,8 +513,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));
@@ -512,8 +523,10 @@
private void parseSystemUiFlags(@SystemUiStateFlags long sysUiStateFlags) {
mSysuiStateFlags = sysUiStateFlags;
- boolean isImeVisible = (sysUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0;
- boolean isImeSwitcherShowing = (sysUiStateFlags & SYSUI_STATE_IME_SWITCHER_SHOWING) != 0;
+ boolean isImeSwitcherButtonVisible =
+ (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,9 +540,9 @@
boolean isKeyboardShortcutHelperShowing =
(sysUiStateFlags & SYSUI_STATE_SHORTCUT_HELPER_SHOWING) != 0;
- // TODO(b/202218289) we're getting IME as not visible on lockscreen from system
+ updateStateForFlag(FLAG_IME_SWITCHER_BUTTON_VISIBLE, isImeSwitcherButtonVisible);
updateStateForFlag(FLAG_IME_VISIBLE, isImeVisible);
- updateStateForFlag(FLAG_SWITCHER_SHOWING, isImeSwitcherShowing);
+ updateStateForFlag(FLAG_BACK_DISMISS_IME, isBackDismissIme);
updateStateForFlag(FLAG_A11Y_VISIBLE, a11yVisible);
updateStateForFlag(FLAG_DISABLE_HOME, isHomeDisabled);
updateStateForFlag(FLAG_DISABLE_RECENTS, isRecentsDisabled);
@@ -1235,9 +1248,10 @@
private static String getStateString(int flags) {
StringJoiner str = new StringJoiner("|");
- appendFlag(str, flags, FLAG_SWITCHER_SHOWING, "FLAG_SWITCHER_SHOWING");
+ 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 e28e488..9d1fc15 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -29,6 +29,7 @@
import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
import static com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_OVERLAY_PROXY;
import static com.android.launcher3.Flags.enableCursorHoverStates;
+import static com.android.launcher3.Flags.removeExcludeFromScreenMagnificationFlagUsage;
import static com.android.launcher3.Utilities.calculateTextHeight;
import static com.android.launcher3.Utilities.isRunningInTestHarness;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
@@ -43,6 +44,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;
@@ -59,7 +61,6 @@
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
-import android.os.Bundle;
import android.os.IRemoteCallback;
import android.os.Process;
import android.os.Trace;
@@ -85,8 +86,8 @@
import com.android.internal.jank.Cuj;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.BubbleTextView.RunningAppState;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Flags;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.R;
@@ -156,6 +157,8 @@
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.DesktopTask;
import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SingleTask;
+import com.android.quickstep.util.SplitTask;
import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -368,7 +371,8 @@
new KeyboardQuickSwitchController(),
new TaskbarPinningController(this),
bubbleControllersOptional,
- new TaskbarDesktopModeController(DesktopVisibilityController.INSTANCE.get(this)));
+ new TaskbarDesktopModeController(this,
+ DesktopVisibilityController.INSTANCE.get(this)));
mLauncherPrefs = LauncherPrefs.get(this);
}
@@ -891,36 +895,10 @@
return makeDefaultActivityOptions(SPLASH_SCREEN_STYLE_UNDEFINED);
}
- private ActivityOptionsWrapper getActivityLaunchDesktopOptions(ItemInfo info) {
- if (!DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue()
- && !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX.isTrue()) {
- return null;
- }
- if (!areDesktopTasksVisible()) {
- return null;
- }
- BubbleTextView.RunningAppState appState =
- mControllers.taskbarRecentAppsController.getDesktopItemState(info);
- AppLaunchType launchType = null;
- switch (appState) {
- case RUNNING:
- return null;
- case MINIMIZED:
- launchType = AppLaunchType.UNMINIMIZE;
- break;
- case NOT_RUNNING:
- launchType = AppLaunchType.LAUNCH;
- break;
- }
+ private ActivityOptionsWrapper getActivityLaunchDesktopOptions() {
ActivityOptions options = ActivityOptions.makeRemoteTransition(
- new RemoteTransition(
- new DesktopAppLaunchTransition(
- /* context= */ this,
- getMainExecutor(),
- launchType,
- Cuj.CUJ_DESKTOP_MODE_APP_LAUNCH_FROM_ICON
- ),
- "TaskbarDesktopLaunch"));
+ createDesktopAppLaunchRemoteTransition(
+ AppLaunchType.LAUNCH, Cuj.CUJ_DESKTOP_MODE_APP_LAUNCH_FROM_ICON));
return new ActivityOptionsWrapper(options, new RunnableList());
}
@@ -1319,23 +1297,26 @@
mControllers.keyboardQuickSwitchController.closeQuickSwitchView(false);
- if (tag instanceof GroupTask groupTask) {
+ // TODO: b/316004172, b/343289567: Handle `DesktopTask` and `SplitTask`.
+ if (tag instanceof SingleTask singleTask) {
RemoteTransition remoteTransition =
- (areDesktopTasksVisible() && canUnminimizeDesktopTask(groupTask.task1.key.id))
- ? createUnminimizeRemoteTransition(
- Cuj.CUJ_DESKTOP_MODE_APP_LAUNCH_FROM_ICON) : null;
+ (areDesktopTasksVisible() && canUnminimizeDesktopTask(
+ singleTask.getTask().key.id))
+ ? createDesktopAppLaunchRemoteTransition(AppLaunchType.UNMINIMIZE,
+ Cuj.CUJ_DESKTOP_MODE_APP_LAUNCH_FROM_ICON)
+ : null;
if (areDesktopTasksVisible() && mControllers.uiController.isInOverviewUi()) {
RunnableList runnableList = recents.launchRunningDesktopTaskView();
// Wrapping it in runnable so we post after DW is ready for the app
// launch.
if (runnableList != null) {
runnableList.add(() -> UI_HELPER_EXECUTOR.execute(
- () -> handleGroupTaskLaunch(groupTask, remoteTransition,
+ () -> handleGroupTaskLaunch(singleTask, remoteTransition,
areDesktopTasksVisible(),
DesktopTaskToFrontReason.TASKBAR_TAP)));
}
} else {
- handleGroupTaskLaunch(groupTask, remoteTransition, areDesktopTasksVisible(),
+ handleGroupTaskLaunch(singleTask, remoteTransition, areDesktopTasksVisible(),
DesktopTaskToFrontReason.TASKBAR_TAP);
}
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
@@ -1354,9 +1335,10 @@
mControllers.uiController.onTaskbarIconLaunched(api);
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
}
- } else if (tag instanceof TaskItemInfo info && !Flags.enableMultiInstanceMenuTaskbar()) {
+ } else if (tag instanceof TaskItemInfo info) {
RemoteTransition remoteTransition = canUnminimizeDesktopTask(info.getTaskId())
- ? createUnminimizeRemoteTransition(Cuj.CUJ_DESKTOP_MODE_APP_LAUNCH_FROM_ICON)
+ ? createDesktopAppLaunchRemoteTransition(
+ AppLaunchType.UNMINIMIZE, Cuj.CUJ_DESKTOP_MODE_APP_LAUNCH_FROM_ICON)
: null;
TaskView taskView = null;
@@ -1364,7 +1346,8 @@
taskView = recents.getTaskViewByTaskId(info.getTaskId());
}
- if (areDesktopTasksVisible() && taskView != null) {
+ if (areDesktopTasksVisible() && taskView != null
+ && mControllers.uiController.isInOverviewUi()) {
RunnableList runnableList = taskView.launchWithAnimation();
if (runnableList != null) {
runnableList.add(() ->
@@ -1503,13 +1486,13 @@
remoteTransition));
return;
}
- if (onDesktop) {
- boolean useRemoteTransition = canUnminimizeDesktopTask(task.task1.key.id);
+ if (onDesktop && task instanceof SingleTask singleTask) {
+ boolean useRemoteTransition = canUnminimizeDesktopTask(singleTask.getTask().key.id);
UI_HELPER_EXECUTOR.execute(() -> {
if (onStartCallback != null) {
onStartCallback.run();
}
- SystemUiProxy.INSTANCE.get(this).showDesktopApp(task.task1.key.id,
+ SystemUiProxy.INSTANCE.get(this).showDesktopApp(singleTask.getTask().key.id,
useRemoteTransition ? remoteTransition : null, toFrontReason);
if (onFinishCallback != null) {
onFinishCallback.run();
@@ -1517,39 +1500,39 @@
});
return;
}
- if (task.task2 == null) {
+ if (task instanceof SingleTask singleTask) {
UI_HELPER_EXECUTOR.execute(() -> {
ActivityOptions activityOptions =
makeDefaultActivityOptions(SPLASH_SCREEN_STYLE_UNDEFINED).options;
activityOptions.setRemoteTransition(remoteTransition);
ActivityManagerWrapper.getInstance().startActivityFromRecents(
- task.task1.key, activityOptions);
+ singleTask.getTask().key, activityOptions);
});
return;
}
- mControllers.uiController.launchSplitTasks(task, remoteTransition);
+ assert task instanceof SplitTask;
+ mControllers.uiController.launchSplitTasks((SplitTask) task, remoteTransition);
}
/** Returns whether the given task is minimized and can be unminimized. */
public boolean canUnminimizeDesktopTask(int taskId) {
BubbleTextView.RunningAppState runningAppState =
mControllers.taskbarRecentAppsController.getRunningAppState(taskId);
- return runningAppState == BubbleTextView.RunningAppState.MINIMIZED
- && (DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS.isTrue()
- || DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS_BUGFIX.isTrue()
- );
+ return runningAppState == RunningAppState.MINIMIZED
+ && DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS_BUGFIX.isTrue();
}
- private RemoteTransition createUnminimizeRemoteTransition(@Cuj.CujType int cujType) {
+ private RemoteTransition createDesktopAppLaunchRemoteTransition(
+ AppLaunchType appLaunchType, @Cuj.CujType int cujType) {
return new RemoteTransition(
new DesktopAppLaunchTransition(
this,
getMainExecutor(),
- AppLaunchType.UNMINIMIZE,
+ appLaunchType,
cujType
),
- "TaskbarDesktopUnminimize");
+ "TaskbarDesktopAppLaunch");
}
/**
@@ -1680,10 +1663,9 @@
return;
}
}
- if (areDesktopTasksVisible()) {
- ActivityOptionsWrapper opts = getActivityLaunchDesktopOptions(info);
- Bundle optionsBundle = opts == null ? Bundle.EMPTY : opts.options.toBundle();
- mSysUiProxy.startLaunchIntentTransition(intent, optionsBundle, displayId);
+ if (areDesktopTasksVisible()
+ && DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX.isTrue()) {
+ launchDesktopApp(intent, info, displayId);
} else {
startActivity(intent, null);
}
@@ -1694,6 +1676,31 @@
}
}
+ private void launchDesktopApp(Intent intent, ItemInfo info, int displayId) {
+ TaskbarRecentAppsController.TaskState taskState =
+ mControllers.taskbarRecentAppsController.getDesktopItemState(info);
+ RunningAppState appState = taskState.getRunningAppState();
+ if (appState == RunningAppState.RUNNING || appState == RunningAppState.MINIMIZED) {
+ // We only need a custom animation (a RemoteTransition) if the task is minimized - if
+ // it's already visible it will just be brought forward.
+ RemoteTransition remoteTransition = (appState == RunningAppState.MINIMIZED)
+ ? createDesktopAppLaunchRemoteTransition(
+ AppLaunchType.UNMINIMIZE, Cuj.CUJ_DESKTOP_MODE_APP_LAUNCH_FROM_ICON)
+ : null;
+ UI_HELPER_EXECUTOR.execute(() ->
+ SystemUiProxy.INSTANCE.get(this).showDesktopApp(taskState.getTaskId(),
+ remoteTransition, DesktopTaskToFrontReason.TASKBAR_TAP));
+ return;
+ }
+ // There is no task associated with this launch - launch a new task through an intent
+ ActivityOptionsWrapper opts = getActivityLaunchDesktopOptions();
+ if (enableStartLaunchTransitionFromTaskbarBugfix()) {
+ mSysUiProxy.startLaunchIntentTransition(intent, opts.options.toBundle(), displayId);
+ } else {
+ startActivity(intent, opts.options.toBundle());
+ }
+ }
+
/** Expands a folder icon when it is clicked */
private void expandFolder(FolderIcon folderIcon) {
Folder folder = folderIcon.getFolder();
@@ -1900,6 +1907,10 @@
return;
}
+ if (removeExcludeFromScreenMagnificationFlagUsage()) {
+ return;
+ }
+
mIsExcludeFromMagnificationRegion = exclude;
if (exclude) {
mWindowLayoutParams.privateFlags |=
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt
index a7c7381..8806bc6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt
@@ -16,13 +16,16 @@
package com.android.launcher3.taskbar
+import android.content.Context
import com.android.launcher3.statehandlers.DesktopVisibilityController
import com.android.launcher3.statehandlers.DesktopVisibilityController.TaskbarDesktopModeListener
import com.android.launcher3.taskbar.TaskbarBackgroundRenderer.Companion.MAX_ROUNDNESS
+import com.android.launcher3.util.DisplayController
/** Handles Taskbar in Desktop Windowing mode. */
class TaskbarDesktopModeController(
- private val desktopVisibilityController: DesktopVisibilityController
+ private val context: Context,
+ private val desktopVisibilityController: DesktopVisibilityController,
) : TaskbarDesktopModeListener {
private lateinit var taskbarControllers: TaskbarControllers
private lateinit var taskbarSharedState: TaskbarSharedState
@@ -45,6 +48,12 @@
taskbarControllers.taskbarCornerRoundness.animateToValue(cornerRadius).start()
}
+ fun shouldShowDesktopTasksInTaskbar(): Boolean {
+ return desktopVisibilityController.areDesktopTasksVisible() ||
+ DisplayController.showLockedTaskbarOnHome(context) &&
+ taskbarControllers.taskbarStashController.isOnHome
+ }
+
fun getTaskbarCornerRoundness(doesAnyTaskRequireTaskbarRounding: Boolean): Float {
return if (doesAnyTaskRequireTaskbarRounding) {
MAX_ROUNDNESS
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index a9e8d6d..3a83db2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -80,9 +80,9 @@
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.views.BubbleTextHolder;
-import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.LogUtils;
import com.android.quickstep.util.MultiValueUpdateListener;
+import com.android.quickstep.util.SingleTask;
import com.android.systemui.shared.recents.model.Task;
import com.android.wm.shell.shared.draganddrop.DragAndDropConstants;
@@ -433,8 +433,8 @@
null, item.user));
}
intent.putExtra(Intent.EXTRA_USER, item.user);
- } else if (tag instanceof GroupTask groupTask && !groupTask.hasMultipleTasks()) {
- Task task = groupTask.task1;
+ } else if (tag instanceof SingleTask singleTask) {
+ Task task = singleTask.getTask();
clipDescription = new ClipDescription(task.titleDescription,
new String[] {
ClipDescription.MIMETYPE_APPLICATION_TASK
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 21c8255..ea2dec1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -89,6 +89,9 @@
public class TaskbarManager {
private static final String TAG = "TaskbarManager";
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.
@@ -112,9 +115,7 @@
private static final Uri NAV_BAR_KIDS_MODE = Settings.Secure.getUriFor(
Settings.Secure.NAV_BAR_KIDS_MODE);
- private final Context mWindowContext;
- private final @Nullable Context mNavigationBarPanelContext;
- private WindowManager mWindowManager;
+ private final Context mParentContext;
private final TaskbarNavButtonController mDefaultNavButtonController;
private final ComponentCallbacks mDefaultComponentCallbacks;
@@ -129,6 +130,8 @@
new NonDestroyableScopedUnfoldTransitionProgressProvider();
/** DisplayId - {@link TaskbarActivityContext} map for Connected Display. */
private final SparseArray<TaskbarActivityContext> mTaskbars = new SparseArray<>();
+ /** DisplayId - {@link Context} map for Connected Display. */
+ private final SparseArray<Context> mWindowContexts = new SparseArray<>();
/** DisplayId - {@link FrameLayout} map for Connected Display. */
private final SparseArray<FrameLayout> mRootLayouts = new SparseArray<>();
/** DisplayId - {@link Boolean} map indicating if RootLayout was added to window. */
@@ -151,6 +154,20 @@
private class RecreationListener implements DisplayController.DisplayInfoChangeListener {
@Override
public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
+
+ if ((flags & CHANGE_DENSITY) != 0) {
+ Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG, "Display density changed");
+ }
+ if ((flags & CHANGE_NAVIGATION_MODE) != 0) {
+ Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG, "Navigation mode changed");
+ }
+ if ((flags & CHANGE_DESKTOP_MODE) != 0) {
+ Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG, "Desktop mode changed");
+ }
+ if ((flags & CHANGE_TASKBAR_PINNING) != 0) {
+ Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG, "Taskbar pinning changed");
+ }
+
if ((flags & (CHANGE_DENSITY | CHANGE_NAVIGATION_MODE | CHANGE_DESKTOP_MODE
| CHANGE_TASKBAR_PINNING)) != 0) {
recreateTaskbar();
@@ -226,36 +243,30 @@
Context context,
AllAppsActionManager allAppsActionManager,
TaskbarNavButtonCallbacks navCallbacks) {
- Display display =
- context.getSystemService(DisplayManager.class).getDisplay(context.getDisplayId());
- mWindowContext = context.createWindowContext(display,
- ENABLE_TASKBAR_NAVBAR_UNIFICATION ? TYPE_NAVIGATION_BAR : TYPE_NAVIGATION_BAR_PANEL,
- null);
+ mParentContext = context;
+ createWindowContext(context.getDisplayId());
mAllAppsActionManager = allAppsActionManager;
- mNavigationBarPanelContext = ENABLE_TASKBAR_NAVBAR_UNIFICATION
- ? context.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null)
- : null;
if (enableTaskbarNoRecreate()) {
- mWindowManager = mWindowContext.getSystemService(WindowManager.class);
createTaskbarRootLayout(getDefaultDisplayId());
}
mDefaultNavButtonController = createDefaultNavButtonController(context, navCallbacks);
mDefaultComponentCallbacks = createDefaultComponentCallbacks();
- SettingsCache.INSTANCE.get(mWindowContext)
+ SettingsCache.INSTANCE.get(getPrimaryWindowContext())
.register(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
- SettingsCache.INSTANCE.get(mWindowContext)
+ SettingsCache.INSTANCE.get(getPrimaryWindowContext())
.register(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
Log.d(TASKBAR_NOT_DESTROYED_TAG, "registering component callbacks from constructor.");
- mWindowContext.registerComponentCallbacks(mDefaultComponentCallbacks);
- mShutdownReceiver.register(mWindowContext, Intent.ACTION_SHUTDOWN);
+ getPrimaryWindowContext().registerComponentCallbacks(mDefaultComponentCallbacks);
+ mShutdownReceiver.register(getPrimaryWindowContext(), Intent.ACTION_SHUTDOWN);
UI_HELPER_EXECUTOR.execute(() -> {
mSharedState.taskbarSystemActionPendingIntent = PendingIntent.getBroadcast(
- mWindowContext,
+ getPrimaryWindowContext(),
SYSTEM_ACTION_ID_TASKBAR,
- new Intent(ACTION_SHOW_TASKBAR).setPackage(mWindowContext.getPackageName()),
+ new Intent(ACTION_SHOW_TASKBAR).setPackage(
+ getPrimaryWindowContext().getPackageName()),
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
mTaskbarBroadcastReceiver.register(
- mWindowContext, RECEIVER_NOT_EXPORTED, ACTION_SHOW_TASKBAR);
+ getPrimaryWindowContext(), RECEIVER_NOT_EXPORTED, ACTION_SHOW_TASKBAR);
});
debugWhyTaskbarNotDestroyed("TaskbarManager created");
@@ -268,14 +279,15 @@
return new TaskbarNavButtonController(
context,
navCallbacks,
- SystemUiProxy.INSTANCE.get(mWindowContext),
+ SystemUiProxy.INSTANCE.get(getPrimaryWindowContext()),
new Handler(),
- new ContextualSearchInvoker(mWindowContext));
+ new ContextualSearchInvoker(getPrimaryWindowContext()));
}
private ComponentCallbacks createDefaultComponentCallbacks() {
return new ComponentCallbacks() {
- private Configuration mOldConfig = mWindowContext.getResources().getConfiguration();
+ private Configuration mOldConfig =
+ getPrimaryWindowContext().getResources().getConfiguration();
@Override
public void onConfigurationChanged(Configuration newConfig) {
@@ -285,11 +297,13 @@
"TaskbarManager#mComponentCallbacks.onConfigurationChanged: " + newConfig);
// TODO: adapt this logic to be specific to different displays.
DeviceProfile dp = mUserUnlocked
- ? LauncherAppState.getIDP(mWindowContext).getDeviceProfile(mWindowContext)
+ ? LauncherAppState.getIDP(getPrimaryWindowContext()).getDeviceProfile(
+ getPrimaryWindowContext())
: null;
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);
@@ -336,6 +350,7 @@
int displayId = mTaskbars.keyAt(i);
destroyTaskbarForDisplay(displayId);
removeTaskbarRootViewFromWindow(displayId);
+ removeWindowContextFromMap(displayId);
}
}
@@ -344,16 +359,18 @@
}
private void destroyTaskbarForDisplay(int displayId) {
+ Log.d(ILLEGAL_ARGUMENT_WM_ADD_VIEW, "destroyTaskbarForDisplay: " + displayId);
TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
- debugWhyTaskbarNotDestroyed(
- "destroyTaskbarForDisplay: " + taskbar + " displayId=" + displayId);
+ debugWhyTaskbarNotDestroyed("destroyTaskbarForDisplay: " + taskbar, displayId);
if (taskbar != null) {
taskbar.onDestroy();
// remove all defaults that we store
removeTaskbarFromMap(displayId);
}
+ // make this display-specific
DeviceProfile dp = mUserUnlocked ?
- LauncherAppState.getIDP(mWindowContext).getDeviceProfile(mWindowContext) : null;
+ LauncherAppState.getIDP(getWindowContext(displayId)).getDeviceProfile(
+ getWindowContext(displayId)) : null;
if (dp == null || !isTaskbarEnabled(dp)) {
removeTaskbarRootViewFromWindow(displayId);
}
@@ -400,7 +417,8 @@
*/
public void onUserUnlocked() {
mUserUnlocked = true;
- DisplayController.INSTANCE.get(mWindowContext).addChangeListener(mRecreationListener);
+ DisplayController.INSTANCE.get(getPrimaryWindowContext()).addChangeListener(
+ mRecreationListener);
recreateTaskbar();
addTaskbarRootViewToWindow(getDefaultDisplayId());
}
@@ -463,7 +481,8 @@
return ql.getUnfoldTransitionProgressProvider();
}
} else {
- return SystemUiProxy.INSTANCE.get(mWindowContext).getUnfoldTransitionProvider();
+ return SystemUiProxy.INSTANCE.get(
+ getPrimaryWindowContext()).getUnfoldTransitionProvider();
}
return null;
}
@@ -506,8 +525,11 @@
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;
+ LauncherAppState.getIDP(getWindowContext(displayId)).getDeviceProfile(
+ getWindowContext(displayId)) : null;
// All Apps action is unrelated to navbar unification, so we only need to check DP.
final boolean isLargeScreenTaskbar = dp != null && dp.isTaskbarPresent;
@@ -521,7 +543,7 @@
+ " FLAG_HIDE_NAVBAR_WINDOW=" + ENABLE_TASKBAR_NAVBAR_UNIFICATION
+ " dp.isTaskbarPresent=" + (dp == null ? "null" : dp.isTaskbarPresent));
if (!isTaskbarEnabled || !isLargeScreenTaskbar) {
- SystemUiProxy.INSTANCE.get(mWindowContext)
+ SystemUiProxy.INSTANCE.get(getPrimaryWindowContext())
.notifyTaskbarStatus(/* visible */ false, /* stashed */ false);
if (!isTaskbarEnabled) {
return;
@@ -547,9 +569,14 @@
if (enableTaskbarNoRecreate()) {
addTaskbarRootViewToWindow(displayId);
FrameLayout taskbarRootLayout = getTaskbarRootLayoutForDisplay(displayId);
- taskbarRootLayout.removeAllViews();
- taskbarRootLayout.addView(taskbar.getDragLayer());
- taskbar.notifyUpdateLayoutParams();
+ if (taskbarRootLayout != null) {
+ taskbarRootLayout.removeAllViews();
+ taskbarRootLayout.addView(taskbar.getDragLayer());
+ taskbar.notifyUpdateLayoutParams();
+ } else {
+ Log.e(NULL_TASKBAR_ROOT_LAYOUT_TAG,
+ "taskbarRootLayout is null for displayId=" + displayId);
+ }
}
} finally {
Trace.endSection();
@@ -689,9 +716,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) {
}
/**
@@ -701,6 +728,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);
@@ -723,56 +755,74 @@
mRecentsViewContainer = null;
debugWhyTaskbarNotDestroyed("TaskbarManager#destroy()");
removeActivityCallbacksAndListeners();
- mTaskbarBroadcastReceiver.unregisterReceiverSafely(mWindowContext);
- destroyAllTaskbars();
+ mTaskbarBroadcastReceiver.unregisterReceiverSafely(getPrimaryWindowContext());
+
if (mUserUnlocked) {
- DisplayController.INSTANCE.get(mWindowContext).removeChangeListener(
+ DisplayController.INSTANCE.get(getPrimaryWindowContext()).removeChangeListener(
mRecreationListener);
}
- SettingsCache.INSTANCE.get(mWindowContext)
+ SettingsCache.INSTANCE.get(getPrimaryWindowContext())
.unregister(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
- SettingsCache.INSTANCE.get(mWindowContext)
+ SettingsCache.INSTANCE.get(getPrimaryWindowContext())
.unregister(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
Log.d(TASKBAR_NOT_DESTROYED_TAG, "unregistering component callbacks from destroy().");
- mWindowContext.unregisterComponentCallbacks(mDefaultComponentCallbacks);
- mShutdownReceiver.unregisterReceiverSafely(mWindowContext);
+ getPrimaryWindowContext().unregisterComponentCallbacks(mDefaultComponentCallbacks);
+ mShutdownReceiver.unregisterReceiverSafely(getPrimaryWindowContext());
+ destroyAllTaskbars();
}
public @Nullable TaskbarActivityContext getCurrentActivityContext() {
- return getTaskbarForDisplay(mWindowContext.getDisplayId());
+ return getTaskbarForDisplay(getDefaultDisplayId());
}
public void dumpLogs(String prefix, PrintWriter pw) {
pw.println(prefix + "TaskbarManager:");
- TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
- if (taskbar == null) {
- pw.println(prefix + "\tTaskbarActivityContext: null");
- } else {
- taskbar.dumpLogs(prefix + "\t", pw);
+ // iterate through taskbars and do the dump for each
+ for (int i = 0; i < mTaskbars.size(); i++) {
+ int displayId = mTaskbars.keyAt(i);
+ TaskbarActivityContext taskbar = mTaskbars.get(i);
+ pw.println(prefix + "\tTaskbar at display " + displayId + ":");
+ if (taskbar == null) {
+ pw.println(prefix + "\t\tTaskbarActivityContext: null");
+ } else {
+ taskbar.dumpLogs(prefix + "\t\t", pw);
+ }
}
}
private void addTaskbarRootViewToWindow(int displayId) {
TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
if (!enableTaskbarNoRecreate() || taskbar == null) {
+ Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG,
+ "addTaskbarRootViewToWindow - taskbar null | displayId=" + displayId);
return;
}
if (!isTaskbarRootLayoutAddedForDisplay(displayId)) {
- mWindowManager.addView(getTaskbarRootLayoutForDisplay(displayId),
- taskbar.getWindowLayoutParams());
- mAddedRootLayouts.put(displayId, true);
+ FrameLayout rootLayout = getTaskbarRootLayoutForDisplay(displayId);
+ if (rootLayout != null) {
+ getWindowManager(displayId).addView(rootLayout, 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="
+ + displayId);
}
}
private void removeTaskbarRootViewFromWindow(int displayId) {
+ Log.d(ILLEGAL_ARGUMENT_WM_ADD_VIEW, "removeTaskbarRootViewFromWindow: " + displayId);
FrameLayout rootLayout = getTaskbarRootLayoutForDisplay(displayId);
if (!enableTaskbarNoRecreate() || rootLayout == null) {
return;
}
if (isTaskbarRootLayoutAddedForDisplay(displayId)) {
- mWindowManager.removeViewImmediate(rootLayout);
+ getWindowManager(displayId).removeViewImmediate(rootLayout);
mAddedRootLayouts.put(displayId, false);
removeTaskbarRootLayoutFromMap(displayId);
}
@@ -805,10 +855,16 @@
* Creates a {@link TaskbarActivityContext} for the given display and adds it to the map.
*/
private TaskbarActivityContext createTaskbarActivityContext(DeviceProfile dp, int displayId) {
- TaskbarActivityContext newTaskbar = new TaskbarActivityContext(mWindowContext,
- mNavigationBarPanelContext, dp, mDefaultNavButtonController,
+ Display display = mParentContext.getSystemService(DisplayManager.class).getDisplay(
+ displayId);
+ Context navigationBarPanelContext = ENABLE_TASKBAR_NAVBAR_UNIFICATION
+ ? mParentContext.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null)
+ : null;
+
+ TaskbarActivityContext newTaskbar = new TaskbarActivityContext(getWindowContext(displayId),
+ navigationBarPanelContext, dp, mDefaultNavButtonController,
mUnfoldProgressProvider, isDefaultDisplay(displayId),
- SystemUiProxy.INSTANCE.get(mWindowContext));
+ SystemUiProxy.INSTANCE.get(getPrimaryWindowContext()));
addTaskbarToMap(displayId, newTaskbar);
return newTaskbar;
@@ -841,7 +897,8 @@
* @param displayId The ID of the display for which to create the taskbar root layout.
*/
private void createTaskbarRootLayout(int displayId) {
- FrameLayout newTaskbarRootLayout = new FrameLayout(mWindowContext) {
+ Log.d(ILLEGAL_ARGUMENT_WM_ADD_VIEW, "createTaskbarRootLayout: " + displayId);
+ FrameLayout newTaskbarRootLayout = new FrameLayout(getWindowContext(displayId)) {
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// The motion events can be outside the view bounds of task bar, and hence
@@ -854,6 +911,7 @@
}
};
addTaskbarRootLayoutToMap(displayId, newTaskbarRootLayout);
+ Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG, "created new root layout - displayId=" + displayId);
}
private boolean isDefaultDisplay(int displayId) {
@@ -867,7 +925,15 @@
* @return The taskbar root layout {@link FrameLayout} for a given display or {@code null}.
*/
private FrameLayout getTaskbarRootLayoutForDisplay(int displayId) {
- return mRootLayouts.get(displayId);
+ Log.d(ILLEGAL_ARGUMENT_WM_ADD_VIEW, "getTaskbarRootLayoutForDisplay: " + displayId);
+ FrameLayout frameLayout = mRootLayouts.get(displayId);
+ if (frameLayout != null) {
+ return frameLayout;
+ } else {
+ Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG,
+ "getTaskbarRootLayoutForDisplay == null | displayId=" + displayId);
+ return null;
+ }
}
/**
@@ -880,6 +946,8 @@
if (!mRootLayouts.contains(displayId) && rootLayout != null) {
mRootLayouts.put(displayId, rootLayout);
}
+
+ Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG, "mRootLayouts.size()=" + mRootLayouts.size());
}
/**
@@ -892,21 +960,101 @@
mAddedRootLayouts.delete(displayId);
mRootLayouts.delete(displayId);
}
+
+ Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG, "mRootLayouts.size()=" + mRootLayouts.size());
+ }
+
+ /**
+ * Creates {@link Context} for the taskbar on the specified display and›› adds it to map.
+ * @param displayId The ID of the display for which to create the window context.
+ */
+ private void createWindowContext(int displayId) {
+ DisplayManager displayManager = mParentContext.getSystemService(DisplayManager.class);
+ if (displayManager == null) {
+ return;
+ }
+
+ Display display = displayManager.getDisplay(displayId);
+ if (display != null) {
+ int windowType = (ENABLE_TASKBAR_NAVBAR_UNIFICATION && isDefaultDisplay(displayId))
+ ? TYPE_NAVIGATION_BAR : TYPE_NAVIGATION_BAR_PANEL;
+ Context newContext = mParentContext.createWindowContext(display, windowType, null);
+ addWindowContextToMap(displayId, newContext);
+ }
+ }
+
+ /**
+ * Retrieves the window context of the taskbar for the specified display.
+ *
+ * @param displayId The ID of the display for which to retrieve the window context.
+ * @return The Window Context {@link Context} for a given display or {@code null}.
+ */
+ private Context getWindowContext(int displayId) {
+ return mWindowContexts.get(displayId);
+ }
+
+ @VisibleForTesting
+ public Context getPrimaryWindowContext() {
+ return getWindowContext(getDefaultDisplayId());
+ }
+
+ /**
+ * Retrieves the window manager {@link WindowManager} of the taskbar for the specified display.
+ *
+ * @param displayId The ID of the display for which to retrieve the window manager.
+ * @return The window manager {@link WindowManager} for a given display or {@code null}.
+ */
+ private WindowManager getWindowManager(int displayId) {
+ return getWindowContext(displayId).getSystemService(WindowManager.class);
+ }
+
+ /**
+ * Adds the window context {@link Context} to taskbar map, mapped to display ID.
+ *
+ * @param displayId The ID of the display to associate with the taskbar root layout.
+ * @param windowContext The window context {@link Context} to add to the map.
+ */
+ private void addWindowContextToMap(int displayId, @NonNull Context windowContext) {
+ if (!mWindowContexts.contains(displayId)) {
+ mWindowContexts.put(displayId, windowContext);
+ }
+ }
+
+ /**
+ * Removes the window context {@link Context} for given display ID from the taskbar map.
+ *
+ * @param displayId The ID of the display for which to remove the taskbar root layout.
+ */
+ private void removeWindowContextFromMap(int displayId) {
+ if (mWindowContexts.contains(displayId)) {
+ mWindowContexts.delete(displayId);
+ }
}
private int getDefaultDisplayId() {
- return mWindowContext.getDisplayId();
+ return mParentContext.getDisplayId();
}
/** Temp logs for b/254119092. */
public void debugWhyTaskbarNotDestroyed(String debugReason) {
+ debugWhyTaskbarNotDestroyed(debugReason, getDefaultDisplayId());
+ }
+
+ /** Temp logs for b/254119092. */
+ public void debugWhyTaskbarNotDestroyed(String debugReason, int displayId) {
StringJoiner log = new StringJoiner("\n");
- log.add(debugReason);
+ log.add(debugReason + " displayId=" + displayId);
boolean activityTaskbarPresent = mActivity != null
&& mActivity.getDeviceProfile().isTaskbarPresent;
- boolean contextTaskbarPresent = mUserUnlocked && LauncherAppState.getIDP(mWindowContext)
- .getDeviceProfile(mWindowContext).isTaskbarPresent;
+ Context windowContext = getWindowContext(displayId);
+ if (windowContext == null) {
+ log.add("window context for displayId" + displayId);
+ return;
+ }
+
+ boolean contextTaskbarPresent = mUserUnlocked && LauncherAppState.getIDP(windowContext)
+ .getDeviceProfile(windowContext).isTaskbarPresent;
if (activityTaskbarPresent == contextTaskbarPresent) {
log.add("mActivity and mWindowContext agree taskbarIsPresent=" + contextTaskbarPresent);
Log.d(TASKBAR_NOT_DESTROYED_TAG, log.toString());
@@ -923,12 +1071,12 @@
log.add("\t\tmActivity.getDeviceProfile().isTaskbarPresent="
+ activityTaskbarPresent);
}
- log.add("\tmWindowContext logs:");
- log.add("\t\tmWindowContext=" + mWindowContext);
- log.add("\t\tmWindowContext.getResources().getConfiguration()="
- + mWindowContext.getResources().getConfiguration());
+ log.add("\tWindowContext logs:");
+ log.add("\t\tWindowContext=" + windowContext);
+ log.add("\t\tWindowContext.getResources().getConfiguration()="
+ + windowContext.getResources().getConfiguration());
if (mUserUnlocked) {
- log.add("\t\tLauncherAppState.getIDP().getDeviceProfile(mWindowContext)"
+ log.add("\t\tLauncherAppState.getIDP().getDeviceProfile(getPrimaryWindowContext())"
+ ".isTaskbarPresent=" + contextTaskbarPresent);
} else {
log.add("\t\tCouldn't get DeviceProfile because !mUserUnlocked");
@@ -940,8 +1088,4 @@
private final DeviceProfile.OnDeviceProfileChangeListener mDebugActivityDeviceProfileChanged =
dp -> debugWhyTaskbarNotDestroyed("mActivity onDeviceProfileChanged");
- @VisibleForTesting
- public Context getWindowContext() {
- return mWindowContext;
- }
}
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/TaskbarOverflowView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarOverflowView.java
index 0ed6669..017a12c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarOverflowView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarOverflowView.java
@@ -39,7 +39,6 @@
import androidx.core.graphics.ColorUtils;
import com.android.app.animation.Interpolators;
-import com.android.launcher3.R;
import com.android.launcher3.Reorderable;
import com.android.launcher3.Utilities;
import com.android.launcher3.icons.IconNormalizer;
@@ -243,7 +242,9 @@
private void init() {
mIsRtlLayout = Utilities.isRtl(getResources());
mItemBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mItemBackgroundColor = getContext().getColor(R.color.taskbar_background);
+ mItemBackgroundColor = getContext().getColor(Utilities.isDarkTheme(getContext())
+ ? com.android.internal.R.color.materialColorSurface
+ : com.android.internal.R.color.materialColorInverseOnSurface);
mLeaveBehindColor = Themes.getAttrColor(getContext(), android.R.attr.textColorTertiary);
setWillNotDraw(false);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
index 4afabde..417ef7e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
@@ -26,9 +26,11 @@
import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
import com.android.launcher3.util.CancellableTask
+import com.android.quickstep.RecentsFilterState
import com.android.quickstep.RecentsModel
import com.android.quickstep.util.DesktopTask
import com.android.quickstep.util.GroupTask
+import com.android.quickstep.util.SingleTask
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import java.io.PrintWriter
@@ -74,6 +76,17 @@
var shownTasks: List<GroupTask> = emptyList()
private set
+ val shownTaskIds: List<Int>
+ get() = shownTasks.flatMap { shownTask -> shownTask.tasks }.map { it.key.id }
+
+ /**
+ * The task-state of an app, i.e. whether the app has a task and what state that task is in.
+ *
+ * @property taskId The ID of the task if one exists (i.e. if the state is RUNNING or
+ * MINIMIZED), null otherwise (NOT_RUNNING).
+ */
+ data class TaskState(val runningAppState: RunningAppState, val taskId: Int? = null)
+
/**
* Returns the state of the most active Desktop task represented by the given [ItemInfo].
*
@@ -81,24 +94,28 @@
* i.e. we return [DesktopAppState.RUNNING] over [DesktopAppState.MINIMIZED], and
* [DesktopAppState.MINIMIZED] over [DesktopAppState.NOT_RUNNING].
*/
- fun getDesktopItemState(itemInfo: ItemInfo?): RunningAppState {
- val packageName = itemInfo?.getTargetPackage() ?: return RunningAppState.NOT_RUNNING
- return getDesktopAppState(packageName, itemInfo.user.identifier)
+ fun getDesktopItemState(itemInfo: ItemInfo?): TaskState {
+ val packageName =
+ itemInfo?.getTargetPackage() ?: return TaskState(RunningAppState.NOT_RUNNING)
+ return getDesktopTaskState(packageName, itemInfo.user.identifier)
}
- private fun getDesktopAppState(packageName: String, userId: Int): RunningAppState {
- val tasks = desktopTask?.tasks ?: return RunningAppState.NOT_RUNNING
+ private fun getDesktopTaskState(packageName: String, userId: Int): TaskState {
+ val tasks = desktopTask?.tasks ?: return TaskState(RunningAppState.NOT_RUNNING)
val appTasks =
tasks.filter { task ->
packageName == task.key.packageName && task.key.userId == userId
}
- if (appTasks.find { getRunningAppState(it.key.id) == RunningAppState.RUNNING } != null) {
- return RunningAppState.RUNNING
+ val runningTask = appTasks.find { getRunningAppState(it.key.id) == RunningAppState.RUNNING }
+ if (runningTask != null) {
+ return TaskState(RunningAppState.RUNNING, runningTask.key.id)
}
- if (appTasks.find { getRunningAppState(it.key.id) == RunningAppState.MINIMIZED } != null) {
- return RunningAppState.MINIMIZED
+ val minimizedTask =
+ appTasks.find { getRunningAppState(it.key.id) == RunningAppState.MINIMIZED }
+ if (minimizedTask != null) {
+ return TaskState(RunningAppState.MINIMIZED, taskId = minimizedTask.key.id)
}
- return RunningAppState.NOT_RUNNING
+ return TaskState(RunningAppState.NOT_RUNNING)
}
/** Get the [RunningAppState] for the given task. */
@@ -119,7 +136,7 @@
get() {
if (
!canShowRunningApps ||
- !controllers.taskbarDesktopModeController.areDesktopTasksVisible
+ !controllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar()
) {
return emptySet()
}
@@ -135,7 +152,7 @@
get() {
if (
!canShowRunningApps ||
- !controllers.taskbarDesktopModeController.areDesktopTasksVisible
+ !controllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar()
) {
return emptySet()
}
@@ -171,10 +188,10 @@
/** Called to update hotseatItems, in order to de-dupe them from Recent/Running tasks later. */
fun updateHotseatItemInfos(hotseatItems: Array<ItemInfo?>): Array<ItemInfo?> {
// Ignore predicted apps - we show running or recent apps instead.
- val areDesktopTasksVisible = controllers.taskbarDesktopModeController.areDesktopTasksVisible
+ val showDesktopTasks =
+ controllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar()
val removePredictions =
- (areDesktopTasksVisible && canShowRunningApps) ||
- (!areDesktopTasksVisible && canShowRecentApps)
+ (showDesktopTasks && canShowRunningApps) || (!showDesktopTasks && canShowRecentApps)
if (!removePredictions) {
shownHotseatItems = hotseatItems.filterNotNull()
onRecentsOrHotseatChanged()
@@ -186,7 +203,7 @@
.filter { itemInfo -> !itemInfo.isPredictedItem }
.toMutableList()
- if (areDesktopTasksVisible && canShowRunningApps) {
+ if (showDesktopTasks && canShowRunningApps) {
shownHotseatItems =
updateHotseatItemsFromRunningTasks(
getOrderedAndWrappedDesktopTasks(),
@@ -199,30 +216,35 @@
return shownHotseatItems.toTypedArray()
}
- private fun getOrderedAndWrappedDesktopTasks(): List<GroupTask> {
+ private fun getOrderedAndWrappedDesktopTasks(): List<SingleTask> {
val tasks = desktopTask?.tasks ?: emptyList()
- // Kind of hacky, we wrap each single task in the Desktop as a GroupTask.
+ // We wrap each task in the Desktop as a `SingleTask`.
val orderFromId = orderedRunningTaskIds.withIndex().associate { (index, id) -> id to index }
val sortedTasks = tasks.sortedWith(compareBy(nullsLast()) { orderFromId[it.key.id] })
- return sortedTasks.map { GroupTask(it) }
+ return sortedTasks.map { SingleTask(it) }
}
private fun reloadRecentTasksIfNeeded() {
if (!recentsModel.isTaskListValid(taskListChangeId)) {
taskListChangeId =
- recentsModel.getTasks { tasks ->
- allRecentTasks = tasks
- val oldRunningTaskdIds = runningTaskIds
- val oldMinimizedTaskIds = minimizedTaskIds
- desktopTask = allRecentTasks.filterIsInstance<DesktopTask>().firstOrNull()
- val runningTasksChanged = oldRunningTaskdIds != runningTaskIds
- val minimizedTasksChanged = oldMinimizedTaskIds != minimizedTaskIds
- if (
- onRecentsOrHotseatChanged() || runningTasksChanged || minimizedTasksChanged
- ) {
- controllers.taskbarViewController.commitRunningAppsToUI()
- }
- }
+ recentsModel.getTasks(
+ { tasks ->
+ allRecentTasks = tasks
+ val oldRunningTaskdIds = runningTaskIds
+ val oldMinimizedTaskIds = minimizedTaskIds
+ desktopTask = allRecentTasks.filterIsInstance<DesktopTask>().firstOrNull()
+ val runningTasksChanged = oldRunningTaskdIds != runningTaskIds
+ val minimizedTasksChanged = oldMinimizedTaskIds != minimizedTaskIds
+ if (
+ onRecentsOrHotseatChanged() ||
+ runningTasksChanged ||
+ minimizedTasksChanged
+ ) {
+ controllers.taskbarViewController.commitRunningAppsToUI()
+ }
+ },
+ RecentsFilterState.EMPTY_FILTER,
+ )
}
}
@@ -235,7 +257,7 @@
val oldShownTasks = shownTasks
orderedRunningTaskIds = updateOrderedRunningTaskIds()
shownTasks =
- if (controllers.taskbarDesktopModeController.areDesktopTasksVisible) {
+ if (controllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar()) {
computeShownRunningTasks()
} else {
computeShownRecentTasks()
@@ -266,7 +288,7 @@
}
private fun updateOrderedRunningTaskIds(): MutableList<Int> {
- val desktopTasksAsList = getOrderedAndWrappedDesktopTasks().flatMap { it.tasks }
+ val desktopTasksAsList = getOrderedAndWrappedDesktopTasks().map { it.task }
val desktopTaskIds = desktopTasksAsList.map { it.key.id }
var newOrder =
orderedRunningTaskIds
@@ -291,42 +313,43 @@
val newShownTasks =
if (Flags.enableMultiInstanceMenuTaskbar()) {
val deduplicatedDesktopTasks =
- desktopTasks.distinctBy { Pair(it.task1.key.packageName, it.task1.key.userId) }
+ desktopTasks.distinctBy { Pair(it.task.key.packageName, it.task.key.userId) }
shownTasks
.filter {
- !it.supportsMultipleTasks() &&
- it.task1.key.id in deduplicatedDesktopTasks.map { it.task1.key.id }
+ it is SingleTask &&
+ it.task.key.id in deduplicatedDesktopTasks.map { it.task.key.id }
}
.toMutableList()
.apply {
addAll(
deduplicatedDesktopTasks.filter { currentTask ->
- val currentTaskKey = currentTask.task1.key
- currentTaskKey.id !in shownTasks.map { it.task1.key.id } &&
+ val currentTaskKey = currentTask.task.key
+ currentTaskKey.id !in shownTaskIds &&
shownHotseatItems.none { hotseatItem ->
- hotseatItem.targetPackage == currentTaskKey.packageName &&
- hotseatItem.user.identifier == currentTaskKey.userId
+ currentTask.containsPackage(
+ hotseatItem.targetPackage,
+ hotseatItem.user.identifier,
+ )
}
}
)
}
} else {
- val desktopTaskIds = desktopTasks.map { it.task1.key.id }
+ val desktopTaskIds = desktopTasks.map { it.task.key.id }
val shownHotseatItemTaskIds =
shownHotseatItems.mapNotNull { it as? TaskItemInfo }.map { it.taskId }
shownTasks
- .filter { !it.supportsMultipleTasks() && it.task1.key.id in desktopTaskIds }
+ .filter { it is SingleTask && it.task.key.id in desktopTaskIds }
.toMutableList()
.apply {
addAll(
desktopTasks.filter { desktopTask ->
- desktopTask.task1.key.id !in
- shownTasks.map { shownTask -> shownTask.task1.key.id }
+ desktopTask.task.key.id !in shownTaskIds
}
)
- removeAll { it.task1.key.id in shownHotseatItemTaskIds }
+ removeAll { it is SingleTask && it.task.key.id in shownHotseatItemTaskIds }
}
}
@@ -351,21 +374,28 @@
groupTasks: List<GroupTask>,
shownHotseatItems: List<ItemInfo>,
): List<GroupTask> {
+ // TODO: b/393476333 - Check the behavior of the Taskbar recents section when empty desks
+ // become supported.
return if (Flags.enableMultiInstanceMenuTaskbar()) {
groupTasks.filter { groupTask ->
- val taskKey = groupTask.task1.key
// Keep tasks that are group tasks or unique package name/user combinations
- groupTask.hasMultipleTasks() ||
- shownHotseatItems.none {
- it.targetPackage == taskKey.packageName &&
- it.user.identifier == taskKey.userId
- }
+ when (groupTask) {
+ is SingleTask ->
+ shownHotseatItems.none {
+ groupTask.containsPackage(it.targetPackage, it.user.identifier)
+ }
+
+ else -> true
+ }
}
} else {
val hotseatPackages = shownHotseatItems.map { it.targetPackage }
groupTasks.filter { groupTask ->
- groupTask.hasMultipleTasks() ||
- !hotseatPackages.contains(groupTask.task1.key.packageName)
+ when (groupTask) {
+ is SingleTask -> hotseatPackages.none { groupTask.containsPackage(it) }
+
+ else -> true
+ }
}
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 502c001..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_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;
@@ -259,8 +258,8 @@
private @Nullable AnimatorSet mAnimator;
private boolean mIsSystemGestureInProgress;
- private boolean mIsImeShowing;
- private boolean mIsImeSwitcherShowing;
+ /** Whether the IME is visible. */
+ private boolean mIsImeVisible;
private final Alarm mTimeoutAlarm = new Alarm();
private boolean mEnableBlockingTimeoutDuringTests = false;
@@ -269,6 +268,8 @@
private final long mTaskbarBackgroundDuration;
private boolean mUserIsNotGoingHome = false;
+ private final boolean mInAppStateAffectsDesktopTasksVisibilityInTaskbar;
+
// Evaluate whether the handle should be stashed
private final LongPredicate mIsStashedPredicate = flags -> {
boolean inApp = hasAnyFlag(flags, FLAGS_IN_APP);
@@ -293,8 +294,17 @@
mSystemUiProxy = SystemUiProxy.INSTANCE.get(activity);
mAccessibilityManager = mActivity.getSystemService(AccessibilityManager.class);
- mTaskbarBackgroundDuration =
- activity.getResources().getInteger(R.integer.taskbar_background_duration);
+ // Taskbar, via `TaskbarDesktopModeController`, depends on `TaskbarStashController` state to
+ // determine whether desktop tasks should be shown because taskbar is pinned on the home
+ // screen for freeform windowing displays. In this case, list of items shown in the taskbar
+ // needs to be updated when in-app state changes.
+ // TODO(b/390665752): Feature to "lock" pinned taskbar to home screen will be superseded by
+ // pinning, in other launcher states, at which point this variable can be removed.
+ mInAppStateAffectsDesktopTasksVisibilityInTaskbar =
+ DisplayController.showLockedTaskbarOnHome(mActivity);
+
+ mTaskbarBackgroundDuration = activity.getResources().getInteger(
+ R.integer.taskbar_background_duration);
if (mActivity.isPhoneMode()) {
mUnstashedHeight = mActivity.getResources().getDimensionPixelSize(
R.dimen.taskbar_phone_size);
@@ -1109,7 +1119,7 @@
*/
@VisibleForTesting
long getTaskbarStashStartDelayForIme() {
- if (mIsImeShowing) {
+ if (mIsImeVisible) {
// Only delay when IME is exiting, not entering.
return 0;
}
@@ -1135,8 +1145,7 @@
updateStateForFlag(FLAG_STASHED_DEVICE_LOCKED,
SystemUiFlagUtils.isLocked(systemUiStateFlags));
- mIsImeShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SHOWING);
- mIsImeSwitcherShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SWITCHER_SHOWING);
+ mIsImeVisible = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_VISIBLE);
if (updateStateForFlag(FLAG_STASHED_IME, shouldStashForIme())) {
animDuration = TASKBAR_STASH_DURATION_FOR_IME;
startDelay = getTaskbarStashStartDelayForIme();
@@ -1152,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.
@@ -1188,7 +1197,7 @@
return false;
}
- return mIsImeShowing || mIsImeSwitcherShowing;
+ return mIsImeVisible;
}
/**
@@ -1237,6 +1246,10 @@
if (hasAnyFlag(changedFlags, FLAG_IN_OVERVIEW | FLAG_IN_APP)) {
mControllers.runAfterInit(() -> mControllers.taskbarInsetsController
.onTaskbarOrBubblebarWindowHeightOrInsetsChanged());
+ if (mInAppStateAffectsDesktopTasksVisibilityInTaskbar) {
+ mControllers.runAfterInit(
+ () -> mControllers.taskbarViewController.commitRunningAppsToUI());
+ }
}
mActivity.applyForciblyShownFlagWhileTransientTaskbarUnstashed(!isStashedInApp());
}
@@ -1359,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 + "\tmIsImeSwitcherShowing=" + mIsImeSwitcherShowing);
+ pw.println(prefix + "\tmIsImeVisible=" + mIsImeVisible);
}
private static String getStateString(long flags) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index f29f95d..e5d642d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -39,7 +39,7 @@
import com.android.launcher3.taskbar.bubbles.BubbleBarController;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.SplitConfigurationOptions;
-import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SplitTask;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskContainer;
import com.android.quickstep.views.TaskView;
@@ -332,7 +332,7 @@
* Launches the given task in split-screen.
*/
public void launchSplitTasks(
- @NonNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition) { }
+ @NonNull SplitTask splitTask, @Nullable RemoteTransition remoteTransition) { }
/**
* Returns the matching view (if any) in the taskbar.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index e4e97e5..a59c9e3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -26,14 +26,11 @@
import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
-import static java.util.function.Predicate.not;
-
import android.content.Context;
import android.content.res.Resources;
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 +38,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;
@@ -70,6 +66,7 @@
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SingleTask;
import com.android.quickstep.views.TaskViewType;
import com.android.systemui.shared.recents.model.Task;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
@@ -311,16 +308,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) {
@@ -334,21 +321,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);
}
@@ -419,7 +397,7 @@
.filter(Objects::nonNull)
.toArray(ItemInfo[]::new);
// TODO(b/343289567 and b/316004172): support app pairs and desktop mode.
- recentTasks = recentTasks.stream().filter(not(GroupTask::supportsMultipleTasks)).toList();
+ recentTasks = recentTasks.stream().filter(it -> it instanceof SingleTask).toList();
if (taskbarRecentsLayoutTransition()) {
updateItemsWithLayoutTransition(hotseatItemInfos, recentTasks);
@@ -657,9 +635,10 @@
final Set<GroupTask> recentTasksSet = new ArraySet<>(recentTasks);
for (GroupTask task : recentTasks) {
if (mTaskbarOverflowView != null && overflownTasks != null
- && overflownTasks.size() < itemsToAddToOverflow) {
+ && overflownTasks.size() < itemsToAddToOverflow
+ && task instanceof SingleTask singleTask) {
// TODO(b/343289567 and b/316004172): support app pairs and desktop mode.
- overflownTasks.add(task.task1);
+ overflownTasks.add(singleTask.getTask());
if (overflownTasks.size() == itemsToAddToOverflow) {
mTaskbarOverflowView.setItems(overflownTasks);
}
@@ -669,7 +648,7 @@
// Replace any Recent views with the appropriate type if it's not already that type.
final int expectedLayoutResId;
boolean isCollection = false;
- if (task.supportsMultipleTasks()) {
+ if (!(task instanceof SingleTask)) {
if (task.taskViewType == TaskViewType.DESKTOP) {
// TODO(b/316004172): use Desktop tile layout.
expectedLayoutResId = -1;
@@ -733,18 +712,22 @@
&& tagClass.isInstance(getChildAt(mNextViewIndex).getTag());
}
- /** Binds the GroupTask to the BubbleTextView to be ready to present to the user. */
+ /** Binds the SingleTask to the BubbleTextView to be ready to present to the user. */
public void applyGroupTaskToBubbleTextView(BubbleTextView btv, GroupTask groupTask) {
- // TODO(b/343289567): support app pairs.
- Task task1 = groupTask.task1;
+ if (!(groupTask instanceof SingleTask singleTask)) {
+ // TODO(b/343289567 and b/316004172): support app pairs and desktop mode.
+ return;
+ }
+
+ Task task = singleTask.getTask();
// TODO(b/344038728): use FastBitmapDrawable instead of Drawable, to get disabled state
// while dragging.
- Drawable taskIcon = groupTask.task1.icon;
+ Drawable taskIcon = task.icon;
if (taskIcon != null) {
taskIcon = taskIcon.getConstantState().newDrawable().mutate();
}
- btv.applyIconAndLabel(taskIcon, task1.titleDescription);
- btv.setTag(groupTask);
+ btv.applyIconAndLabel(taskIcon, task.titleDescription);
+ btv.setTag(singleTask);
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 0f05887..cbc5d3d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -92,6 +92,7 @@
import com.android.launcher3.util.MultiTranslateDelegate;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SingleTask;
import com.android.systemui.shared.recents.model.Task;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
@@ -739,9 +740,9 @@
return mControllers.taskbarRecentAppsController.getRunningAppState(
itemInfo.getTaskId());
}
- if (tag instanceof GroupTask groupTask && !groupTask.hasMultipleTasks()) {
+ if (tag instanceof SingleTask singleTask) {
return mControllers.taskbarRecentAppsController.getRunningAppState(
- groupTask.task1.key.id);
+ singleTask.getTask().key.id);
}
return BubbleTextView.RunningAppState.NOT_RUNNING;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index 7d39bf8..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_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;
@@ -34,6 +33,7 @@
import android.os.SystemProperties;
import android.util.ArrayMap;
import android.util.Log;
+import android.widget.Toast;
import com.android.launcher3.taskbar.TaskbarSharedState;
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController;
@@ -91,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_SHOWING;
+ | SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
private static final long MASK_HIDE_HANDLE_VIEW = SYSUI_STATE_BOUNCER_SHOWING
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
@@ -238,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();
}
@@ -589,6 +588,18 @@
});
}
+ @Override
+ public void onDragItemOverBubbleBarDragZone(BubbleBarLocation location) {
+ //TODO(b/388894910): add meaningful implementation
+ MAIN_EXECUTOR.execute(() ->
+ Toast.makeText(mContext, "onDragItemOver " + location, Toast.LENGTH_SHORT).show());
+ }
+
+ @Override
+ public void onItemDraggedOutsideBubbleBarDropZone() {
+
+ }
+
/** Notifies WMShell to show the expanded view. */
void showExpandedView() {
mSystemUiProxy.showExpandedView();
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/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 58ebc50..23f4f67 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -23,6 +23,7 @@
import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.internal.jank.Cuj.CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE;
+import static com.android.launcher3.Flags.enableExpressiveDismissTaskMotion;
import static com.android.launcher3.Flags.enableUnfoldStateAnimation;
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.PENDING_SPLIT_SELECT_INFO;
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE;
@@ -64,7 +65,6 @@
import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback;
import static com.android.quickstep.util.SplitAnimationTimings.TABLET_HOME_TO_SPLIT;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -149,7 +149,10 @@
import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.QuickSwitchTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
-import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewDismissTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewLaunchTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewRecentsTouchContext;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchControllerDeprecated;
import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.TwoButtonNavbarTouchController;
import com.android.launcher3.util.ActivityOptionsWrapper;
@@ -175,10 +178,10 @@
import com.android.quickstep.TouchInteractionService.TISBinder;
import com.android.quickstep.util.ActiveGestureProtoLogProxy;
import com.android.quickstep.util.AsyncClockEventDelegate;
-import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.LauncherUnfoldAnimationController;
import com.android.quickstep.util.QuickstepOnboardingPrefs;
import com.android.quickstep.util.SplitSelectStateController;
+import com.android.quickstep.util.SplitTask;
import com.android.quickstep.util.SplitToWorkspaceController;
import com.android.quickstep.util.SplitWithKeyboardShortcutController;
import com.android.quickstep.util.TISBindHelper;
@@ -265,6 +268,25 @@
private final OverviewChangeListener mOverviewChangeListener = this::onOverviewTargetChanged;
+ private final TaskViewRecentsTouchContext mTaskViewRecentsTouchContext =
+ new TaskViewRecentsTouchContext() {
+ @Override
+ public boolean isRecentsInteractive() {
+ return isInState(OVERVIEW) || isInState(OVERVIEW_MODAL_TASK);
+ }
+
+ @Override
+ public boolean isRecentsModal() {
+ return isInState(OVERVIEW_MODAL_TASK);
+ }
+
+ @Override
+ public void onUserControlledAnimationCreated(
+ AnimatorPlaybackController animController) {
+ getStateManager().setCurrentUserControlledAnimation(animController);
+ }
+ };
+
public static QuickstepLauncher getLauncher(Context context) {
return fromContext(context);
}
@@ -438,7 +460,7 @@
protected void onItemClicked(View view) {
if (!mSplitToWorkspaceController.handleSecondAppSelectionForSplit(view)) {
- QuickstepLauncher.super.getItemOnClickListener().onClick(view);
+ super.getItemOnClickListener().onClick(view);
}
}
@@ -665,7 +687,12 @@
list.add(new StatusBarTouchController(this));
}
- list.add(new LauncherTaskViewController(this));
+ if (enableExpressiveDismissTaskMotion()) {
+ list.add(new TaskViewLaunchTouchController<>(this, mTaskViewRecentsTouchContext));
+ list.add(new TaskViewDismissTouchController<>(this, mTaskViewRecentsTouchContext));
+ } else {
+ list.add(new TaskViewTouchControllerDeprecated<>(this, mTaskViewRecentsTouchContext));
+ }
return list.toArray(new TouchController[list.size()]);
}
@@ -706,6 +733,9 @@
final boolean ret = super.initDeviceProfile(idp);
mDeviceProfile.isPredictiveBackSwipe =
getApplicationInfo().isOnBackInvokedCallbackEnabled();
+ if (ret) {
+ SystemUiProxy.INSTANCE.get(this).setLauncherAppIconSize(mDeviceProfile.iconSizePx);
+ }
return ret;
}
@@ -1135,9 +1165,9 @@
.getHotseatTranslationXForNavBar(this, isBubblesOnLeft);
}
if (isBubbleBarEnabled()
- && mDeviceProfile.shouldAdjustHotseatForBubbleBar(getContext(), hasBubbles())) {
+ && mDeviceProfile.shouldAdjustHotseatForBubbleBar(asContext(), hasBubbles())) {
translationX += (int) mDeviceProfile
- .getHotseatAdjustedTranslation(getContext(), itemInfo.cellX);
+ .getHotseatAdjustedTranslation(asContext(), itemInfo.cellX);
}
return translationX;
}
@@ -1212,6 +1242,7 @@
}
}
+ @NonNull
@Override
public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) {
ActivityOptionsWrapper activityOptions = mAppTransitionManager.getActivityLaunchOptions(
@@ -1342,12 +1373,6 @@
}
@Override
- protected void onDeviceProfileInitiated() {
- super.onDeviceProfileInitiated();
- SystemUiProxy.INSTANCE.get(this).setLauncherAppIconSize(mDeviceProfile.iconSizePx);
- }
-
- @Override
public void dispatchDeviceProfileChanged() {
super.dispatchDeviceProfileChanged();
Trace.instantForTrack(TRACE_TAG_APP, "QuickstepLauncher#DeviceProfileChanged",
@@ -1360,33 +1385,20 @@
}
/**
- * Launches the given {@link GroupTask} in splitscreen.
+ * Launches the given {@link SplitTask} in splitscreen.
*/
public void launchSplitTasks(
- @NonNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition) {
- // SplitBounds can be null if coming from Taskbar launch.
- final boolean firstTaskIsLeftTopTask = isFirstTaskLeftTopTask(groupTask);
- // task2 should never be null when calling this method. Allow a crash to catch invalid calls
- Task task1 = firstTaskIsLeftTopTask ? groupTask.task1 : groupTask.task2;
- Task task2 = firstTaskIsLeftTopTask ? groupTask.task2 : groupTask.task1;
- mSplitSelectStateController.launchExistingSplitPair(
- null /* launchingTaskView */,
- task1.key.id,
- task2.key.id,
+ @NonNull SplitTask splitTask, @Nullable RemoteTransition remoteTransition) {
+ mSplitSelectStateController.launchExistingSplitPair(null /* launchingTaskView */,
+ splitTask.getTopLeftTask().key.id,
+ splitTask.getBottomRightTask().key.id,
SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
/* callback= */ success -> mSplitSelectStateController.resetState(),
/* freezeTaskList= */ false,
- groupTask.mSplitBounds == null
- ? SNAP_TO_2_50_50
- : groupTask.mSplitBounds.snapPosition,
+ splitTask.getSplitBounds().snapPosition,
remoteTransition);
}
- private static boolean isFirstTaskLeftTopTask(@NonNull GroupTask groupTask) {
- return groupTask.mSplitBounds == null
- || groupTask.mSplitBounds.leftTopTaskId == groupTask.task1.key.id;
- }
-
/**
* Launches two apps as an app pair.
*/
@@ -1442,29 +1454,6 @@
mBubbleBarLocation = bubbleBarLocation;
}
- private static final class LauncherTaskViewController extends
- TaskViewTouchController<QuickstepLauncher> {
-
- LauncherTaskViewController(QuickstepLauncher activity) {
- super(activity);
- }
-
- @Override
- protected boolean isRecentsInteractive() {
- return mContainer.isInState(OVERVIEW) || mContainer.isInState(OVERVIEW_MODAL_TASK);
- }
-
- @Override
- protected boolean isRecentsModal() {
- return mContainer.isInState(OVERVIEW_MODAL_TASK);
- }
-
- @Override
- protected void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
- mContainer.getStateManager().setCurrentUserControlledAnimation(animController);
- }
- }
-
@Override
public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
super.dump(prefix, fd, writer, args);
@@ -1519,4 +1508,9 @@
public void setCanShowAllAppsEducationView(boolean canShowAllAppsEducationView) {
mCanShowAllAppsEducationView = canShowAllAppsEducationView;
}
+
+ @Override
+ public void returnToHomescreen() {
+ getStateManager().goToState(LauncherState.NORMAL);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt b/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
index 2e2d7cc..6e901ee 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
@@ -144,6 +144,14 @@
override fun isNonResizeableActivity(lai: LauncherActivityInfo) =
lai.activityInfo.resizeMode == ActivityInfo.RESIZE_MODE_UNRESIZEABLE
+ override fun supportsMultiInstance(lai: LauncherActivityInfo) : Boolean {
+ return try {
+ super.supportsMultiInstance(lai) || lai.supportsMultiInstance()
+ } catch (e: Exception) {
+ false
+ }
+ }
+
/**
* Starts an Activity which can be used to set this Launcher as the HOME app, via a consent
* screen. In case the consent screen cannot be shown, or the user does not set current Launcher
@@ -192,4 +200,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/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index dae63af..10513c0 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -48,8 +48,7 @@
}
@Override
- public <DEVICE_PROFILE_CONTEXT extends Context & ActivityContext>
- int getTransitionDuration(DEVICE_PROFILE_CONTEXT context, boolean isToState) {
+ public int getTransitionDuration(ActivityContext context, boolean isToState) {
return isToState
? context.getDeviceProfile().allAppsOpenDuration
: context.getDeviceProfile().allAppsCloseDuration;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
index 932d241..80fc5fa 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
@@ -17,12 +17,12 @@
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
-import android.content.Context;
import android.graphics.Rect;
import com.android.launcher3.Flags;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.views.RecentsView;
/**
@@ -39,7 +39,7 @@
}
@Override
- public int getTransitionDuration(Context launcher, boolean isToState) {
+ public int getTransitionDuration(ActivityContext launcher, boolean isToState) {
return 300;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 5c16a62..15216fe 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -29,6 +29,7 @@
import com.android.launcher3.R;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.Themes;
+import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.util.BaseDepthController;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.views.RecentsView;
@@ -62,10 +63,10 @@
}
@Override
- public int getTransitionDuration(Context context, boolean isToState) {
+ public int getTransitionDuration(ActivityContext context, boolean isToState) {
if (isToState) {
// In gesture modes, overview comes in all the way from the side, so give it more time.
- return DisplayController.getNavigationMode(context).hasGestures
+ return DisplayController.getNavigationMode(context.asContext()).hasGestures
? OVERVIEW_SLIDE_IN_DURATION
: OVERVIEW_POP_IN_DURATION;
} else {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
index 3ae221b..2631fbf 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
@@ -16,9 +16,8 @@
package com.android.launcher3.uioverrides.states;
-import android.content.Context;
-
import com.android.launcher3.Launcher;
+import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.util.SplitAnimationTimings;
import com.android.quickstep.views.RecentsView;
@@ -43,11 +42,10 @@
}
@Override
- public int getTransitionDuration(Context context, boolean isToState) {
- boolean isTablet = ((Launcher) context).getDeviceProfile().isTablet;
- if (isToState && isTablet) {
+ public int getTransitionDuration(ActivityContext context, boolean isToState) {
+ if (isToState && context.getDeviceProfile().isTablet) {
return SplitAnimationTimings.TABLET_ENTER_DURATION;
- } else if (isToState && !isTablet) {
+ } else if (isToState) {
return SplitAnimationTimings.PHONE_ENTER_DURATION;
} else {
return SplitAnimationTimings.ABORT_DURATION;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 49a5ac5..05d12c3 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)
+ //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/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt
new file mode 100644
index 0000000..99b962b
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt
@@ -0,0 +1,212 @@
+/*
+ * 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.uioverrides.touchcontrollers
+
+import android.content.Context
+import android.view.MotionEvent
+import androidx.dynamicanimation.animation.SpringAnimation
+import com.android.app.animation.Interpolators.DECELERATE
+import com.android.launcher3.AbstractFloatingView
+import com.android.launcher3.Utilities.EDGE_NAV_BAR
+import com.android.launcher3.Utilities.boundToRange
+import com.android.launcher3.Utilities.isRtl
+import com.android.launcher3.Utilities.mapToRange
+import com.android.launcher3.touch.SingleAxisSwipeDetector
+import com.android.launcher3.util.TouchController
+import com.android.quickstep.views.RecentsView
+import com.android.quickstep.views.RecentsViewContainer
+import com.android.quickstep.views.TaskView
+import kotlin.math.abs
+import kotlin.math.sign
+
+/** Touch controller for handling task view card dismiss swipes */
+class TaskViewDismissTouchController<CONTAINER>(
+ private val container: CONTAINER,
+ private val taskViewRecentsTouchContext: TaskViewRecentsTouchContext,
+) : TouchController, SingleAxisSwipeDetector.Listener where
+CONTAINER : Context,
+CONTAINER : RecentsViewContainer {
+ private val recentsView: RecentsView<*, *> = container.getOverviewPanel()
+ private val detector: SingleAxisSwipeDetector =
+ SingleAxisSwipeDetector(
+ container as Context,
+ this,
+ recentsView.pagedOrientationHandler.upDownSwipeDirection,
+ )
+ private val isRtl = isRtl(container.resources)
+
+ private var taskBeingDragged: TaskView? = null
+ private var springAnimation: SpringAnimation? = null
+ private var dismissLength: Int = 0
+ private var verticalFactor: Int = 0
+ private var initialDisplacement: Float = 0f
+
+ private fun canInterceptTouch(ev: MotionEvent): Boolean =
+ when {
+ // Don't intercept swipes on the nav bar, as user might be trying to go home during a
+ // task dismiss animation.
+ (ev.edgeFlags and EDGE_NAV_BAR) != 0 -> {
+ false
+ }
+
+ // Floating views that a TouchController should not try to intercept touches from.
+ AbstractFloatingView.getTopOpenViewWithType(
+ container,
+ AbstractFloatingView.TYPE_TOUCH_CONTROLLER_NO_INTERCEPT,
+ ) != null -> false
+
+ // Disable swiping if the task overlay is modal.
+ taskViewRecentsTouchContext.isRecentsModal -> {
+ false
+ }
+
+ else -> taskViewRecentsTouchContext.isRecentsInteractive
+ }
+
+ override fun onControllerInterceptTouchEvent(ev: MotionEvent): Boolean {
+ if ((ev.action == MotionEvent.ACTION_UP || ev.action == MotionEvent.ACTION_CANCEL)) {
+ clearState()
+ }
+ if (ev.action == MotionEvent.ACTION_DOWN) {
+ if (!onActionDown(ev)) {
+ return false
+ }
+ }
+
+ onControllerTouchEvent(ev)
+ return detector.isDraggingState && detector.wasInitialTouchPositive()
+ }
+
+ override fun onControllerTouchEvent(ev: MotionEvent?): Boolean = detector.onTouchEvent(ev)
+
+ private fun onActionDown(ev: MotionEvent): Boolean {
+ springAnimation?.cancel()
+ if (!canInterceptTouch(ev)) {
+ return false
+ }
+
+ taskBeingDragged =
+ recentsView.taskViews
+ .firstOrNull {
+ recentsView.isTaskViewVisible(it) && container.dragLayer.isEventOverView(it, ev)
+ }
+ ?.also {
+ dismissLength = recentsView.pagedOrientationHandler.getSecondaryDimension(it)
+ verticalFactor =
+ recentsView.pagedOrientationHandler.secondaryTranslationDirectionFactor
+ }
+
+ detector.setDetectableScrollConditions(
+ recentsView.pagedOrientationHandler.getUpDirection(isRtl),
+ /* ignoreSlop = */ false,
+ )
+
+ return true
+ }
+
+ override fun onDragStart(start: Boolean, startDisplacement: Float) {
+ val taskBeingDragged = taskBeingDragged ?: return
+
+ initialDisplacement =
+ taskBeingDragged.secondaryDismissTranslationProperty.get(taskBeingDragged)
+
+ // Add a tiny bit of translation Z, so that it draws on top of other views. This is relevant
+ // (e.g.) when we dismiss a task by sliding it upward: if there is a row of icons above, we
+ // want the dragged task to stay above all other views.
+ taskBeingDragged.translationZ = 0.1f
+ }
+
+ override fun onDrag(displacement: Float): Boolean {
+ val taskBeingDragged = taskBeingDragged ?: return false
+ val currentDisplacement = displacement + initialDisplacement
+ val boundedDisplacement =
+ boundToRange(abs(currentDisplacement), 0f, dismissLength.toFloat())
+ // When swiping below origin, allow slight undershoot to simulate resisting the movement.
+ val totalDisplacement =
+ if (isDisplacementPositiveDirection(currentDisplacement))
+ boundedDisplacement * sign(currentDisplacement)
+ else
+ mapToRange(
+ boundedDisplacement,
+ 0f,
+ dismissLength.toFloat(),
+ 0f,
+ DISMISS_MAX_UNDERSHOOT,
+ DECELERATE,
+ )
+ taskBeingDragged.secondaryDismissTranslationProperty.setValue(
+ taskBeingDragged,
+ totalDisplacement,
+ )
+ if (taskBeingDragged.isRunningTask && recentsView.enableDrawingLiveTile) {
+ recentsView.runActionOnRemoteHandles { remoteTargetHandle ->
+ remoteTargetHandle.taskViewSimulator.taskSecondaryTranslation.value =
+ totalDisplacement
+ }
+ recentsView.redrawLiveTile()
+ }
+ return true
+ }
+
+ override fun onDragEnd(velocity: Float) {
+ val taskBeingDragged = taskBeingDragged ?: return
+
+ val currentDisplacement =
+ taskBeingDragged.secondaryDismissTranslationProperty.get(taskBeingDragged)
+ if (currentDisplacement == 0f) {
+ clearState()
+ return
+ }
+ val isBeyondDismissThreshold =
+ abs(currentDisplacement) > abs(DISMISS_THRESHOLD_FRACTION * dismissLength)
+ val isFlingingTowardsDismiss = detector.isFling(velocity) && velocity < 0
+ val isFlingingTowardsRestState = detector.isFling(velocity) && velocity > 0
+ val isDismissing =
+ isFlingingTowardsDismiss || (isBeyondDismissThreshold && !isFlingingTowardsRestState)
+ springAnimation =
+ recentsView
+ .createTaskDismissSettlingSpringAnimation(
+ taskBeingDragged,
+ velocity,
+ isDismissing,
+ detector,
+ dismissLength,
+ this::clearState,
+ )
+ .apply {
+ animateToFinalPosition(
+ if (isDismissing) (dismissLength * verticalFactor).toFloat() else 0f
+ )
+ }
+ }
+
+ // Returns if the current task being dragged is towards "positive" (e.g. dismissal).
+ private fun isDisplacementPositiveDirection(displacement: Float): Boolean =
+ sign(displacement) == sign(verticalFactor.toFloat())
+
+ private fun clearState() {
+ detector.finishedScrolling()
+ detector.setDetectableScrollConditions(0, false)
+ taskBeingDragged?.translationZ = 0f
+ taskBeingDragged = null
+ springAnimation = null
+ }
+
+ companion object {
+ private const val DISMISS_THRESHOLD_FRACTION = 0.5f
+ private const val DISMISS_MAX_UNDERSHOOT = 25f
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewLaunchTouchController.kt b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewLaunchTouchController.kt
new file mode 100644
index 0000000..c740dad
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewLaunchTouchController.kt
@@ -0,0 +1,201 @@
+/*
+ * 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.uioverrides.touchcontrollers
+
+import android.content.Context
+import android.graphics.Rect
+import android.view.MotionEvent
+import com.android.app.animation.Interpolators.ZOOM_IN
+import com.android.launcher3.AbstractFloatingView
+import com.android.launcher3.LauncherAnimUtils
+import com.android.launcher3.Utilities.EDGE_NAV_BAR
+import com.android.launcher3.Utilities.boundToRange
+import com.android.launcher3.Utilities.isRtl
+import com.android.launcher3.anim.AnimatorPlaybackController
+import com.android.launcher3.touch.BaseSwipeDetector
+import com.android.launcher3.touch.SingleAxisSwipeDetector
+import com.android.launcher3.util.DisplayController
+import com.android.launcher3.util.FlingBlockCheck
+import com.android.launcher3.util.TouchController
+import com.android.quickstep.views.RecentsView
+import com.android.quickstep.views.RecentsViewContainer
+import com.android.quickstep.views.TaskView
+import kotlin.math.abs
+
+/** Touch controller which handles dragging task view cards for launch. */
+class TaskViewLaunchTouchController<CONTAINER>(
+ private val container: CONTAINER,
+ private val taskViewRecentsTouchContext: TaskViewRecentsTouchContext,
+) : TouchController, SingleAxisSwipeDetector.Listener where
+CONTAINER : Context,
+CONTAINER : RecentsViewContainer {
+ private val tempRect = Rect()
+ private val flingBlockCheck = FlingBlockCheck()
+ private val recentsView: RecentsView<*, *> = container.getOverviewPanel()
+ private val detector: SingleAxisSwipeDetector =
+ SingleAxisSwipeDetector(
+ container as Context,
+ this,
+ recentsView.pagedOrientationHandler.upDownSwipeDirection,
+ )
+ private val isRtl = isRtl(container.resources)
+
+ private var taskBeingDragged: TaskView? = null
+ private var launchEndDisplacement: Float = 0f
+ private var playbackController: AnimatorPlaybackController? = null
+ private var verticalFactor: Int = 0
+
+ private fun canTaskLaunchTaskView(taskView: TaskView?) =
+ taskView != null &&
+ taskView === recentsView.currentPageTaskView &&
+ DisplayController.getNavigationMode(container).hasGestures &&
+ (!recentsView.showAsGrid() || taskView.isLargeTile) &&
+ recentsView.isTaskInExpectedScrollPosition(taskView)
+
+ private fun canInterceptTouch(ev: MotionEvent): Boolean =
+ when {
+ // Don't intercept swipes on the nav bar, as user might be trying to go home during a
+ // task dismiss animation.
+ (ev.edgeFlags and EDGE_NAV_BAR) != 0 -> {
+ false
+ }
+
+ // Floating views that a TouchController should not try to intercept touches from.
+ AbstractFloatingView.getTopOpenViewWithType(
+ container,
+ AbstractFloatingView.TYPE_TOUCH_CONTROLLER_NO_INTERCEPT,
+ ) != null -> {
+ false
+ }
+
+ // Disable swiping if the task overlay is modal.
+ taskViewRecentsTouchContext.isRecentsModal -> {
+ false
+ }
+
+ else -> taskViewRecentsTouchContext.isRecentsInteractive
+ }
+
+ override fun onControllerInterceptTouchEvent(ev: MotionEvent): Boolean {
+ if (
+ (ev.action == MotionEvent.ACTION_UP || ev.action == MotionEvent.ACTION_CANCEL) &&
+ playbackController == null
+ ) {
+ clearState()
+ }
+ if (ev.action == MotionEvent.ACTION_DOWN) {
+ if (!onActionDown(ev)) {
+ clearState()
+ return false
+ }
+ }
+ onControllerTouchEvent(ev)
+ return detector.isDraggingState && !detector.wasInitialTouchPositive()
+ }
+
+ override fun onControllerTouchEvent(ev: MotionEvent) = detector.onTouchEvent(ev)
+
+ private fun onActionDown(ev: MotionEvent): Boolean {
+ if (!canInterceptTouch(ev)) {
+ return false
+ }
+ taskBeingDragged =
+ recentsView.taskViews
+ .firstOrNull {
+ recentsView.isTaskViewVisible(it) && container.dragLayer.isEventOverView(it, ev)
+ }
+ ?.also {
+ verticalFactor =
+ recentsView.pagedOrientationHandler.secondaryTranslationDirectionFactor
+ }
+ if (!canTaskLaunchTaskView(taskBeingDragged)) {
+ return false
+ }
+ detector.setDetectableScrollConditions(
+ recentsView.pagedOrientationHandler.getDownDirection(isRtl),
+ /* ignoreSlop = */ false,
+ )
+ return true
+ }
+
+ override fun onDragStart(start: Boolean, startDisplacement: Float) {
+ val taskBeingDragged = taskBeingDragged ?: return
+
+ val secondaryLayerDimension: Int =
+ recentsView.pagedOrientationHandler.getSecondaryDimension(container.getDragLayer())
+ val maxDuration = 2L * secondaryLayerDimension
+ recentsView.clearPendingAnimation()
+ val pendingAnimation =
+ recentsView.createTaskLaunchAnimation(taskBeingDragged, maxDuration, ZOOM_IN)
+ // Since the thumbnail is what is filling the screen, based the end displacement on it.
+ taskBeingDragged.getThumbnailBounds(tempRect, /* relativeToDragLayer= */ true)
+ launchEndDisplacement = (secondaryLayerDimension - tempRect.bottom).toFloat()
+ playbackController =
+ pendingAnimation.createPlaybackController()?.apply {
+ taskViewRecentsTouchContext.onUserControlledAnimationCreated(this)
+ dispatchOnStart()
+ }
+ }
+
+ override fun onDrag(displacement: Float): Boolean {
+ playbackController?.setPlayFraction(
+ boundToRange(displacement / launchEndDisplacement, 0f, 1f)
+ )
+ return true
+ }
+
+ override fun onDragEnd(velocity: Float) {
+ val playbackController = playbackController ?: return
+
+ val isBeyondLaunchThreshold =
+ abs(playbackController.progressFraction) > abs(LAUNCH_THRESHOLD_FRACTION)
+ val isFlingingTowardsLaunch = detector.isFling(velocity) && velocity > 0
+ val isFlingingTowardsRestState = detector.isFling(velocity) && velocity < 0
+ val isLaunching =
+ isFlingingTowardsLaunch || (isBeyondLaunchThreshold && !isFlingingTowardsRestState)
+
+ val progress = playbackController.progressFraction
+ var animationDuration =
+ BaseSwipeDetector.calculateDuration(
+ velocity,
+ if (isLaunching) (1 - progress) else progress,
+ )
+ if (detector.isFling(velocity) && flingBlockCheck.isBlocked && !isLaunching) {
+ animationDuration *= LauncherAnimUtils.blockedFlingDurationFactor(velocity).toLong()
+ }
+
+ playbackController.setEndAction(this::clearState)
+ playbackController.startWithVelocity(
+ container,
+ isLaunching,
+ velocity,
+ launchEndDisplacement,
+ animationDuration,
+ )
+ }
+
+ private fun clearState() {
+ detector.finishedScrolling()
+ detector.setDetectableScrollConditions(0, false)
+ taskBeingDragged = null
+ playbackController = null
+ }
+
+ companion object {
+ private const val LAUNCH_THRESHOLD_FRACTION: Float = 0.5f
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewRecentsTouchContext.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewRecentsTouchContext.java
new file mode 100644
index 0000000..e8d31c1
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewRecentsTouchContext.java
@@ -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.launcher3.uioverrides.touchcontrollers;
+
+import com.android.launcher3.anim.AnimatorPlaybackController;
+
+/** Interface providing context about the RecentsView state to a {@link TaskViewTouchController}. */
+public interface TaskViewRecentsTouchContext {
+ /** Returns whether Recents is interactive for touch. */
+ boolean isRecentsInteractive();
+
+ /** Returns if Recents is showing a single task in a modal way. */
+ boolean isRecentsModal();
+
+ /** Runs when a user controlled animation is created. */
+ default void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchControllerDeprecated.java
similarity index 94%
rename from quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
rename to quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchControllerDeprecated.java
index d622987..b1a36c7 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchControllerDeprecated.java
@@ -49,10 +49,13 @@
/**
* Touch controller for handling task view card swipes
+ *
+ * @deprecated This class will be replaced by the new {@link TaskViewTouchController}.
*/
-public abstract class TaskViewTouchController<CONTAINER extends Context & RecentsViewContainer>
- extends AnimatorListenerAdapter implements TouchController,
- SingleAxisSwipeDetector.Listener {
+@Deprecated
+public class TaskViewTouchControllerDeprecated<
+ CONTAINER extends Context & RecentsViewContainer> extends AnimatorListenerAdapter
+ implements TouchController, SingleAxisSwipeDetector.Listener {
private static final float ANIMATION_PROGRESS_FRACTION_MIDPOINT = 0.5f;
private static final long MIN_TASK_DISMISS_ANIMATION_DURATION = 300;
@@ -65,6 +68,7 @@
VibrationConstants.EFFECT_TEXTURE_TICK;
protected final CONTAINER mContainer;
+ private final TaskViewRecentsTouchContext mTaskViewRecentsTouchContext;
private final SingleAxisSwipeDetector mDetector;
private final RecentsView<?, ?> mRecentsView;
private final Rect mTempRect = new Rect();
@@ -88,8 +92,10 @@
private boolean mIsDismissHapticRunning = false;
- public TaskViewTouchController(CONTAINER container) {
+ public TaskViewTouchControllerDeprecated(CONTAINER container,
+ TaskViewRecentsTouchContext taskViewRecentsTouchContext) {
mContainer = container;
+ mTaskViewRecentsTouchContext = taskViewRecentsTouchContext;
mRecentsView = container.getOverviewPanel();
mIsRtl = Utilities.isRtl(container.getResources());
SingleAxisSwipeDetector.Direction dir =
@@ -117,15 +123,7 @@
mContainer, TYPE_TOUCH_CONTROLLER_NO_INTERCEPT) != null) {
return false;
}
- return isRecentsInteractive();
- }
-
- protected abstract boolean isRecentsInteractive();
-
- /** Is recents view showing a single task in a modal way. */
- protected abstract boolean isRecentsModal();
-
- protected void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
+ return mTaskViewRecentsTouchContext.isRecentsInteractive();
}
@Override
@@ -161,7 +159,7 @@
if (mRecentsView.isTaskViewVisible(taskView) && mContainer.getDragLayer()
.isEventOverView(taskView, ev)) {
// Disable swiping up and down if the task overlay is modal.
- if (isRecentsModal()) {
+ if (mTaskViewRecentsTouchContext.isRecentsModal()) {
mTaskBeingDragged = null;
break;
}
@@ -259,7 +257,7 @@
// Setting this interpolator doesn't affect the visual motion, but is used to determine
// whether we successfully reached the target state in onDragEnd().
mCurrentAnimation.getTarget().setInterpolator(currentInterpolator);
- onUserControlledAnimationCreated(mCurrentAnimation);
+ mTaskViewRecentsTouchContext.onUserControlledAnimationCreated(mCurrentAnimation);
mCurrentAnimation.getTarget().addListener(this);
mCurrentAnimation.dispatchOnStart();
mProgressMultiplier = 1 / mEndDisplacement;
diff --git a/quickstep/src/com/android/launcher3/widget/picker/WidgetCategoryFilter.kt b/quickstep/src/com/android/launcher3/widget/picker/WidgetCategoryFilter.kt
new file mode 100644
index 0000000..69feb4a
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/widget/picker/WidgetCategoryFilter.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.widget.picker
+
+/**
+ * A filter that can be applied on the widgetCategory attribute from appwidget-provider to identify
+ * if the widget can be displayed on a specific widget surface.
+ * - Negative value (e.g. "category_a.inv() and category_b.inv()" excludes the widgets with given
+ * categories.
+ * - Positive value (e.g. "category_a or category_b" includes widgets with those categories.
+ * - 0 means no filter.
+ */
+class WidgetCategoryFilter(val categoryMask: Int) {
+ /** Applies the [categoryMask] to return if the [widgetCategory] matches. */
+ fun matches(widgetCategory: Int): Boolean {
+ return if (categoryMask > 0) { // inclusion filter
+ (widgetCategory and categoryMask) != 0
+ } else if (categoryMask < 0) { // exclusion filter
+ (widgetCategory and categoryMask) == widgetCategory
+ } else {
+ true // no filter
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 8b76ce9..f46f9ae 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -932,7 +932,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 +955,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);
@@ -1490,7 +1490,7 @@
startShift, endShift, duration, interpolator, endTarget, velocityPxPerMs);
}
- private void doLogGesture(GestureEndTarget endTarget, @Nullable TaskView targetTask) {
+ private void doLogGesture(GestureEndTarget endTarget, @Nullable TaskView targetTaskView) {
if (mDp == null || !mDp.isGestureMode) {
// We probably never received an animation controller, skip logging.
return;
@@ -1508,9 +1508,9 @@
case NEW_TASK:
events.add(mLogDirectionUpOrLeft ? LAUNCHER_QUICKSWITCH_LEFT
: LAUNCHER_QUICKSWITCH_RIGHT);
- if (targetTask != null && DesktopModeStatus.canEnterDesktopMode(mContext)
+ if (targetTaskView != null && DesktopModeStatus.canEnterDesktopMode(mContext)
&& DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH.isTrue()) {
- if (targetTask.getType() == TaskViewType.DESKTOP) {
+ if (targetTaskView.getType() == TaskViewType.DESKTOP) {
events.add(LAUNCHER_QUICKSWITCH_ENTER_DESKTOP_MODE);
} else if (mPreviousTaskViewType == TaskViewType.DESKTOP) {
events.add(LAUNCHER_QUICKSWITCH_EXIT_DESKTOP_MODE);
@@ -1527,8 +1527,8 @@
.withInputType(mGestureState.isTrackpadGesture()
? SysUiStatsLog.LAUNCHER_UICHANGED__INPUT_TYPE__TRACKPAD
: SysUiStatsLog.LAUNCHER_UICHANGED__INPUT_TYPE__TOUCH);
- if (targetTask != null) {
- logger.withItemInfo(targetTask.getFirstItemInfo());
+ if (targetTaskView != null) {
+ logger.withItemInfo(targetTaskView.getItemInfo());
}
int pageIndex = endTarget == LAST_TASK || mRecentsView == null
@@ -1709,7 +1709,7 @@
if (mRecentsView != null) {
mRecentsView.onPrepareGestureEndAnimation(null, mGestureState.getEndTarget(),
- getRemoteTaskViewSimulators());
+ mRemoteTargetHandles);
}
} else {
AnimatorSet animatorSet = new AnimatorSet();
@@ -1753,7 +1753,7 @@
mRecentsView.onPrepareGestureEndAnimation(
mGestureState.isHandlingAtomicEvent() ? null : animatorSet,
mGestureState.getEndTarget(),
- getRemoteTaskViewSimulators());
+ mRemoteTargetHandles);
}
animatorSet.setDuration(duration).setInterpolator(interpolator);
animatorSet.start();
@@ -2369,9 +2369,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/HomeVisibilityState.kt b/quickstep/src/com/android/quickstep/HomeVisibilityState.kt
index 241e16d..1345e0b 100644
--- a/quickstep/src/com/android/quickstep/HomeVisibilityState.kt
+++ b/quickstep/src/com/android/quickstep/HomeVisibilityState.kt
@@ -18,6 +18,7 @@
import android.os.RemoteException
import android.util.Log
+import com.android.launcher3.Utilities
import com.android.launcher3.config.FeatureFlags
import com.android.launcher3.util.Executors
import com.android.wm.shell.shared.IHomeTransitionListener.Stub
@@ -41,10 +42,13 @@
transitions?.setHomeTransitionListener(
object : Stub() {
override fun onHomeVisibilityChanged(isVisible: Boolean) {
- Executors.MAIN_EXECUTOR.execute {
- isHomeVisible = isVisible
- listeners.forEach { it.onHomeVisibilityChanged(isVisible) }
- }
+ Utilities.postAsyncCallback(
+ Executors.MAIN_EXECUTOR.handler,
+ {
+ isHomeVisible = isVisible
+ listeners.forEach { it.onHomeVisibilityChanged(isVisible) }
+ },
+ )
}
}
)
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 ee4ee38..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;
@@ -39,17 +40,26 @@
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.quickstep.util.DesktopTask;
import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SingleTask;
+import com.android.quickstep.util.SplitTask;
import com.android.systemui.shared.recents.model.Task;
import com.android.wm.shell.Flags;
import com.android.wm.shell.recents.IRecentTasksListener;
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;
@@ -347,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;
}
@@ -360,22 +369,19 @@
final Task.TaskKey task1Key = new Task.TaskKey(taskInfo1);
final Task task1 = Task.from(task1Key, taskInfo1,
tmpLockedUsers.get(task1Key.userId) /* isLocked */);
- final Task task2;
- final SplitConfigurationOptions.SplitBounds launcherSplitBounds;
if (rawTask.isBaseType(TYPE_SPLIT)) {
final TaskInfo taskInfo2 = rawTask.getBaseGroupedTask().getTaskInfo2();
final Task.TaskKey task2Key = new Task.TaskKey(taskInfo2);
- task2 = Task.from(task2Key, taskInfo2,
+ final Task task2 = Task.from(task2Key, taskInfo2,
tmpLockedUsers.get(task2Key.userId) /* isLocked */);
- launcherSplitBounds =
+ final SplitConfigurationOptions.SplitBounds launcherSplitBounds =
convertShellSplitBoundsToLauncher(
rawTask.getBaseGroupedTask().getSplitBounds());
+ allTasks.add(new SplitTask(task1, task2, launcherSplitBounds));
} else {
- task2 = null;
- launcherSplitBounds = null;
+ allTasks.add(new SingleTask(task1));
}
- allTasks.add(new GroupTask(task1, task2, launcherSplitBounds));
} else {
TaskInfo taskInfo1 = rawTask.getTaskInfo1();
TaskInfo taskInfo2 = rawTask.getTaskInfo2();
@@ -407,33 +413,49 @@
if (taskInfo1.isVisible) {
numVisibleTasks++;
}
- final SplitConfigurationOptions.SplitBounds launcherSplitBounds =
- convertShellSplitBoundsToLauncher(rawTask.getSplitBounds());
- allTasks.add(new GroupTask(task1, task2, launcherSplitBounds));
+ if (task2 != null) {
+ Objects.requireNonNull(rawTask.getSplitBounds());
+ final SplitConfigurationOptions.SplitBounds launcherSplitBounds =
+ convertShellSplitBoundsToLauncher(rawTask.getSplitBounds());
+ allTasks.add(new SplitTask(task1, task2, launcherSplitBounds));
+ } else {
+ allTasks.add(new SingleTask(task1));
+ }
}
}
return allTasks;
}
- private @Nullable DesktopTask createDesktopTask(GroupedTaskInfo recentTaskInfo) {
- ArrayList<Task> tasks = new ArrayList<>(recentTaskInfo.getTaskInfoList().size());
- int[] minimizedTaskIds = recentTaskInfo.getMinimizedTaskIds();
- if (minimizedTaskIds.length == recentTaskInfo.getTaskInfoList().size()) {
- // All Tasks are minimized -> don't create a DesktopTask
- return null;
+ 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));
}
- 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);
- }
- return new DesktopTask(tasks);
}
public void dump(String prefix, PrintWriter writer) {
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 5e8ea37..fca67c3 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -26,6 +26,7 @@
import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_PRE_DELAY;
import static com.android.launcher3.testing.shared.TestProtocol.LAUNCHER_ACTIVITY_STOPPED_MESSAGE;
import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL;
+import static com.android.launcher3.util.WallpaperThemeManager.setWallpaperDependentTheme;
import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely;
import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
import static com.android.quickstep.TaskViewUtils.createRecentsWindowAnimator;
@@ -247,7 +248,6 @@
@Override
public void returnToHomescreen() {
- super.returnToHomescreen();
// TODO(b/137318995) This should go home, but doing so removes freeform windows
}
@@ -261,6 +261,7 @@
}
}
+ @NonNull
@Override
public ActivityOptionsWrapper getActivityLaunchOptions(final View v, @Nullable ItemInfo item) {
if (!(v instanceof TaskView)) {
@@ -371,6 +372,7 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ setWallpaperDependentTheme(this);
mStateManager = new StateManager<>(this, RecentsState.BG_LAUNCHER);
@@ -431,7 +433,6 @@
*/
private void initDeviceProfile() {
mDeviceProfile = createDeviceProfile();
- onDeviceProfileInitiated();
}
@Override
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 d4305a5..090ccdc 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;
@@ -64,12 +64,15 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppComponent;
+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.DisplayController.DisplayInfoChangeListener;
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.TopTaskTracker.CachedTaskInfo;
import com.android.quickstep.util.ActiveGestureLog;
@@ -85,13 +88,14 @@
import com.android.systemui.shared.system.TaskStackChangeListeners;
import java.io.PrintWriter;
-import java.util.ArrayList;
+
+import javax.inject.Inject;
/**
* Manages the state of the system during a swipe up gesture.
*/
-public class RecentsAnimationDeviceState implements DisplayInfoChangeListener, ExclusionListener,
- SafeCloseable {
+@LauncherAppSingleton
+public class RecentsAnimationDeviceState implements DisplayInfoChangeListener, ExclusionListener {
static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode";
@@ -99,8 +103,8 @@
private static final float QUICKSTEP_TOUCH_SLOP_RATIO_TWO_BUTTON = 3f;
private static final float QUICKSTEP_TOUCH_SLOP_RATIO_GESTURAL = 1.414f;
- public static MainThreadInitializedObject<RecentsAnimationDeviceState> INSTANCE =
- new MainThreadInitializedObject<>(RecentsAnimationDeviceState::new);
+ public static DaggerSingletonObject<RecentsAnimationDeviceState> INSTANCE =
+ new DaggerSingletonObject<>(LauncherAppComponent::getRecentsAnimationDeviceState);
private final Context mContext;
private final DisplayController mDisplayController;
@@ -114,8 +118,6 @@
private final boolean mCanImeRenderGesturalNavButtons =
InputMethodService.canImeRenderGesturalNavButtons();
- private final ArrayList<Runnable> mOnDestroyActions = new ArrayList<>();
-
private @SystemUiStateFlags long mSystemUiStateFlags = QuickStepContract.SYSUI_STATE_AWAKE;
private NavigationMode mMode = THREE_BUTTONS;
private NavBarPosition mNavBarPosition;
@@ -134,35 +136,38 @@
private @NonNull Region mExclusionRegion = GestureExclusionManager.EMPTY_REGION;
private boolean mExclusionListenerRegistered;
- private RecentsAnimationDeviceState(Context context) {
- this(context, GestureExclusionManager.INSTANCE);
- }
-
@VisibleForTesting
- RecentsAnimationDeviceState(Context context, GestureExclusionManager exclusionManager) {
+ @Inject
+ RecentsAnimationDeviceState(
+ @ApplicationContext Context context,
+ GestureExclusionManager exclusionManager,
+ DisplayController displayController,
+ ContextualSearchStateManager contextualSearchStateManager,
+ RotationTouchHelper rotationTouchHelper,
+ SettingsCache settingsCache,
+ DaggerSingletonTracker lifeCycle) {
mContext = context;
- mDisplayController = DisplayController.INSTANCE.get(context);
+ mDisplayController = displayController;
mExclusionManager = exclusionManager;
- mContextualSearchStateManager = ContextualSearchStateManager.INSTANCE.get(context);
+ mContextualSearchStateManager = contextualSearchStateManager;
+ mRotationTouchHelper = rotationTouchHelper;
mIsOneHandedModeSupported = SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false);
- mRotationTouchHelper = RotationTouchHelper.INSTANCE.get(context);
// Register for exclusion updates
- runOnDestroy(this::unregisterExclusionListener);
+ lifeCycle.addCloseable(this::unregisterExclusionListener);
// Register for display changes changes
mDisplayController.addChangeListener(this);
onDisplayInfoChanged(context, mDisplayController.getInfo(), CHANGE_ALL);
- runOnDestroy(() -> mDisplayController.removeChangeListener(this));
+ lifeCycle.addCloseable(() -> mDisplayController.removeChangeListener(this));
- SettingsCache settingsCache = SettingsCache.INSTANCE.get(mContext);
if (mIsOneHandedModeSupported) {
Uri oneHandedUri = Settings.Secure.getUriFor(ONE_HANDED_ENABLED);
SettingsCache.OnChangeListener onChangeListener =
enabled -> mIsOneHandedModeEnabled = enabled;
settingsCache.register(oneHandedUri, onChangeListener);
mIsOneHandedModeEnabled = settingsCache.getValue(oneHandedUri);
- runOnDestroy(() -> settingsCache.unregister(oneHandedUri, onChangeListener));
+ lifeCycle.addCloseable(() -> settingsCache.unregister(oneHandedUri, onChangeListener));
} else {
mIsOneHandedModeEnabled = false;
}
@@ -173,14 +178,16 @@
enabled -> mIsSwipeToNotificationEnabled = enabled;
settingsCache.register(swipeBottomNotificationUri, onChangeListener);
mIsSwipeToNotificationEnabled = settingsCache.getValue(swipeBottomNotificationUri);
- runOnDestroy(() -> settingsCache.unregister(swipeBottomNotificationUri, onChangeListener));
+ lifeCycle.addCloseable(
+ () -> settingsCache.unregister(swipeBottomNotificationUri, onChangeListener));
Uri setupCompleteUri = Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE);
mIsUserSetupComplete = settingsCache.getValue(setupCompleteUri, 0);
if (!mIsUserSetupComplete) {
SettingsCache.OnChangeListener userSetupChangeListener = e -> mIsUserSetupComplete = e;
settingsCache.register(setupCompleteUri, userSetupChangeListener);
- runOnDestroy(() -> settingsCache.unregister(setupCompleteUri, userSetupChangeListener));
+ lifeCycle.addCloseable(
+ () -> settingsCache.unregister(setupCompleteUri, userSetupChangeListener));
}
try {
@@ -201,26 +208,19 @@
}
};
TaskStackChangeListeners.getInstance().registerTaskStackListener(mPipListener);
- runOnDestroy(() ->
+ lifeCycle.addCloseable(() ->
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mPipListener));
}
- private void runOnDestroy(Runnable action) {
- mOnDestroyActions.add(action);
- }
-
- @Override
- public void close() {
- for (Runnable r : mOnDestroyActions) {
- r.run();
- }
- }
-
/**
* Adds a listener for the nav mode change, guaranteed to be called after the device state's
* mode has changed.
+ *
+ * @return Added {@link DisplayInfoChangeListener} so that caller is
+ * responsible for removing the listener from {@link DisplayController} to avoid memory leak.
*/
- public void addNavigationModeChangedCallback(Runnable callback) {
+ public DisplayController.DisplayInfoChangeListener addNavigationModeChangedCallback(
+ Runnable callback) {
DisplayController.DisplayInfoChangeListener listener = (context, info, flags) -> {
if ((flags & CHANGE_NAVIGATION_MODE) != 0) {
callback.run();
@@ -228,7 +228,16 @@
};
mDisplayController.addChangeListener(listener);
callback.run();
- runOnDestroy(() -> mDisplayController.removeChangeListener(listener));
+ return listener;
+ }
+
+ /**
+ * Remove the {DisplayController.DisplayInfoChangeListener} added from
+ * {@link #addNavigationModeChangedCallback} when {@link TouchInteractionService} is destroyed.
+ */
+ public void removeDisplayInfoChangeListener(
+ DisplayController.DisplayInfoChangeListener listener) {
+ mDisplayController.removeChangeListener(listener);
}
@Override
@@ -588,7 +597,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/RecentsFilterState.java b/quickstep/src/com/android/quickstep/RecentsFilterState.java
index 70d5696..c4b0f25 100644
--- a/quickstep/src/com/android/quickstep/RecentsFilterState.java
+++ b/quickstep/src/com/android/quickstep/RecentsFilterState.java
@@ -19,6 +19,7 @@
import androidx.annotation.Nullable;
import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.views.TaskViewType;
import com.android.systemui.shared.recents.model.Task;
import java.util.HashMap;
@@ -38,7 +39,7 @@
public static final int MIN_FILTERING_TASK_COUNT = 2;
// default filter that returns true for any input
- public static final Predicate<GroupTask> DEFAULT_FILTER = (groupTask -> true);
+ public static final Predicate<GroupTask> EMPTY_FILTER = (groupTask -> true);
// the package name to filter recent tasks by
@Nullable
@@ -116,14 +117,37 @@
* Returns a predicate for filtering out GroupTasks by package name.
*
* @param packageName package name to filter GroupTasks by
- * if null, Predicate always returns true.
+ * if null, Predicate filters out desktop tasks with no non-minimized tasks.
*/
public static Predicate<GroupTask> getFilter(@Nullable String packageName) {
if (packageName == null) {
- return DEFAULT_FILTER;
+ return getEmptyDesktopTaskFilter();
}
- return (groupTask) -> (groupTask.containsPackage(packageName));
+ return (groupTask) -> (groupTask.containsPackage(packageName)
+ && !isDestopTaskWithMinimizedTasksOnly(groupTask));
+ }
+
+ /**
+ * Returns a predicate that filters out desk tasks that contain no non-minimized desktop tasks.
+ */
+ public static Predicate<GroupTask> getEmptyDesktopTaskFilter() {
+ return (groupTask -> !isDestopTaskWithMinimizedTasksOnly(groupTask));
+ }
+
+ /**
+ * Whether the provided task is a desktop task with no non-minimized tasks - returns true if the
+ * desktop task has no tasks at all.
+ *
+ * @param groupTask The group task to check.
+ */
+ static boolean isDestopTaskWithMinimizedTasksOnly(GroupTask groupTask) {
+ if (groupTask.taskViewType != TaskViewType.DESKTOP) {
+ return false;
+ }
+ return groupTask.getTasks().stream()
+ .filter(task -> !task.isMinimized)
+ .toList().isEmpty();
}
/**
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 6498b7a..87b58e6 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -22,6 +22,7 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
+import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.app.KeyguardManager;
@@ -41,6 +42,7 @@
import com.android.launcher3.graphics.ThemeManager.ThemeChangeListener;
import com.android.launcher3.icons.IconProvider;
import com.android.launcher3.util.Executors.SimpleThreadFactory;
+import com.android.launcher3.util.LockedUserState;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.SafeCloseable;
import com.android.quickstep.recents.data.RecentTasksDataSource;
@@ -63,6 +65,8 @@
import java.util.function.Consumer;
import java.util.function.Predicate;
+import javax.inject.Provider;
+
/**
* Singleton class to load and manage recents model.
*/
@@ -86,15 +90,19 @@
private final TaskIconCache mIconCache;
private final TaskThumbnailCache mThumbnailCache;
private final ComponentCallbacks mCallbacks;
- private final ThemeManager mThemeManager;
private final TaskStackChangeListeners mTaskStackChangeListeners;
private final SafeCloseable mIconChangeCloseable;
+ private final LockedUserState mLockedUserState;
+ private final Provider<ThemeManager> mThemeManagerProvider;
+ private final Runnable mUnlockCallback;
+
private RecentsModel(Context context) {
this(context, new IconProvider(context));
}
+ @SuppressLint("VisibleForTests")
private RecentsModel(Context context, IconProvider iconProvider) {
this(context,
new RecentTasksList(
@@ -107,14 +115,16 @@
new TaskThumbnailCache(context, RECENTS_MODEL_EXECUTOR),
iconProvider,
TaskStackChangeListeners.getInstance(),
- ThemeManager.INSTANCE.get(context));
+ LockedUserState.get(context),
+ () -> ThemeManager.INSTANCE.get(context));
}
@VisibleForTesting
RecentsModel(Context context, RecentTasksList taskList, TaskIconCache iconCache,
TaskThumbnailCache thumbnailCache, IconProvider iconProvider,
TaskStackChangeListeners taskStackChangeListeners,
- ThemeManager themeManager) {
+ LockedUserState lockedUserState,
+ Provider<ThemeManager> themeManagerProvider) {
mContext = context;
mTaskList = taskList;
mIconCache = iconCache;
@@ -140,8 +150,11 @@
mTaskStackChangeListeners.registerTaskStackListener(this);
mIconChangeCloseable = iconProvider.registerIconChangeListener(
this::onAppIconChanged, MAIN_EXECUTOR.getHandler());
- mThemeManager = themeManager;
- themeManager.addChangeListener(this);
+
+ mLockedUserState = lockedUserState;
+ mThemeManagerProvider = themeManagerProvider;
+ mUnlockCallback = () -> mThemeManagerProvider.get().addChangeListener(this);
+ lockedUserState.runOnUserUnlocked(mUnlockCallback);
}
public TaskIconCache getIconCache() {
@@ -154,7 +167,7 @@
/**
* Fetches the list of recent tasks. Tasks are ordered by recency, with the latest active tasks
- * at the end of the list.
+ * at the end of the list. Filters out desktop tasks that contain no non-minimized tasks.
*
* @param callback The callback to receive the task plan once its complete or null. This is
* always called on the UI thread.
@@ -163,7 +176,7 @@
@Override
public int getTasks(@Nullable Consumer<List<GroupTask>> callback) {
return mTaskList.getTasks(false /* loadKeysOnly */, callback,
- RecentsFilterState.DEFAULT_FILTER);
+ RecentsFilterState.getEmptyDesktopTaskFilter());
}
/**
@@ -402,7 +415,12 @@
mIconCache.removeTaskVisualsChangeListener();
mTaskStackChangeListeners.unregisterTaskStackListener(this);
mIconChangeCloseable.close();
- mThemeManager.removeChangeListener(this);
+
+ if (mLockedUserState.isUserUnlocked()) {
+ mThemeManagerProvider.get().removeChangeListener(this);
+ } else {
+ mLockedUserState.removeOnUserUnlockedRunnable(mUnlockCallback);
+ }
}
private boolean isCachePreloadingEnabled() {
diff --git a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
index 8edbacb..f96bbcb 100644
--- a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
+++ b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
@@ -142,7 +142,9 @@
if (mSplitBounds == null) {
SplitBounds shellSplitBounds = targets.extras.getParcelable(KEY_EXTRA_SPLIT_BOUNDS,
SplitBounds.class);
- mSplitBounds = convertShellSplitBoundsToLauncher(shellSplitBounds);
+ if (shellSplitBounds != null) {
+ mSplitBounds = convertShellSplitBoundsToLauncher(shellSplitBounds);
+ }
}
boolean containsSplitTargets = mSplitBounds != null;
@@ -215,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/RotationTouchHelper.java b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
index f54b655..a614327 100644
--- a/quickstep/src/com/android/quickstep/RotationTouchHelper.java
+++ b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
@@ -33,13 +33,16 @@
import android.view.MotionEvent;
import android.view.OrientationEventListener;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherAppSingleton;
import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.util.DaggerSingletonObject;
+import com.android.launcher3.util.DaggerSingletonTracker;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
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.quickstep.util.RecentsOrientedState;
import com.android.systemui.shared.Flags;
import com.android.systemui.shared.system.QuickStepContract;
@@ -48,16 +51,20 @@
import java.io.PrintWriter;
+import javax.inject.Inject;
+
/**
* Helper class for transforming touch events
*/
-public class RotationTouchHelper implements DisplayInfoChangeListener, SafeCloseable {
+@LauncherAppSingleton
+public class RotationTouchHelper implements DisplayInfoChangeListener {
- public static final MainThreadInitializedObject<RotationTouchHelper> INSTANCE =
- new MainThreadInitializedObject<>(RotationTouchHelper::new);
+ public static final DaggerSingletonObject<RotationTouchHelper> INSTANCE =
+ new DaggerSingletonObject<>(LauncherAppComponent::getRotationTouchHelper);
private final OrientationTouchTransformer mOrientationTouchTransformer;
private final DisplayController mDisplayController;
+ private final SystemUiProxy mSystemUiProxy;
private final int mDisplayId;
private int mDisplayRotation;
@@ -127,12 +134,17 @@
private boolean mTaskListFrozen;
private final Context mContext;
- private RotationTouchHelper(Context context) {
+ @Inject
+ RotationTouchHelper(@ApplicationContext Context context,
+ DisplayController displayController,
+ SystemUiProxy systemUiProxy,
+ DaggerSingletonTracker lifeCycle) {
mContext = context;
- mDisplayController = DisplayController.INSTANCE.get(mContext);
- Resources resources = mContext.getResources();
+ mDisplayController = displayController;
+ mSystemUiProxy = systemUiProxy;
mDisplayId = DEFAULT_DISPLAY;
+ Resources resources = mContext.getResources();
mOrientationTouchTransformer = new OrientationTouchTransformer(resources, mMode,
() -> QuickStepContract.getWindowCornerRadius(mContext));
@@ -160,14 +172,13 @@
}
}
};
- }
- @Override
- public void close() {
- mDisplayController.removeChangeListener(this);
- mOrientationListener.disable();
- TaskStackChangeListeners.getInstance()
- .unregisterTaskStackListener(mFrozenTaskListener);
+ lifeCycle.addCloseable(() -> {
+ mDisplayController.removeChangeListener(this);
+ mOrientationListener.disable();
+ TaskStackChangeListeners.getInstance()
+ .unregisterTaskStackListener(mFrozenTaskListener);
+ });
}
public boolean isTaskListFrozen() {
@@ -340,8 +351,7 @@
}
private void notifySysuiOfCurrentRotation(int rotation) {
- UI_HELPER_EXECUTOR.execute(() -> SystemUiProxy.INSTANCE.get(mContext)
- .notifyPrioritizedRotation(rotation));
+ UI_HELPER_EXECUTOR.execute(() -> mSystemUiProxy.notifyPrioritizedRotation(rotation));
}
/**
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 94ef4dd..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.
@@ -1073,6 +1073,19 @@
//
// Desktop Mode
//
+ /** Calls shell to create a new desk (if possible) on the display whose ID is `displayId`. */
+ fun createDesktop(displayId: Int) =
+ executeWithErrorLog({ "Failed call createDesk" }) { desktopMode?.createDesk(displayId) }
+
+ /**
+ * Calls shell to activate the desk whose ID is `deskId` on whatever display it exists on. This
+ * will bring all tasks on this desk to the front.
+ */
+ fun activateDesktop(deskId: Int, transition: RemoteTransition?) =
+ executeWithErrorLog({ "Failed call activateDesk" }) {
+ desktopMode?.activateDesk(deskId, transition)
+ }
+
/** Call shell to show all apps active on the desktop */
fun showDesktopApps(displayId: Int, transition: RemoteTransition?) =
executeWithErrorLog({ "Failed call showDesktopApps" }) {
@@ -1089,14 +1102,6 @@
desktopMode?.showDesktopApp(taskId, transition, toFrontReason)
}
- /** Call shell to get number of visible freeform tasks */
- fun getVisibleDesktopTaskCount(displayId: Int): Int {
- executeWithErrorLog({ "Failed call getVisibleDesktopTaskCount" }) {
- return desktopMode?.getVisibleTaskCount(displayId) ?: 0
- }
- return 0
- }
-
/** Set a listener on shell to get updates about desktop task state */
fun setDesktopTaskListener(listener: IDesktopTaskListener?) {
desktopTaskListener = listener
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..7990aae 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(),
+ super(option.iconResId, option.textResId, container, taskContainer.getItemInfo(),
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,11 +162,9 @@
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(),
- taskView);
+ super(iconResId, R.string.save_app_pair, container, taskView.getItemInfo(), taskView);
mTaskView = taskView;
}
@@ -202,14 +210,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 +260,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 +297,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 +358,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 +452,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 +540,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..2df4a45 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,70 +301,66 @@
@BinderThread
@Override
- public void onDisplayReady(int displayId) {
- MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(
- tis -> executeForTaskbarManager(
- taskbarManager -> taskbarManager.onDisplayReady(displayId))));
+ public void onDisplayAddSystemDecorations(int displayId) {
+ executeForTaskbarManager(taskbarManager ->
+ taskbarManager.onDisplayAddSystemDecorations(displayId));
}
@BinderThread
@Override
public void onDisplayRemoved(int displayId) {
- MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(
- tis -> executeForTaskbarManager(
- taskbarManager -> taskbarManager.onDisplayRemoved(displayId))));
+ executeForTaskbarManager(taskbarManager ->
+ taskbarManager.onDisplayRemoved(displayId));
+ }
+
+ @BinderThread
+ @Override
+ public void onDisplayRemoveSystemDecorations(int displayId) {
+ executeForTaskbarManager(taskbarManager ->
+ taskbarManager.onDisplayRemoveSystemDecorations(displayId));
}
@BinderThread
@Override
public void updateWallpaperVisibility(int displayId, boolean visible) {
- MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(
- tis -> executeForTaskbarManager(
- taskbarManager -> taskbarManager.setWallpaperVisible(displayId,
- visible))));
+ executeForTaskbarManager(taskbarManager ->
+ taskbarManager.setWallpaperVisible(displayId, visible));
}
@BinderThread
@Override
public void checkNavBarModes(int displayId) {
- MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis ->
- executeForTaskbarManager(
- taskbarManager -> taskbarManager.checkNavBarModes(displayId))));
+ executeForTaskbarManager(taskbarManager ->
+ taskbarManager.checkNavBarModes(displayId));
}
@BinderThread
@Override
public void finishBarAnimations(int displayId) {
- MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(
- tis -> executeForTaskbarManager(
- taskbarManager -> taskbarManager.finishBarAnimations(displayId))));
+ executeForTaskbarManager(taskbarManager ->
+ taskbarManager.finishBarAnimations(displayId));
}
@BinderThread
@Override
public void touchAutoDim(int displayId, boolean reset) {
- MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(
- tis -> executeForTaskbarManager(
- taskbarManager -> taskbarManager.touchAutoDim(displayId, reset))));
+ executeForTaskbarManager(taskbarManager ->
+ taskbarManager.touchAutoDim(displayId, reset));
}
@BinderThread
@Override
public void transitionTo(int displayId, @BarTransitions.TransitionMode int barMode,
boolean animate) {
- MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(
- tis -> executeForTaskbarManager(
- taskbarManager -> taskbarManager.transitionTo(displayId, barMode,
- animate))));
+ executeForTaskbarManager(taskbarManager ->
+ taskbarManager.transitionTo(displayId, barMode, animate));
}
@BinderThread
@Override
public void appTransitionPending(boolean pending) {
- MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis ->
- executeForTaskbarManager(
- taskbarManager -> taskbarManager.appTransitionPending(pending))
- ));
+ executeForTaskbarManager(taskbarManager ->
+ taskbarManager.appTransitionPending(pending));
}
@Override
@@ -412,7 +408,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);
}
});
}
@@ -565,6 +561,8 @@
private DesktopAppLaunchTransitionManager mDesktopAppLaunchTransitionManager;
+ private DisplayController.DisplayInfoChangeListener mDisplayInfoChangeListener;
+
@Override
public void onCreate() {
super.onCreate();
@@ -594,7 +592,8 @@
// Call runOnUserUnlocked() before any other callbacks to ensure everything is initialized.
LockedUserState.get(this).runOnUserUnlocked(mUserUnlockedRunnable);
- mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
+ mDisplayInfoChangeListener =
+ mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
ScreenOnTracker.INSTANCE.get(this).addListener(mScreenOnListener);
}
@@ -746,7 +745,7 @@
mDesktopAppLaunchTransitionManager.unregisterTransitions();
}
mDesktopAppLaunchTransitionManager = null;
-
+ mDeviceState.removeDisplayInfoChangeListener(mDisplayInfoChangeListener);
LockedUserState.get(this).removeOnUserUnlockedRunnable(mUserUnlockedRunnable);
ScreenOnTracker.INSTANCE.get(this).removeListener(mScreenOnListener);
super.onDestroy();
diff --git a/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java b/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
index 1d40d76..adc45ae 100644
--- a/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
+++ b/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
@@ -21,10 +21,15 @@
import com.android.launcher3.model.WellbeingModel;
import com.android.launcher3.statehandlers.DesktopVisibilityController;
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;
/**
* Launcher Quickstep base component for Dagger injection.
@@ -49,4 +54,15 @@
DesktopVisibilityController getDesktopVisibilityController();
TopTaskTracker getTopTaskTracker();
+
+ RotationTouchHelper getRotationTouchHelper();
+
+ ContextualSearchStateManager getContextualSearchStateManager();
+
+ RecentsAnimationDeviceState getRecentsAnimationDeviceState();
+
+ SettingsChangeLogger getSettingsChangeLogger();
+
+ SimpleOrientationTouchTransformer getSimpleOrientationTouchTransformer();
+
}
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index 44fdaec..b4b80c5 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -128,8 +128,7 @@
state.getScrimColor(mRecentsViewContainer.asContext()),
config.getInterpolator(ANIM_SCRIM_FADE, LINEAR));
if (isSplitSelectionState(state)) {
- int duration =
- state.getTransitionDuration(mRecentsViewContainer.asContext(), true);
+ int duration = state.getTransitionDuration(mRecentsViewContainer, true);
// TODO (b/246851887): Pass in setter as a NO_ANIM PendingAnimation instead
PendingAnimation pa = new PendingAnimation(duration);
mRecentsView.createSplitSelectInitAnimation(pa, duration);
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index d9209bf..f426bf5 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -33,7 +33,6 @@
import com.android.launcher3.Flags;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.desktop.DesktopRecentsTransitionController;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.statehandlers.DesktopVisibilityController;
@@ -45,13 +44,15 @@
import com.android.quickstep.BaseContainerInterface;
import com.android.quickstep.FallbackActivityInterface;
import com.android.quickstep.GestureState;
+import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
import com.android.quickstep.fallback.window.RecentsDisplayModel;
import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SingleTask;
import com.android.quickstep.util.SplitSelectStateController;
-import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.RecentsViewContainer;
+import com.android.quickstep.views.TaskContainer;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
@@ -128,8 +129,8 @@
@Override
public void onPrepareGestureEndAnimation(
@Nullable AnimatorSet animatorSet, GestureState.GestureEndTarget endTarget,
- TaskViewSimulator[] taskViewSimulators) {
- super.onPrepareGestureEndAnimation(animatorSet, endTarget, taskViewSimulators);
+ RemoteTargetHandle[] remoteTargetHandles) {
+ super.onPrepareGestureEndAnimation(animatorSet, endTarget, remoteTargetHandles);
if (mHomeTask != null && endTarget == RECENTS && animatorSet != null) {
TaskView tv = getTaskViewByTaskId(mHomeTask.key.id);
if (tv != null) {
@@ -210,7 +211,7 @@
if (!found) {
ArrayList<GroupTask> newList = new ArrayList<>(taskGroups.size() + 1);
newList.addAll(taskGroups);
- newList.add(new GroupTask(mHomeTask, null, null));
+ newList.add(new SingleTask(mHomeTask));
taskGroups = newList;
}
}
@@ -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/RecentsDragLayer.java b/quickstep/src/com/android/quickstep/fallback/RecentsDragLayer.java
index a2884b6..5d4f1db 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsDragLayer.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsDragLayer.java
@@ -15,9 +15,16 @@
*/
package com.android.quickstep.fallback;
+import static com.android.launcher3.Flags.enableExpressiveDismissTaskMotion;
+
import android.content.Context;
import android.util.AttributeSet;
+import com.android.launcher3.statemanager.StatefulContainer;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewDismissTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewLaunchTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewRecentsTouchContext;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchControllerDeprecated;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.views.RecentsViewContainer;
@@ -25,16 +32,41 @@
/**
* Drag layer for fallback recents activity
*/
-public class RecentsDragLayer<T extends Context & RecentsViewContainer> extends BaseDragLayer<T> {
+public class RecentsDragLayer<T extends Context & RecentsViewContainer
+ & StatefulContainer<RecentsState>> extends BaseDragLayer<T> {
+
+ private final TaskViewRecentsTouchContext mTaskViewRecentsTouchContext =
+ new TaskViewRecentsTouchContext() {
+ @Override
+ public boolean isRecentsInteractive() {
+ return mContainer.getRootView().hasWindowFocus()
+ || mContainer.getStateManager().getState().hasLiveTile();
+ }
+
+ @Override
+ public boolean isRecentsModal() {
+ return false;
+ }
+ };
+
public RecentsDragLayer(Context context, AttributeSet attrs) {
super(context, attrs, 1 /* alphaChannelCount */);
}
@Override
public void recreateControllers() {
- mControllers = new TouchController[] {
- new RecentsTaskController(mContainer),
- new FallbackNavBarTouchController(mContainer),
- };
+ mControllers = enableExpressiveDismissTaskMotion()
+ ? new TouchController[]{
+ new TaskViewLaunchTouchController<>(mContainer,
+ mTaskViewRecentsTouchContext),
+ new TaskViewDismissTouchController<>(mContainer,
+ mTaskViewRecentsTouchContext),
+ new FallbackNavBarTouchController(mContainer)
+ }
+ : new TouchController[]{
+ new TaskViewTouchControllerDeprecated<>(mContainer,
+ mTaskViewRecentsTouchContext),
+ new FallbackNavBarTouchController(mContainer)
+ };
}
}
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsState.java b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
index c2e7536..f27b60c 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsState.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
@@ -27,6 +27,7 @@
import com.android.launcher3.R;
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.util.Themes;
+import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.views.RecentsViewContainer;
/**
@@ -92,7 +93,7 @@
}
@Override
- public int getTransitionDuration(Context context, boolean isToState) {
+ public int getTransitionDuration(ActivityContext context, boolean isToState) {
return 250;
}
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java b/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java
deleted file mode 100644
index 07da379..0000000
--- a/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.fallback;
-
-import android.content.Context;
-
-import com.android.launcher3.statemanager.StatefulContainer;
-import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
-import com.android.quickstep.views.RecentsViewContainer;
-
-public class RecentsTaskController<T extends Context & RecentsViewContainer &
- StatefulContainer<RecentsState>> extends TaskViewTouchController<T> {
- public RecentsTaskController(T container) {
- super(container);
- }
-
- @Override
- protected boolean isRecentsInteractive() {
- return mContainer.getRootView().hasWindowFocus()
- || mContainer.getStateManager().getState().hasLiveTile();
- }
-
- @Override
- protected boolean isRecentsModal() {
- return false;
- }
-}
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
index cda6c1b..07288d8 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() {
@@ -387,10 +390,6 @@
return systemUiController
}
- override fun getContext(): Context {
- return this
- }
-
override fun getScrimView(): ScrimView? {
return scrimView
}
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/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
index 42e8694..be47df9 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
@@ -24,11 +24,10 @@
import android.graphics.PointF;
import android.view.MotionEvent;
-import com.android.launcher3.BaseActivity;
-import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.GestureState;
import com.android.quickstep.InputConsumer;
import com.android.quickstep.RecentsAnimationDeviceState;
@@ -80,7 +79,7 @@
@Override
public void onSwipeUp(boolean wasFling, PointF finalVelocity) {
startHomeIntentSafely(mContext, mGestureState.getHomeIntent(), null, TAG);
- BaseActivity activity = BaseDraggingActivity.fromContext(mContext);
+ ActivityContext activity = ActivityContext.lookupContext(mContext);
int state = (mGestureState != null && mGestureState.getEndTarget() != null)
? mGestureState.getEndTarget().containerType
: LAUNCHER_STATE_HOME;
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..594c99a 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)
@@ -423,6 +425,22 @@
case LAUNCHER_PRIVATE_SPACE_UNLOCK_ANIMATION_END:
InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK);
break;
+ case LAUNCHER_WORK_UTILITY_VIEW_EXPAND_ANIMATION_BEGIN:
+ InteractionJankMonitorWrapper.begin(
+ view,
+ Cuj.CUJ_LAUNCHER_WORK_UTILITY_VIEW_EXPAND);
+ break;
+ case LAUNCHER_WORK_UTILITY_VIEW_EXPAND_ANIMATION_END:
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_WORK_UTILITY_VIEW_EXPAND);
+ break;
+ case LAUNCHER_WORK_UTILITY_VIEW_SHRINK_ANIMATION_BEGIN:
+ InteractionJankMonitorWrapper.begin(
+ view,
+ Cuj.CUJ_LAUNCHER_WORK_UTILITY_VIEW_SHRINK);
+ break;
+ case LAUNCHER_WORK_UTILITY_VIEW_SHRINK_ANIMATION_END:
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_WORK_UTILITY_VIEW_SHRINK);
+ break;
default:
break;
}
diff --git a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
index 88ef0a8..e72ccbf 100644
--- a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
@@ -306,6 +306,10 @@
if (isRtl) SingleAxisSwipeDetector.DIRECTION_NEGATIVE
else SingleAxisSwipeDetector.DIRECTION_POSITIVE
+ override fun getDownDirection(isRtl: Boolean): Int =
+ if (isRtl) SingleAxisSwipeDetector.DIRECTION_POSITIVE
+ else SingleAxisSwipeDetector.DIRECTION_NEGATIVE
+
override fun isGoingUp(displacement: Float, isRtl: Boolean): Boolean =
if (isRtl) displacement < 0 else displacement > 0
diff --git a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
index c0b697d..c1e1c2b 100644
--- a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
+++ b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
@@ -310,6 +310,12 @@
}
@Override
+ public int getDownDirection(boolean isRtl) {
+ // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
+ return SingleAxisSwipeDetector.DIRECTION_NEGATIVE;
+ }
+
+ @Override
public boolean isGoingUp(float displacement, boolean isRtl) {
// Ignore rtl since it only affects X value displacement, Y displacement doesn't change
return displacement < 0;
diff --git a/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt b/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt
index b8d0412..78f9a0a 100644
--- a/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt
@@ -332,6 +332,9 @@
/** @return Given [.getUpDownSwipeDirection], whether POSITIVE or NEGATIVE is up. */
fun getUpDirection(isRtl: Boolean): Int
+ /** @return Given [.getUpDownSwipeDirection], whether POSITIVE or NEGATIVE is down. */
+ fun getDownDirection(isRtl: Boolean): Int
+
/** @return Whether the displacement is going towards the top of the screen. */
fun isGoingUp(displacement: Float, isRtl: Boolean): Boolean
diff --git a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
index bc91911..3fb4f54 100644
--- a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
@@ -351,6 +351,10 @@
if (isRtl) SingleAxisSwipeDetector.DIRECTION_POSITIVE
else SingleAxisSwipeDetector.DIRECTION_NEGATIVE
+ override fun getDownDirection(isRtl: Boolean): Int =
+ if (isRtl) SingleAxisSwipeDetector.DIRECTION_NEGATIVE
+ else SingleAxisSwipeDetector.DIRECTION_POSITIVE
+
override fun isGoingUp(displacement: Float, isRtl: Boolean): Boolean =
if (isRtl) displacement > 0 else displacement < 0
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..1f428f3 100644
--- a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
+++ b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
@@ -26,9 +26,11 @@
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.domain.usecase.OrganizeDesktopTasksUseCase
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 +179,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,15 +193,16 @@
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(),
rotationStateRepository = inject(),
tasksRepository = inject(),
)
+ OrganizeDesktopTasksUseCase::class.java -> OrganizeDesktopTasksUseCase()
SplashAlphaUseCase::class.java ->
SplashAlphaUseCase(
recentsViewData = inject(),
diff --git a/quickstep/src/com/android/quickstep/recents/domain/model/DesktopTaskBoundsData.kt b/quickstep/src/com/android/quickstep/recents/domain/model/DesktopTaskBoundsData.kt
new file mode 100644
index 0000000..a7f102c
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/domain/model/DesktopTaskBoundsData.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.model
+
+import android.graphics.Rect
+
+data class DesktopTaskBoundsData(val taskId: Int, val bounds: Rect)
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/domain/usecase/OrganizeDesktopTasksUseCase.kt b/quickstep/src/com/android/quickstep/recents/domain/usecase/OrganizeDesktopTasksUseCase.kt
new file mode 100644
index 0000000..3e7d142
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/domain/usecase/OrganizeDesktopTasksUseCase.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.graphics.Rect
+import android.util.Size
+import com.android.quickstep.recents.domain.model.DesktopTaskBoundsData
+import kotlin.math.ceil
+import kotlin.math.sqrt
+
+/**
+ * This usecase is responsible for organizing desktop windows in a non-overlapping way. Note: this
+ * is currently a placeholder implementation.
+ */
+class OrganizeDesktopTasksUseCase {
+ fun run(
+ desktopSize: Size,
+ taskBounds: List<DesktopTaskBoundsData>,
+ ): List<DesktopTaskBoundsData> {
+ return getRects(desktopSize, taskBounds.size).zip(taskBounds) { rect, task ->
+ shrinkRect(rect, 0.8f)
+ DesktopTaskBoundsData(task.taskId, fitRect(task.bounds, rect))
+ }
+ }
+
+ private fun shrinkRect(bounds: Rect, fraction: Float) {
+ val xMargin = (bounds.width() * ((1.0f - fraction) / 2.0f)).toInt()
+ val yMargin = (bounds.height() * ((1.0f - fraction) / 2.0f)).toInt()
+ bounds.inset(xMargin, yMargin, xMargin, yMargin)
+ }
+
+ /** Generates `tasks` number of non-overlapping rects that fit into `desktopSize`. */
+ private fun getRects(desktopSize: Size, tasks: Int): List<Rect> {
+ val (xSlots, ySlots) =
+ when (tasks) {
+ 2 -> Pair(2, 1)
+ 3,
+ 4 -> Pair(2, 2)
+ 5,
+ 6 -> Pair(3, 2)
+ else -> {
+ val sides = ceil(sqrt(tasks.toDouble())).toInt()
+ Pair(sides, sides)
+ }
+ }
+
+ // The width and height of one of the boxes.
+ val boxWidth = desktopSize.width / xSlots
+ val boxHeight = desktopSize.height / ySlots
+
+ return (0 until tasks).map {
+ val x = it % xSlots
+ val y = it / xSlots
+ Rect(x * boxWidth, y * boxHeight, (x + 1) * boxWidth, (y + 1) * boxHeight)
+ }
+ }
+
+ /** Centers and fits `rect` into `bounds`, while preserving the former's aspect ratio. */
+ private fun fitRect(rect: Rect, bounds: Rect): Rect {
+ val boundsAspect = bounds.width().toFloat() / bounds.height()
+ val rectAspect = rect.width().toFloat() / rect.height()
+
+ if (rectAspect > boundsAspect) {
+ // The width is the limiting dimension.
+ val scale = bounds.width().toFloat() / rect.width()
+ val width = bounds.width()
+ val height = (rect.height() * scale).toInt()
+ val top = (bounds.top + bounds.height() / 2.0f - height / 2.0f).toInt()
+ return Rect(bounds.left, top, bounds.left + width, top + height)
+ } else {
+ // The height is the limiting dimension.
+ val scale = bounds.height().toFloat() / rect.height()
+ val width = (rect.width() * scale).toInt()
+ val height = bounds.height()
+ val left = (bounds.left + bounds.width() / 2.0f - width / 2.0f).toInt()
+ return Rect(left, bounds.top, left + width, bounds.top + height)
+ }
+ }
+}
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/DesktopTaskViewModel.kt b/quickstep/src/com/android/quickstep/recents/ui/viewmodel/DesktopTaskViewModel.kt
new file mode 100644
index 0000000..0a60ee9
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/ui/viewmodel/DesktopTaskViewModel.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.recents.ui.viewmodel
+
+import android.util.Size
+import com.android.quickstep.recents.domain.model.DesktopTaskBoundsData
+import com.android.quickstep.recents.domain.usecase.OrganizeDesktopTasksUseCase
+
+/** ViewModel used for [com.android.quickstep.views.DesktopTaskView]. */
+class DesktopTaskViewModel(private val organizeDesktopTasksUseCase: OrganizeDesktopTasksUseCase) {
+ var organizedDesktopTaskPositions = emptyList<DesktopTaskBoundsData>()
+ private set
+
+ fun organizeDesktopTasks(desktopSize: Size, defaultPositions: List<DesktopTaskBoundsData>) {
+ organizedDesktopTaskPositions =
+ organizeDesktopTasksUseCase.run(desktopSize, defaultPositions)
+ }
+}
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..961446f 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,18 @@
}
.distinctUntilChanged()
+ private val taskData =
+ taskIds.flatMapLatest { ids ->
+ // Combine Tasks requests
+ combine(
+ ids.map { id -> getTaskUseCase(id).map { taskModel -> id to taskModel } },
+ ::mapToTaskData,
+ )
+ }
+
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 +78,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/RecentsViewData.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt
index 6ccf372..a1f8454 100644
--- a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt
+++ b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt
@@ -20,8 +20,6 @@
// This is far from complete but serves the purpose of enabling refactoring in other areas
class RecentsViewData {
- val fullscreenProgress = MutableStateFlow(1f)
-
// Whether the current RecentsView state supports task overlays.
// TODO(b/331753115): Derive from RecentsView state flow once migrated to MVVM.
val overlayEnabled = MutableStateFlow(false)
@@ -29,9 +27,6 @@
// The settled set of visible taskIds that is updated after RecentsView scroll settles.
val settledFullyVisibleTaskIds = MutableStateFlow(emptySet<Int>())
- // Color tint on foreground scrim
- val tintAmount = MutableStateFlow(0f)
-
val thumbnailSplashProgress = MutableStateFlow(0f)
// A list of taskIds that are associated with a RecentsAnimationController. */
diff --git a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
index cfebb81..73332fc 100644
--- a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
+++ b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
@@ -34,10 +34,6 @@
recentsTasksRepository.setVisibleTasks(visibleTaskIdList.toSet())
}
- fun updateFullscreenProgress(fullscreenProgress: Float) {
- recentsViewData.fullscreenProgress.value = fullscreenProgress
- }
-
fun updateTasksFullyVisible(taskIds: Set<Int>) {
recentsViewData.settledFullyVisibleTaskIds.value = taskIds
}
@@ -46,10 +42,6 @@
recentsViewData.overlayEnabled.value = isOverlayEnabled
}
- fun setTintAmount(tintAmount: Float) {
- recentsViewData.tintAmount.value = tintAmount
- }
-
fun updateThumbnailSplashProgress(taskThumbnailSplashAlpha: Float) {
recentsViewData.thumbnailSplashProgress.value = taskThumbnailSplashAlpha
}
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 639d3a7..28152ec 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)
@@ -146,8 +131,8 @@
}
}
- override fun onDetachedFromWindow() {
- super.onDetachedFromWindow()
+ // TODO(b/391842220): Cancel scope in onDetach instead of having a specific method for this.
+ fun destroyScopes() {
val scopeToCancel = viewAttachedScope
recentsCoroutineScope.launch(dispatcherProvider.background) {
scopeToCancel.cancel("TaskThumbnailView detaching from window")
@@ -166,6 +151,27 @@
}
}
+ fun setState(state: TaskThumbnailUiState, taskId: Int? = null) {
+ logDebug("taskId: $taskId - uiState 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,8 +245,8 @@
thumbnailView.imageMatrix = viewModel.getThumbnailPositionState(width, height, isLayoutRtl)
}
- private companion object {
- const val TAG = "TaskThumbnailView"
+ private fun logDebug(message: String) {
+ Log.d(TAG, "[TaskThumbnailView@${Integer.toHexString(hashCode())}] $message")
}
private fun maybeCreateHeader() {
@@ -251,4 +258,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/AnimUtils.java b/quickstep/src/com/android/quickstep/util/AnimUtils.java
index 31aca03..fda0c29 100644
--- a/quickstep/src/com/android/quickstep/util/AnimUtils.java
+++ b/quickstep/src/com/android/quickstep/util/AnimUtils.java
@@ -78,8 +78,7 @@
@NonNull SplitAnimationController animationController) {
StateAnimationConfig config = new StateAnimationConfig();
BaseState startState = stateManager.getState();
- long duration = startState.getTransitionDuration(container.asContext(),
- false /*isToState*/);
+ long duration = startState.getTransitionDuration(container, false /*isToState*/);
if (duration == 0) {
// Case where we're in contextual on workspace (NORMAL), which by default has 0
// transition duration
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/ContextualSearchStateManager.java b/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java
index f75d3b3..ed96399 100644
--- a/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java
+++ b/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java
@@ -21,7 +21,6 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_OMNI_SUCCESSFUL_SYSTEM_ACTION;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
import static com.android.quickstep.util.SystemActionConstants.SYSTEM_ACTION_ID_SEARCH_SCREEN;
import android.app.PendingIntent;
@@ -44,11 +43,13 @@
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.R;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherAppSingleton;
import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.util.DaggerSingletonObject;
+import com.android.launcher3.util.DaggerSingletonTracker;
import com.android.launcher3.util.EventLogArray;
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.ResourceBasedOverride;
-import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.quickstep.DeviceConfigWrapper;
@@ -58,12 +59,14 @@
import java.io.PrintWriter;
import java.util.Optional;
-/** Long-lived class to manage Contextual Search states like the user setting and availability. */
-public class ContextualSearchStateManager implements ResourceBasedOverride, SafeCloseable {
+import javax.inject.Inject;
- public static final MainThreadInitializedObject<ContextualSearchStateManager> INSTANCE =
- forOverride(ContextualSearchStateManager.class,
- R.string.contextual_search_state_manager_class);
+/** Long-lived class to manage Contextual Search states like the user setting and availability. */
+@LauncherAppSingleton
+public class ContextualSearchStateManager {
+
+ public static final DaggerSingletonObject<ContextualSearchStateManager> INSTANCE =
+ new DaggerSingletonObject<>(LauncherAppComponent::getContextualSearchStateManager);
private static final String TAG = "ContextualSearchStMgr";
private static final int MAX_DEBUG_EVENT_SIZE = 20;
@@ -73,23 +76,29 @@
private final Runnable mSysUiStateChangeListener = this::updateOverridesToSysUi;
private final SimpleBroadcastReceiver mContextualSearchPackageReceiver =
new SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, (unused) -> requestUpdateProperties());
- private final SettingsCache.OnChangeListener mContextualSearchSettingChangedListener =
- this::onContextualSearchSettingChanged;
protected final EventLogArray mEventLogArray = new EventLogArray(TAG, MAX_DEBUG_EVENT_SIZE);
// Cached value whether the ContextualSearch intent filter matched any enabled components.
private boolean mIsContextualSearchIntentAvailable;
private boolean mIsContextualSearchSettingEnabled;
- protected Context mContext;
- protected String mContextualSearchPackage;
+ protected final Context mContext;
+ protected final String mContextualSearchPackage;
+ protected final SystemUiProxy mSystemUiProxy;
+ protected final TopTaskTracker mTopTaskTracker;
- public ContextualSearchStateManager() {}
-
- public ContextualSearchStateManager(Context context) {
+ @Inject
+ public ContextualSearchStateManager(
+ @ApplicationContext Context context,
+ SettingsCache settingsCache,
+ SystemUiProxy systemUiProxy,
+ TopTaskTracker topTaskTracker,
+ DaggerSingletonTracker lifeCycle) {
mContext = context;
mContextualSearchPackage = mContext.getResources().getString(
com.android.internal.R.string.config_defaultContextualSearchPackageName);
+ mSystemUiProxy = systemUiProxy;
+ mTopTaskTracker = topTaskTracker;
if (areAllContextualSearchFlagsDisabled()
|| !context.getPackageManager().hasSystemFeature(FEATURE_CONTEXTUAL_SEARCH)) {
@@ -106,11 +115,20 @@
context, mContextualSearchPackage, Intent.ACTION_PACKAGE_ADDED,
Intent.ACTION_PACKAGE_CHANGED, Intent.ACTION_PACKAGE_REMOVED);
- SettingsCache.INSTANCE.get(context).register(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI,
- mContextualSearchSettingChangedListener);
- onContextualSearchSettingChanged(
- SettingsCache.INSTANCE.get(context).getValue(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI));
- SystemUiProxy.INSTANCE.get(mContext).addOnStateChangeListener(mSysUiStateChangeListener);
+ SettingsCache.OnChangeListener settingChangedListener =
+ isEnabled -> mIsContextualSearchSettingEnabled = isEnabled;
+ settingsCache.register(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI, settingChangedListener);
+ mIsContextualSearchSettingEnabled =
+ settingsCache.getValue(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI);
+
+ systemUiProxy.addOnStateChangeListener(mSysUiStateChangeListener);
+
+ lifeCycle.addCloseable(() -> {
+ mContextualSearchPackageReceiver.unregisterReceiverSafely(mContext);
+ unregisterSearchScreenSystemAction();
+ settingsCache.unregister(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI, settingChangedListener);
+ systemUiProxy.removeOnStateChangeListener(mSysUiStateChangeListener);
+ });
}
/** Return {@code true} if the Settings toggle is enabled. */
@@ -118,10 +136,6 @@
return mIsContextualSearchSettingEnabled;
}
- private void onContextualSearchSettingChanged(boolean isEnabled) {
- mIsContextualSearchSettingEnabled = isEnabled;
- }
-
/** Whether search supports showing on the lockscreen. */
protected boolean supportsShowWhenLocked() {
return false;
@@ -208,7 +222,7 @@
protected final void updateOverridesToSysUi() {
// LPH commit haptic is always enabled
- SystemUiProxy.INSTANCE.get(mContext).setOverrideHomeButtonLongPress(
+ mSystemUiProxy.setOverrideHomeButtonLongPress(
getLPHDurationMillis().orElse(0L), getLPHCustomSlopMultiplier().orElse(0f), true);
Log.i(TAG, "Sent LPH override to sysui: " + getLPHDurationMillis().orElse(0L) + ";"
+ getLPHCustomSlopMultiplier().orElse(0f));
@@ -227,10 +241,8 @@
new ContextualSearchInvoker(mContext).show(
ENTRYPOINT_SYSTEM_ACTION);
if (contextualSearchInvoked) {
- String runningPackage =
- TopTaskTracker.INSTANCE.get(mContext).getCachedTopTask(
- /* filterOnlyVisibleRecents */
- true).getPackageName();
+ String runningPackage = mTopTaskTracker.getCachedTopTask(
+ /* filterOnlyVisibleRecents */ true).getPackageName();
StatsLogManager.newInstance(mContext).logger()
.withPackageName(runningPackage)
.log(LAUNCHER_LAUNCH_OMNI_SUCCESSFUL_SYSTEM_ACTION);
@@ -259,15 +271,6 @@
}
}
- @Override
- public void close() {
- mContextualSearchPackageReceiver.unregisterReceiverSafely(mContext);
- unregisterSearchScreenSystemAction();
- SettingsCache.INSTANCE.get(mContext).unregister(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI,
- mContextualSearchSettingChangedListener);
- SystemUiProxy.INSTANCE.get(mContext).removeOnStateChangeListener(mSysUiStateChangeListener);
- }
-
protected final void addEventLog(String event) {
synchronized (mEventLogArray) {
mEventLogArray.addLog(event);
diff --git a/quickstep/src/com/android/quickstep/util/DesktopTask.kt b/quickstep/src/com/android/quickstep/util/DesktopTask.kt
index 1cee2d2..53ea022 100644
--- a/quickstep/src/com/android/quickstep/util/DesktopTask.kt
+++ b/quickstep/src/com/android/quickstep/util/DesktopTask.kt
@@ -17,20 +17,12 @@
import com.android.quickstep.views.TaskViewType
import com.android.systemui.shared.recents.model.Task
-import java.util.Objects
/**
* A [Task] container that can contain N number of tasks that are part of the desktop in recent
- * tasks list.
+ * tasks list. Note that desktops can be empty with no tasks in them.
*/
-class DesktopTask(override val tasks: List<Task>) :
- GroupTask(tasks[0], null, null, TaskViewType.DESKTOP) {
-
- override fun containsTask(taskId: Int) = tasks.any { it.key.id == taskId }
-
- override fun hasMultipleTasks() = tasks.size > 1
-
- override fun supportsMultipleTasks() = true
+class DesktopTask(tasks: List<Task>) : GroupTask(tasks, TaskViewType.DESKTOP) {
override fun copy() = DesktopTask(tasks)
@@ -39,9 +31,6 @@
override fun equals(o: Any?): Boolean {
if (this === o) return true
if (o !is DesktopTask) return false
- if (!super.equals(o)) return false
- return tasks == o.tasks
+ return super.equals(o)
}
-
- override fun hashCode() = Objects.hash(super.hashCode(), tasks)
}
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..455b312
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/ExternalDisplays.kt
@@ -0,0 +1,40 @@
+/*
+ * 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 android.view.Display.INVALID_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.let { displayId ->
+ when (displayId) {
+ null -> DEFAULT_DISPLAY
+ INVALID_DISPLAY -> DEFAULT_DISPLAY
+ else -> displayId
+ }
+ }
+
+/** Returns if this task belongs tto [DEFAULT_DISPLAY] */
+val Task?.isExternalDisplay
+ get() = displayId.isExternalDisplay
diff --git a/quickstep/src/com/android/quickstep/util/GroupTask.kt b/quickstep/src/com/android/quickstep/util/GroupTask.kt
index 0bee5f6..49c37dc 100644
--- a/quickstep/src/com/android/quickstep/util/GroupTask.kt
+++ b/quickstep/src/com/android/quickstep/util/GroupTask.kt
@@ -15,62 +15,85 @@
*/
package com.android.quickstep.util
-import androidx.annotation.VisibleForTesting
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.quickstep.views.TaskViewType
import com.android.systemui.shared.recents.model.Task
import java.util.Objects
/**
- * A [Task] container that can contain one or two tasks, depending on if the two tasks are
- * represented as an app-pair in the recents task list.
+ * An abstract class for creating [Task] containers that can be [SingleTask]s, [SplitTask]s, or
+ * [DesktopTask]s in the recent tasks list.
*/
-open class GroupTask
-@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
-constructor(
- @Deprecated("Prefer using `getTasks()` instead") @JvmField val task1: Task,
- @Deprecated("Prefer using `getTasks()` instead") @JvmField val task2: Task?,
- @JvmField val mSplitBounds: SplitConfigurationOptions.SplitBounds?,
- @JvmField val taskViewType: TaskViewType,
-) {
- constructor(task: Task) : this(task, null, null)
-
- constructor(
- t1: Task,
- t2: Task?,
- splitBounds: SplitConfigurationOptions.SplitBounds?,
- ) : this(t1, t2, splitBounds, if (t2 != null) TaskViewType.GROUPED else TaskViewType.SINGLE)
-
- open fun containsTask(taskId: Int) =
- task1.key.id == taskId || (task2 != null && task2.key.id == taskId)
+abstract class GroupTask(val tasks: List<Task>, @JvmField val taskViewType: TaskViewType) {
+ fun containsTask(taskId: Int) = tasks.any { it.key.id == taskId }
/**
* Returns true if a task in this group has a package name that matches the given `packageName`.
*/
- fun containsPackage(packageName: String) = tasks.any { it.key.packageName == packageName }
+ fun containsPackage(packageName: String?) = tasks.any { it.key.packageName == packageName }
- open fun hasMultipleTasks() = task2 != null
+ /**
+ * Returns true if a task in this group has a package name that matches the given `packageName`,
+ * and its user ID matches the given `userId`.
+ */
+ fun containsPackage(packageName: String?, userId: Int) =
+ tasks.any { it.key.packageName == packageName && it.key.userId == userId }
- /** Returns whether this task supports multiple tasks or not. */
- open fun supportsMultipleTasks() = taskViewType == TaskViewType.GROUPED
-
- /** Returns a List of all the Tasks in this GroupTask */
- open val tasks: List<Task>
- get() = listOfNotNull(task1, task2)
+ fun isEmpty() = tasks.isEmpty()
/** Creates a copy of this instance */
- open fun copy() = GroupTask(Task(task1), if (task2 != null) Task(task2) else null, mSplitBounds)
-
- override fun toString() = "type=$taskViewType task1=$task1 task2=$task2"
+ abstract fun copy(): GroupTask
override fun equals(o: Any?): Boolean {
if (this === o) return true
if (o !is GroupTask) return false
- return taskViewType == o.taskViewType &&
- task1 == o.task1 &&
- task2 == o.task2 &&
- mSplitBounds == o.mSplitBounds
+ return taskViewType == o.taskViewType && tasks == o.tasks
}
- override fun hashCode() = Objects.hash(task1, task2, mSplitBounds, taskViewType)
+ override fun hashCode() = Objects.hash(tasks, taskViewType)
+}
+
+/** A [Task] container that must contain exactly one task in the recent tasks list. */
+class SingleTask(task: Task) : GroupTask(listOf(task), TaskViewType.SINGLE) {
+
+ val task: Task
+ get() = tasks[0]
+
+ override fun copy() = SingleTask(task)
+
+ override fun toString() = "type=$taskViewType task=$task"
+
+ override fun equals(o: Any?): Boolean {
+ if (this === o) return true
+ if (o !is SingleTask) return false
+ return super.equals(o)
+ }
+}
+
+/**
+ * A [Task] container that must contain exactly two tasks and split bounds to represent an app-pair
+ * in the recent tasks list.
+ */
+class SplitTask(task1: Task, task2: Task, val splitBounds: SplitConfigurationOptions.SplitBounds) :
+ GroupTask(listOf(task1, task2), TaskViewType.GROUPED) {
+
+ val topLeftTask: Task
+ get() = if (splitBounds.leftTopTaskId == tasks[0].key.id) tasks[0] else tasks[1]
+
+ val bottomRightTask: Task
+ get() = if (topLeftTask == tasks[0]) tasks[1] else tasks[0]
+
+ override fun copy() = SplitTask(tasks[0], tasks[1], splitBounds)
+
+ override fun toString() =
+ "type=$taskViewType topLeftTask=$topLeftTask bottomRightTask=$bottomRightTask"
+
+ override fun equals(o: Any?): Boolean {
+ if (this === o) return true
+ if (o !is SplitTask) return false
+ if (splitBounds != o.splitBounds) return false
+ return super.equals(o)
+ }
+
+ override fun hashCode() = Objects.hash(super.hashCode(), splitBounds)
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index 9b4c772..0182969 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -749,8 +749,8 @@
// launcher side animation)
val leftTopApp =
leafRoots.single { change ->
- (isLeftRightSplit && change.endAbsBounds.left == 0) ||
- (!isLeftRightSplit && change.endAbsBounds.top == 0)
+ (isLeftRightSplit && change.endAbsBounds.left <= 0) ||
+ (!isLeftRightSplit && change.endAbsBounds.top <= 0)
}
val dividerPos =
if (isLeftRightSplit) leftTopApp.endAbsBounds.right
diff --git a/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt b/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt
index d982e81..4005c5a 100644
--- a/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt
@@ -33,38 +33,34 @@
// TODO(b/254378592): Remove these methods when the two classes are reunited
/** Converts the shell version of SplitBounds to the launcher version */
@JvmStatic
- fun convertShellSplitBoundsToLauncher(
- shellSplitBounds: SplitBounds?
- ): SplitConfigurationOptions.SplitBounds? {
- return if (shellSplitBounds == null) {
- null
- } else {
- SplitConfigurationOptions.SplitBounds(
- shellSplitBounds.leftTopBounds,
- shellSplitBounds.rightBottomBounds,
- shellSplitBounds.leftTopTaskId,
- shellSplitBounds.rightBottomTaskId,
- shellSplitBounds.snapPosition
- )
- }
- }
+ fun convertShellSplitBoundsToLauncher(shellSplitBounds: SplitBounds) =
+ SplitConfigurationOptions.SplitBounds(
+ shellSplitBounds.leftTopBounds,
+ shellSplitBounds.rightBottomBounds,
+ shellSplitBounds.leftTopTaskId,
+ shellSplitBounds.rightBottomTaskId,
+ shellSplitBounds.snapPosition,
+ )
/**
* Given a TransitionInfo, generates the tree structure for those changes and extracts out
- * the top most root and it's two immediate children.
- * Changes can be provided in any order.
+ * the top most root and it's two immediate children. Changes can be provided in any order.
*
- * @return a [Pair] where first -> top most split root,
- * second -> [List] of 2, leftTop/bottomRight stage roots
+ * @return a [Pair] where first -> top most split root, second -> [List] of 2,
+ * leftTop/bottomRight stage roots
*/
- fun extractTopParentAndChildren(transitionInfo: TransitionInfo):
- Pair<Change, List<Change>>? {
+ fun extractTopParentAndChildren(
+ transitionInfo: TransitionInfo
+ ): Pair<Change, List<Change>>? {
val parentToChildren = mutableMapOf<Change, MutableList<Change>>()
val hasParent = mutableSetOf<Change>()
// filter out anything that isn't opening and the divider
- val taskChanges: List<Change> = transitionInfo.changes
- .filter { change -> (change.mode == TRANSIT_OPEN ||
- change.mode == TRANSIT_TO_FRONT) && change.flags < FLAG_FIRST_CUSTOM}
+ val taskChanges: List<Change> =
+ transitionInfo.changes
+ .filter { change ->
+ (change.mode == TRANSIT_OPEN || change.mode == TRANSIT_TO_FRONT) &&
+ change.flags < FLAG_FIRST_CUSTOM
+ }
.toList()
// 1. Build Parent-Child Relationships
@@ -73,8 +69,8 @@
// startAnimation() and we can know the precise taskIds of launching tasks.
change.parent?.let { parent ->
parentToChildren
- .getOrPut(transitionInfo.getChange(parent)!!) { mutableListOf() }
- .add(change)
+ .getOrPut(transitionInfo.getChange(parent)!!) { mutableListOf() }
+ .add(change)
hasParent.add(change)
}
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 5f8b4d9..fd8b356 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -71,7 +71,6 @@
import com.android.launcher3.R;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.apppairs.AppPairIcon;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.IconProvider;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.ItemInfo;
@@ -260,7 +259,7 @@
GroupTask groupTask = taskGroups.get(i);
if (isInstanceOfAppPair(
groupTask, componentKeys.get(0), componentKeys.get(1))) {
- lastActiveTasks[0] = groupTask.task1;
+ lastActiveTasks[0] = ((SplitTask) groupTask).getTopLeftTask();
break;
}
}
@@ -314,11 +313,15 @@
*/
public boolean isInstanceOfAppPair(GroupTask groupTask, @NonNull ComponentKey componentKey1,
@NonNull ComponentKey componentKey2) {
- return ((isInstanceOfComponent(groupTask.task1, componentKey1)
- && isInstanceOfComponent(groupTask.task2, componentKey2))
- ||
- (isInstanceOfComponent(groupTask.task1, componentKey2)
- && isInstanceOfComponent(groupTask.task2, componentKey1)));
+ if (groupTask instanceof SplitTask splitTask) {
+ return ((isInstanceOfComponent(splitTask.getTopLeftTask(), componentKey1)
+ && isInstanceOfComponent(splitTask.getBottomRightTask(), componentKey2))
+ ||
+ (isInstanceOfComponent(splitTask.getTopLeftTask(), componentKey2)
+ && isInstanceOfComponent(splitTask.getBottomRightTask(),
+ componentKey1)));
+ }
+ return false;
}
/**
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index a1e55fb..09e9c8b 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -120,6 +120,9 @@
private int mTaskRectTranslationY;
private int mDesktopTaskIndex = 0;
+ @Nullable
+ private Matrix mTaskRectTransform = null;
+
public TaskViewSimulator(Context context, BaseContainerInterface sizeStrategy,
boolean isDesktop, int desktopTaskIndex) {
mContext = context;
@@ -364,6 +367,15 @@
}
/**
+ * Sets a matrix used to transform the position of tasks. If set, this matrix is applied to
+ * the task rect after the task has been scaled and positioned inside the fulltask, but
+ * before scaling and translation of the whole recents view is performed.
+ */
+ public void setTaskRectTransform(@Nullable Matrix taskRectTransform) {
+ mTaskRectTransform = taskRectTransform;
+ }
+
+ /**
* Applies the rotation on the matrix to so that it maps from launcher coordinate space to
* window coordinate space.
*/
@@ -424,8 +436,11 @@
mMatrix.set(mPositionHelper.getMatrix());
- // Apply TaskView matrix: taskRect, translate
+ // Apply TaskView matrix: taskRect, optional transform, translate
mMatrix.postTranslate(mTaskRect.left, mTaskRect.top);
+ if (mTaskRectTransform != null) {
+ mMatrix.postConcat(mTaskRectTransform);
+ }
mOrientationState.getOrientationHandler().setPrimary(mMatrix, MATRIX_POST_TRANSLATE,
taskPrimaryTranslation.value);
mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
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/DesktopTaskView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
index 471313a..bb6829a 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
@@ -15,33 +15,44 @@
*/
package com.android.quickstep.views
+import android.animation.Animator
import android.annotation.SuppressLint
import android.content.Context
-import android.graphics.Point
+import android.graphics.Matrix
import android.graphics.PointF
import android.graphics.Rect
+import android.graphics.RectF
import android.util.AttributeSet
import android.util.Log
+import android.util.Size
import android.view.Gravity
import android.view.View
import android.view.ViewStub
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.updateLayoutParams
+import com.android.launcher3.Flags.enableDesktopExplodedView
import com.android.launcher3.Flags.enableOverviewIconMenu
import com.android.launcher3.Flags.enableRefactorTaskThumbnail
import com.android.launcher3.R
+import com.android.launcher3.anim.AnimatedFloat
import com.android.launcher3.testing.TestLogging
import com.android.launcher3.testing.shared.TestProtocol
import com.android.launcher3.util.RunnableList
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.launcher3.util.TransformingTouchDelegate
import com.android.launcher3.util.ViewPool
+import com.android.launcher3.util.rects.lerpRect
import com.android.launcher3.util.rects.set
import com.android.quickstep.BaseContainerInterface
import com.android.quickstep.DesktopFullscreenDrawParams
import com.android.quickstep.FullscreenDrawParams
+import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle
import com.android.quickstep.TaskOverlayFactory
import com.android.quickstep.ViewUtils
+import com.android.quickstep.recents.di.RecentsDependencies
+import com.android.quickstep.recents.di.get
+import com.android.quickstep.recents.domain.model.DesktopTaskBoundsData
+import com.android.quickstep.recents.ui.viewmodel.DesktopTaskViewModel
import com.android.quickstep.task.thumbnail.TaskThumbnailView
import com.android.quickstep.util.RecentsOrientedState
import com.android.systemui.shared.recents.model.Task
@@ -79,11 +90,40 @@
} else null
private val tempPointF = PointF()
- private val tempRect = Rect()
+ private val lastComputedTaskSize = Rect()
private lateinit var iconView: TaskViewIcon
private lateinit var contentView: DesktopTaskContentView
private lateinit var backgroundView: View
+ private var viewModel: DesktopTaskViewModel? = null
+
+ /**
+ * Holds the default (user placed) positions of task windows. This can be moved into the
+ * viewModel once RefactorTaskThumbnail has been launched.
+ */
+ private var defaultTaskPositions: List<DesktopTaskBoundsData> = emptyList()
+
+ /**
+ * When enableDesktopExplodedView is enabled, this controls the gradual transition from the
+ * default positions to the organized non-overlapping positions.
+ */
+ var explodeProgress = 0.0f
+ set(value) {
+ field = value
+ positionTaskWindows()
+ }
+
+ var remoteTargetHandles: Array<RemoteTargetHandle>? = null
+ set(value) {
+ field = value
+ positionTaskWindows()
+ }
+
+ private fun getRemoteTargetHandle(taskId: Int): RemoteTargetHandle? =
+ remoteTargetHandles?.firstOrNull {
+ it.transformParams.targetSet.firstAppTargetTaskId == taskId
+ }
+
override fun onFinishInflate() {
super.onFinishInflate()
iconView =
@@ -121,6 +161,113 @@
?.inflate()
}
+ fun startWindowExplodeAnimation(): Animator =
+ AnimatedFloat { progress -> explodeProgress = progress }.animateToValue(0.0f, 1.0f)
+
+ private fun positionTaskWindows() {
+ if (taskContainers.isEmpty()) {
+ return
+ }
+
+ val thumbnailTopMarginPx = container.deviceProfile.overviewTaskThumbnailTopMarginPx
+
+ val containerWidth = layoutParams.width
+ val containerHeight = layoutParams.height - thumbnailTopMarginPx
+
+ BaseContainerInterface.getTaskDimension(mContext, container.deviceProfile, tempPointF)
+
+ val windowWidth = tempPointF.x.toInt()
+ val windowHeight = tempPointF.y.toInt()
+ val scaleWidth = containerWidth / windowWidth.toFloat()
+ val scaleHeight = containerHeight / windowHeight.toFloat()
+
+ taskContainers.forEach {
+ val taskId = it.task.key.id
+ val defaultPosition = defaultTaskPositions.firstOrNull { it.taskId == taskId } ?: return
+ val position =
+ if (enableDesktopExplodedView()) {
+ viewModel!!
+ .organizedDesktopTaskPositions
+ .firstOrNull { it.taskId == taskId }
+ ?.let { organizedPosition ->
+ TEMP_RECT.apply {
+ lerpRect(
+ defaultPosition.bounds,
+ organizedPosition.bounds,
+ explodeProgress,
+ )
+ }
+ } ?: defaultPosition.bounds
+ } else {
+ defaultPosition.bounds
+ }
+
+ if (enableDesktopExplodedView()) {
+ getRemoteTargetHandle(taskId)?.let { remoteTargetHandle ->
+ val fromRect =
+ TEMP_RECTF1.apply {
+ set(defaultPosition.bounds)
+ scale(scaleWidth)
+ offset(
+ lastComputedTaskSize.left.toFloat(),
+ lastComputedTaskSize.top.toFloat(),
+ )
+ }
+ val toRect =
+ TEMP_RECTF2.apply {
+ set(position)
+ scale(scaleWidth)
+ offset(
+ lastComputedTaskSize.left.toFloat(),
+ lastComputedTaskSize.top.toFloat(),
+ )
+ }
+ val transform = Matrix()
+ transform.setRectToRect(fromRect, toRect, Matrix.ScaleToFit.FILL)
+ remoteTargetHandle.taskViewSimulator.setTaskRectTransform(transform)
+ remoteTargetHandle.taskViewSimulator.apply(remoteTargetHandle.transformParams)
+ }
+ }
+
+ val taskLeft = position.left * scaleWidth
+ val taskTop = position.top * scaleHeight
+ val taskWidth = position.width() * scaleWidth
+ val taskHeight = position.height() * scaleHeight
+ // TODO(b/394660950): Revisit the choice to update the layout when explodeProgress == 1.
+ // To run the explode animation in reverse, it may be simpler to use translation/scale
+ // for all cases where the progress is non-zero.
+ if (explodeProgress == 0.0f || explodeProgress == 1.0f) {
+ // Reset scaling and translation that may have been applied during animation.
+ it.snapshotView.apply {
+ scaleX = 1.0f
+ scaleY = 1.0f
+ translationX = 0.0f
+ translationY = 0.0f
+ }
+
+ // Position the task to the same position as it would be on the desktop
+ it.snapshotView.updateLayoutParams<LayoutParams> {
+ gravity = Gravity.LEFT or Gravity.TOP
+ width = taskWidth.toInt()
+ height = taskHeight.toInt()
+ leftMargin = taskLeft.toInt()
+ topMargin = taskTop.toInt()
+ }
+ } else {
+ // During the animation, apply translation and scale such that the view is
+ // transformed to where we want, without triggering layout.
+ it.snapshotView.apply {
+ pivotX = 0.0f
+ pivotY = 0.0f
+ translationX = taskLeft - left
+ translationY = taskTop - top
+ scaleX = taskWidth / width.toFloat()
+ scaleY = taskHeight / height.toFloat()
+ }
+ }
+ }
+ }
+
/** Updates this desktop task to the gives task list defined in `tasks` */
fun bind(
tasks: List<Task>,
@@ -133,6 +280,7 @@
tasks.forEach { sb.append(" key=${it.key}\n") }
Log.d(TAG, sb.toString())
}
+
cancelPendingLoadTasks()
val backgroundViewIndex = contentView.indexOfChild(backgroundView)
taskContainers =
@@ -160,8 +308,19 @@
onBind(orientedState)
}
+ override fun onBind(orientedState: RecentsOrientedState) {
+ super.onBind(orientedState)
+
+ if (enableRefactorTaskThumbnail()) {
+ viewModel =
+ DesktopTaskViewModel(organizeDesktopTasksUseCase = RecentsDependencies.get())
+ }
+ }
+
override fun onRecycle() {
super.onRecycle()
+ explodeProgress = 0.0f
+ viewModel = null
visibility = VISIBLE
taskContainers.forEach {
contentView.removeView(it.snapshotView)
@@ -176,61 +335,21 @@
@SuppressLint("RtlHardcoded")
override fun updateTaskSize(lastComputedTaskSize: Rect, lastComputedGridTaskSize: Rect) {
super.updateTaskSize(lastComputedTaskSize, lastComputedGridTaskSize)
- if (taskContainers.isEmpty()) {
- return
- }
-
- val thumbnailTopMarginPx = container.deviceProfile.overviewTaskThumbnailTopMarginPx
-
- val containerWidth = layoutParams.width
- val containerHeight = layoutParams.height - thumbnailTopMarginPx
+ this.lastComputedTaskSize.set(lastComputedTaskSize)
BaseContainerInterface.getTaskDimension(mContext, container.deviceProfile, tempPointF)
+ val desktopSize = Size(tempPointF.x.toInt(), tempPointF.y.toInt())
+ DEFAULT_BOUNDS.set(0, 0, desktopSize.width / 4, desktopSize.height / 4)
- val windowWidth = tempPointF.x.toInt()
- val windowHeight = tempPointF.y.toInt()
- val scaleWidth = containerWidth / windowWidth.toFloat()
- val scaleHeight = containerHeight / windowHeight.toFloat()
-
- if (DEBUG) {
- Log.d(
- TAG,
- "onMeasure: container=[$containerWidth,$containerHeight]" +
- "window=[$windowWidth,$windowHeight] scale=[$scaleWidth,$scaleHeight]",
- )
- }
-
- // Desktop tile is a shrunk down version of launcher and freeform task thumbnails.
- taskContainers.forEach {
- // Default to quarter of the desktop if we did not get app bounds.
- val taskSize =
- it.task.appBounds
- ?: tempRect.apply {
- left = 0
- top = 0
- right = windowWidth / 4
- bottom = windowHeight / 4
- }
- val positionInParent = it.task.positionInParent ?: ORIGIN
-
- // Position the task to the same position as it would be on the desktop
- it.snapshotView.updateLayoutParams<LayoutParams> {
- gravity = Gravity.LEFT or Gravity.TOP
- width = (taskSize.width() * scaleWidth).toInt()
- height = (taskSize.height() * scaleHeight).toInt()
- leftMargin = (positionInParent.x * scaleWidth).toInt()
- topMargin = (positionInParent.y * scaleHeight).toInt()
+ defaultTaskPositions =
+ taskContainers.map {
+ DesktopTaskBoundsData(it.task.key.id, it.task.appBounds ?: DEFAULT_BOUNDS)
}
- if (DEBUG) {
- with(it.snapshotView.layoutParams as LayoutParams) {
- Log.d(
- TAG,
- "onMeasure: task=${it.task.key} size=[$width,$height]" +
- " margin=[$leftMargin,$topMargin]",
- )
- }
- }
+
+ if (enableDesktopExplodedView()) {
+ viewModel?.organizeDesktopTasks(desktopSize, defaultTaskPositions)
}
+ positionTaskWindows()
}
override fun onTaskListVisibilityChanged(visible: Boolean, changes: Int) {
@@ -319,6 +438,10 @@
// As DesktopTaskView is inflated in background, use initialSize=0 to avoid initPool.
private const val VIEW_POOL_INITIAL_SIZE = 0
- private val ORIGIN = Point(0, 0)
+ private val DEFAULT_BOUNDS = Rect()
+ // Temporaries used for various purposes to avoid allocations.
+ private val TEMP_RECT = Rect()
+ private val TEMP_RECTF1 = RectF()
+ private val TEMP_RECTF2 = RectF()
}
}
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 684e84a..9d3b23a 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -36,10 +36,12 @@
import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType;
import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
import static com.android.launcher3.Flags.enableAdditionalHomeAnimations;
+import static com.android.launcher3.Flags.enableDesktopExplodedView;
import static com.android.launcher3.Flags.enableDesktopTaskAlphaAnimation;
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;
@@ -135,6 +137,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.core.graphics.ColorUtils;
+import androidx.dynamicanimation.animation.SpringAnimation;
import com.android.internal.jank.Cuj;
import com.android.launcher3.AbstractFloatingView;
@@ -164,6 +167,7 @@
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.touch.OverScroll;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.util.CancellableTask;
import com.android.launcher3.util.DynamicResource;
import com.android.launcher3.util.IntArray;
@@ -212,9 +216,11 @@
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.RecentsAtomicAnimationFactory;
import com.android.quickstep.util.RecentsOrientedState;
+import com.android.quickstep.util.SingleTask;
import com.android.quickstep.util.SplitAnimationController.Companion.SplitAnimInitProps;
import com.android.quickstep.util.SplitAnimationTimings;
import com.android.quickstep.util.SplitSelectStateController;
+import com.android.quickstep.util.SplitTask;
import com.android.quickstep.util.SurfaceTransaction;
import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.quickstep.util.TaskGridNavHelper;
@@ -236,6 +242,7 @@
import kotlin.Unit;
import kotlin.collections.CollectionsKt;
+import kotlin.jvm.functions.Function0;
import kotlinx.coroutines.CoroutineScope;
@@ -651,13 +658,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,
@@ -846,7 +853,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();
@@ -907,6 +914,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 */,
@@ -1115,7 +1123,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(),
@@ -1130,9 +1138,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 */);
@@ -1827,7 +1836,7 @@
return;
}
- int runningTaskExpectedIndex = getRunningTaskExpectedIndex(runningTaskView);
+ int runningTaskExpectedIndex = mUtils.getRunningTaskExpectedIndex(runningTaskView);
if (mCurrentPage == runningTaskExpectedIndex) {
return;
}
@@ -1847,28 +1856,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();
@@ -1956,6 +1943,9 @@
if (enableLargeDesktopWindowingTile()) {
taskGroups = mUtils.sortDesktopTasksToFront(taskGroups);
}
+ if (enableSeparateExternalDisplayTasks()) {
+ taskGroups = mUtils.sortExternalDisplayTasksToFront(taskGroups);
+ }
if (mAddDesktopButton != null) {
// Add `mAddDesktopButton` as the first child.
@@ -1968,7 +1958,7 @@
GroupTask groupTask = taskGroups.get(i);
boolean containsStagedTask = stagedTaskIdToBeRemoved != INVALID_TASK_ID
&& groupTask.containsTask(stagedTaskIdToBeRemoved);
- boolean shouldSkipGroupTask = containsStagedTask && !groupTask.hasMultipleTasks();
+ boolean shouldSkipGroupTask = containsStagedTask && groupTask instanceof SingleTask;
if ((isSplitSelectionActive() && groupTask.taskViewType == TaskViewType.DESKTOP)
|| shouldSkipGroupTask) {
@@ -1982,25 +1972,27 @@
// to be a temporary container for the remaining task.
TaskView taskView = getTaskViewFromPool(
containsStagedTask ? TaskViewType.SINGLE : groupTask.taskViewType);
- if (taskView instanceof GroupedTaskView) {
- boolean firstTaskIsLeftTopTask =
- groupTask.mSplitBounds.leftTopTaskId == groupTask.task1.key.id;
- Task leftTopTask = firstTaskIsLeftTopTask ? groupTask.task1 : groupTask.task2;
- Task rightBottomTask = firstTaskIsLeftTopTask ? groupTask.task2 : groupTask.task1;
- ((GroupedTaskView) taskView).bind(leftTopTask, rightBottomTask, mOrientationState,
- mTaskOverlayFactory, groupTask.mSplitBounds);
- } else if (taskView instanceof DesktopTaskView) {
+ if (taskView instanceof GroupedTaskView groupedTaskView) {
+ var splitTask = (SplitTask) groupTask;
+ groupedTaskView.bind(splitTask.getTopLeftTask(),
+ splitTask.getBottomRightTask(), mOrientationState,
+ mTaskOverlayFactory, splitTask.getSplitBounds());
+ } else if (taskView instanceof DesktopTaskView desktopTaskView) {
// Minimized tasks should not be shown in Overview
List<Task> nonMinimizedTasks =
groupTask.getTasks().stream()
.filter(task -> !task.isMinimized)
.toList();
- ((DesktopTaskView) taskView).bind(nonMinimizedTasks, mOrientationState,
+ desktopTaskView.bind(nonMinimizedTasks, mOrientationState,
mTaskOverlayFactory);
- } else {
- Task task = groupTask.task1.key.id == stagedTaskIdToBeRemoved ? groupTask.task2
- : groupTask.task1;
+ } else if (groupTask instanceof SplitTask splitTask) {
+ Task task = splitTask.getTopLeftTask().key.id == stagedTaskIdToBeRemoved
+ ? splitTask.getBottomRightTask()
+ : splitTask.getTopLeftTask();
taskView.bind(task, mOrientationState, mTaskOverlayFactory);
+ } else {
+ taskView.bind(((SingleTask) groupTask).getTask(), mOrientationState,
+ mTaskOverlayFactory);
}
addView(taskView);
@@ -2137,6 +2129,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.
*/
@@ -2193,9 +2190,6 @@
public void setFullscreenProgress(float fullscreenProgress) {
mFullscreenProgress = fullscreenProgress;
- if (enableRefactorTaskThumbnail()) {
- mRecentsViewModel.updateFullscreenProgress(mFullscreenProgress);
- }
for (TaskView taskView : getTaskViews()) {
taskView.setFullscreenProgress(mFullscreenProgress);
}
@@ -2693,16 +2687,17 @@
}
private void onReset() {
- if (enableRefactorTaskThumbnail()) {
- mRecentsViewModel.onReset();
- removeAllViews();
- }
unloadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
setCurrentPage(0);
LayoutUtils.setViewEnabled(mActionsView, true);
if (mOrientationState.setGestureActive(false)) {
updateOrientationHandler(/* forceRecreateDragLayerControllers = */ false);
}
+ if (enableRefactorTaskThumbnail()) {
+ mRecentsViewModel.onReset();
+ // TODO(b/391842220) Remove TaskViews rather than calling specific logic to cancel scope
+ getTaskViews().forEach(TaskView::destroyScopes);
+ }
}
public int getRunningTaskViewId() {
@@ -2904,7 +2899,7 @@
*/
public void onPrepareGestureEndAnimation(
@Nullable AnimatorSet animatorSet, GestureState.GestureEndTarget endTarget,
- TaskViewSimulator[] taskViewSimulators) {
+ RemoteTargetHandle[] remoteTargetHandles) {
Log.d(TAG, "onPrepareGestureEndAnimation - endTarget: " + endTarget);
mCurrentGestureEndTarget = endTarget;
boolean isOverviewEndTarget = endTarget == GestureState.GestureEndTarget.RECENTS;
@@ -2912,6 +2907,19 @@
updateGridProperties();
}
+ if (enableDesktopExplodedView()) {
+ for (TaskView taskView : getTaskViews()) {
+ if (taskView instanceof DesktopTaskView desktopTaskView) {
+ if (animatorSet == null) {
+ desktopTaskView.setExplodeProgress(1.0f);
+ } else {
+ animatorSet.play(desktopTaskView.startWindowExplodeAnimation());
+ }
+ desktopTaskView.setRemoteTargetHandles(remoteTargetHandles);
+ }
+ }
+ }
+
BaseState<?> endState = mSizeStrategy.stateFromGestureEndTarget(endTarget);
if (endState.displayOverviewTasksAsGrid(mContainer.getDeviceProfile())) {
TaskView runningTaskView = getRunningTaskView();
@@ -2924,7 +2932,8 @@
- runningTaskView.getNonGridTranslationX();
runningTaskSecondaryGridTranslation = runningTaskView.getGridTranslationY();
}
- for (TaskViewSimulator tvs : taskViewSimulators) {
+ for (RemoteTargetHandle remoteTargetHandle : remoteTargetHandles) {
+ TaskViewSimulator tvs = remoteTargetHandle.getTaskViewSimulator();
if (animatorSet == null) {
setGridProgress(1);
tvs.taskPrimaryTranslation.value = runningTaskPrimaryGridTranslation;
@@ -2972,6 +2981,12 @@
startIconFadeInOnGestureComplete();
animateActionsViewIn();
+ for (TaskView taskView : getTaskViews()) {
+ if (taskView instanceof DesktopTaskView desktopTaskView) {
+ desktopTaskView.setRemoteTargetHandles(mRemoteTargetHandles);
+ }
+ }
+
mCurrentGestureEndTarget = null;
}
@@ -3032,7 +3047,7 @@
if (mAddDesktopButton != null && wasEmpty) {
addView(mAddDesktopButton);
}
- addView(taskView, getRunningTaskExpectedIndex(taskView));
+ addView(taskView, mUtils.getRunningTaskExpectedIndex(taskView));
runningTaskViewId = taskView.getTaskViewId();
if (wasEmpty) {
addView(mClearAllButton);
@@ -3466,7 +3481,7 @@
// `mAddDesktopButton`, shift `mAddDesktopButton` to accommodate.
translationX += largeTaskWidthAndSpacing;
}
- mAddDesktopButton.setTranslationX(translationX);
+ mAddDesktopButton.setGridTranslationX(translationX);
}
final TaskView runningTask = getRunningTaskView();
@@ -4096,7 +4111,7 @@
removeTaskInternal(dismissedTaskView);
}
mContainer.getStatsLogManager().logger()
- .withItemInfo(dismissedTaskView.getFirstItemInfo())
+ .withItemInfo(dismissedTaskView.getItemInfo())
.log(LAUNCHER_TASK_DISMISS_SWIPE_UP);
}
@@ -4604,6 +4619,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) {
@@ -4972,8 +4993,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(
@@ -5176,18 +5197,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);
@@ -5278,15 +5301,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(),
@@ -5699,6 +5723,13 @@
mTempRect, mContainer.getDeviceProfile(), mTempPointF);
}
+ /**
+ * Clears the existing PendingAnimation.
+ */
+ public void clearPendingAnimation() {
+ mPendingAnimation = null;
+ }
+
public PendingAnimation createTaskLaunchAnimation(
TaskView taskView, long duration, Interpolator interpolator) {
if (FeatureFlags.IS_STUDIO_BUILD && mPendingAnimation != null) {
@@ -5714,7 +5745,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 -> {
@@ -5772,7 +5803,7 @@
} else {
taskView.launchWithoutAnimation(this::onTaskLaunchAnimationEnd);
}
- mContainer.getStatsLogManager().logger().withItemInfo(taskView.getFirstItemInfo())
+ mContainer.getStatsLogManager().logger().withItemInfo(taskView.getItemInfo())
.log(LAUNCHER_TASK_LAUNCH_SWIPE_DOWN);
} else {
onTaskLaunchAnimationEnd(false);
@@ -5856,6 +5887,10 @@
mEnableDrawingLiveTile = enableDrawingLiveTile;
}
+ public boolean getEnableDrawingLiveTile() {
+ return mEnableDrawingLiveTile;
+ }
+
public void redrawLiveTile() {
runActionOnRemoteHandles(remoteTargetHandle -> {
TransformParams params = remoteTargetHandle.getTransformParams();
@@ -6156,7 +6191,7 @@
if (addDesktopButtonIndex != -1 && addDesktopButtonIndex < outPageScrolls.length) {
outPageScrolls[addDesktopButtonIndex] =
newPageScrolls[addDesktopButtonIndex] + Math.round(
- mAddDesktopButton.getTranslationX());
+ mAddDesktopButton.getGridTranslationX());
}
int lastTaskScroll = getLastTaskScroll(clearAllScroll, clearAllWidth);
@@ -6548,10 +6583,6 @@
private void setColorTint(float tintAmount) {
mColorTint = tintAmount;
- if (enableRefactorTaskThumbnail()) {
- mRecentsViewModel.setTintAmount(tintAmount);
- }
-
for (TaskView taskView : getTaskViews()) {
taskView.setColorTint(mColorTint, mTintingColor);
}
@@ -6889,6 +6920,19 @@
return Typeface.Builder.NORMAL_WEIGHT;
}
+ /**
+ * Creates the spring animations which run as a task settles back into its place in overview.
+ *
+ * <p>When a task dismiss is cancelled, the task will return to its original position via a
+ * spring animation.
+ */
+ public SpringAnimation createTaskDismissSettlingSpringAnimation(TaskView draggedTaskView,
+ float velocity, boolean isDismissing, SingleAxisSwipeDetector detector,
+ int dismissLength, Function0<Unit> onEndRunnable) {
+ return mUtils.createTaskDismissSettlingSpringAnimation(draggedTaskView, velocity,
+ isDismissing, detector, dismissLength, onEndRunnable);
+ }
+
public interface TaskLaunchListener {
void onTaskLaunched();
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java b/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
index b1a4808..e61d402 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
@@ -63,19 +63,6 @@
<T extends View> T getOverviewPanel();
/**
- * Returns the RootView
- */
- View getRootView();
-
- /**
- * Dispatches a generic motion event to the view hierarchy.
- * Returns the current RecentsViewContainer as context
- */
- default Context asContext() {
- return (Context) this;
- }
-
- /**
* @see Window.Callback#dispatchGenericMotionEvent(MotionEvent)
*/
boolean dispatchGenericMotionEvent(MotionEvent ev);
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
index dcb954a..f610335 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
@@ -19,12 +19,21 @@
import android.graphics.Rect
import android.view.View
import androidx.core.view.children
+import androidx.dynamicanimation.animation.FloatPropertyCompat
+import androidx.dynamicanimation.animation.SpringAnimation
+import androidx.dynamicanimation.animation.SpringForce
import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
+import com.android.launcher3.Flags.enableSeparateExternalDisplayTasks
+import com.android.launcher3.R
+import com.android.launcher3.touch.SingleAxisSwipeDetector
+import com.android.launcher3.util.DynamicResource
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
+import kotlin.math.abs
/**
* Helper class for [RecentsView]. This util class contains refactored and extracted functions from
@@ -52,6 +61,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 +82,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 +114,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 +231,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 +263,7 @@
outTopRowRect: Rect,
outBottomRowRect: Rect,
) {
- if (!(recentsView.mContainer as RecentsViewContainer).deviceProfile.isTablet) {
+ if (!getDeviceProfile().isTablet) {
getRowRect(getFirstTaskView(), getLastTaskView(), outTaskViewRowRect)
return
}
@@ -242,6 +301,58 @@
}
}
+ /**
+ * Creates the spring animations which run when a dragged task view in overview is released.
+ *
+ * <p>When a task dismiss is cancelled, the task will return to its original position via a
+ * spring animation.
+ */
+ fun createTaskDismissSettlingSpringAnimation(
+ draggedTaskView: TaskView?,
+ velocity: Float,
+ isDismissing: Boolean,
+ detector: SingleAxisSwipeDetector,
+ dismissLength: Int,
+ onEndRunnable: () -> Unit,
+ ): SpringAnimation? {
+ draggedTaskView ?: return null
+ val taskDismissFloatProperty =
+ FloatPropertyCompat.createFloatPropertyCompat(
+ draggedTaskView.secondaryDismissTranslationProperty
+ )
+ val rp = DynamicResource.provider(recentsView.mContainer)
+ return SpringAnimation(draggedTaskView, taskDismissFloatProperty)
+ .setSpring(
+ SpringForce()
+ .setDampingRatio(rp.getFloat(R.dimen.dismiss_task_trans_y_damping_ratio))
+ .setStiffness(rp.getFloat(R.dimen.dismiss_task_trans_y_stiffness))
+ )
+ .setStartVelocity(if (detector.isFling(velocity)) velocity else 0f)
+ .addUpdateListener { animation, value, _ ->
+ if (isDismissing && abs(value) >= abs(dismissLength)) {
+ // TODO(b/393553524): Remove 0 alpha, instead animate task fully off screen.
+ draggedTaskView.alpha = 0f
+ animation.cancel()
+ } else if (draggedTaskView.isRunningTask && recentsView.enableDrawingLiveTile) {
+ recentsView.runActionOnRemoteHandles { remoteTargetHandle ->
+ remoteTargetHandle.taskViewSimulator.taskSecondaryTranslation.value =
+ taskDismissFloatProperty.getValue(draggedTaskView)
+ }
+ recentsView.redrawLiveTile()
+ }
+ }
+ .addEndListener { _, _, _, _ ->
+ if (isDismissing) {
+ recentsView.dismissTask(
+ draggedTaskView,
+ /* animateTaskView = */ false,
+ /* removeTask = */ true,
+ )
+ }
+ onEndRunnable()
+ }
+ }
+
companion object {
val TEMP_RECT = Rect()
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
index 5de8d1c..b6f6bed 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,15 +104,9 @@
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)
+ get() = TaskViewItemInfo(taskView, this)
fun bind() {
digitalWellBeingToast?.bind(task, taskView, snapshotView, stagePosition)
@@ -141,7 +129,12 @@
}
}
- fun bindThumbnailView() {
+ // TODO(b/391842220): Cancel scope in onDetach instead of having a specific method for this.
+ fun destroyScopes() {
+ thumbnailView.destroyScopes()
+ }
+
+ private fun bindThumbnailView() {
taskThumbnailViewModel.bind(task.key.id)
}
@@ -158,4 +151,21 @@
digitalWellBeingToast?.let { addAccessibleChildToList(it, outChildren) }
overlay.addChildForAccessibility(outChildren)
}
+
+ fun setState(state: TaskData?, liveTile: Boolean, hasHeader: Boolean) {
+ thumbnailView.setState(
+ TaskUiStateMapper.toTaskThumbnailUiState(state, liveTile, hasHeader),
+ state?.taskId,
+ )
+ 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 99df84c..4b1b8dc 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -37,7 +37,6 @@
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,11 +49,13 @@
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
import com.android.launcher3.logging.StatsLogManager.LauncherEvent
import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.TaskViewItemInfo
import com.android.launcher3.testing.TestLogging
import com.android.launcher3.testing.shared.TestProtocol
import com.android.launcher3.util.CancellableTask
@@ -63,13 +64,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 +77,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 +89,18 @@
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.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,24 @@
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
+
+ /**
+ * A [TaskViewItemInfo] of this TaskView. The [firstTaskContainer] will be used to get some
+ * specific information like user, title etc of the Task. However, these task specific
+ * information will be skipped if the TaskView has no [taskContainers]. Note, please use
+ * [TaskContainer.itemInfo] for [TaskViewItemInfo] on a specific [TaskContainer].
+ */
+ val itemInfo: TaskViewItemInfo
+ get() = TaskViewItemInfo(this, firstTaskContainer)
protected val container: RecentsViewContainer =
RecentsViewContainer.containerFromContext(context)
@@ -192,7 +222,7 @@
get() =
pagedOrientationHandler.getPrimaryValue(DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y)
- protected val secondaryDismissTranslationProperty: FloatProperty<TaskView>
+ val secondaryDismissTranslationProperty: FloatProperty<TaskView>
get() =
pagedOrientationHandler.getSecondaryValue(DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y)
@@ -231,6 +261,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 +483,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 +641,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
@@ -617,6 +659,11 @@
taskContainers.forEach { it.destroy() }
}
+ fun destroyScopes() {
+ // TODO(b/391842220): Cancel scope in onDetach instead of having a specific method for this.
+ taskContainers.forEach { it.destroyScopes() }
+ }
+
// TODO: Clip-out the icon region from the thumbnail, since they are overlapping.
override fun hasOverlappingRendering() = false
@@ -630,13 +677,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
@@ -667,11 +707,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
@@ -710,6 +745,45 @@
?.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 +=
+ coroutineScope.launch { viewModel!!.state.collectLatest(::updateTaskViewState) }
+ }
+ }
+
+ 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,
@@ -733,6 +807,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()) {
@@ -843,7 +929,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() }
@@ -1009,7 +1095,7 @@
Log.d("b/310064698", "${taskIds.contentToString()} - onClick - callbackList: $callbackList")
container.statsLogManager
.logger()
- .withItemInfo(firstItemInfo)
+ .withItemInfo(itemInfo)
.log(LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP)
}
@@ -1113,6 +1199,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",
@@ -1120,11 +1207,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,
@@ -1163,18 +1250,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
@@ -1206,12 +1293,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
@@ -1238,14 +1325,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
@@ -1451,7 +1530,9 @@
/** Set a color tint on the snapshot and supporting views. */
open fun setColorTint(amount: Float, tintColor: Int) {
taskContainers.forEach {
- if (!enableRefactorTaskThumbnail()) {
+ if (enableRefactorTaskThumbnail()) {
+ it.updateTintAmount(amount)
+ } else {
it.thumbnailViewDeprecated.dimAlpha = amount
}
it.iconView.setIconColorTint(tintColor, amount)
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..de0da64 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
@@ -18,12 +18,16 @@
import android.content.ComponentName
import android.content.Intent
+import android.os.Process
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
@@ -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)
}
@@ -76,7 +84,7 @@
whenever(taskView.type).thenReturn(TaskViewType.SINGLE)
whenever(taskView.taskContainers).thenReturn(taskContainers)
- val taskViewItemInfo = TaskViewItemInfo(taskContainers[0])
+ val taskViewItemInfo = TaskViewItemInfo(taskContainers[0].taskView, taskContainers[0])
assertThat(taskViewItemInfo.taskViewAtom)
.isEqualTo(
@@ -97,7 +105,7 @@
whenever(taskView.type).thenReturn(TaskViewType.GROUPED)
whenever(taskView.taskContainers).thenReturn(taskContainers)
- val taskViewItemInfo = TaskViewItemInfo(taskContainers[0])
+ val taskViewItemInfo = TaskViewItemInfo(taskContainers[0].taskView, taskContainers[0])
assertThat(taskViewItemInfo.taskViewAtom)
.isEqualTo(
@@ -122,7 +130,7 @@
whenever(taskView.type).thenReturn(TaskViewType.DESKTOP)
whenever(taskView.taskContainers).thenReturn(taskContainers)
- val taskViewItemInfo = TaskViewItemInfo(taskContainers[0])
+ val taskViewItemInfo = TaskViewItemInfo(taskContainers[0].taskView, taskContainers[0])
assertThat(taskViewItemInfo.taskViewAtom)
.isEqualTo(
@@ -143,7 +151,7 @@
whenever(taskView.taskContainers).thenReturn(taskContainers)
whenever(userInfo.isPrivate).thenReturn(true)
- val taskViewItemInfo = TaskViewItemInfo(taskContainers[0])
+ val taskViewItemInfo = TaskViewItemInfo(taskContainers[0].taskView, taskContainers[0])
assertThat(taskViewItemInfo.taskViewAtom)
.isEqualTo(
@@ -158,6 +166,25 @@
.isEqualTo(FLAG_NOT_PINNABLE)
}
+ @Test
+ fun emptyDesktopTask() {
+ whenever(taskView.type).thenReturn(TaskViewType.DESKTOP)
+
+ val taskViewItemInfo = TaskViewItemInfo(taskView = taskView, taskContainer = null)
+
+ assertThat(taskViewItemInfo.taskViewAtom)
+ .isEqualTo(
+ createTaskViewAtom(
+ type = 2,
+ index = TASK_VIEW_INDEX,
+ componentName = "",
+ cardinality = 0,
+ )
+ )
+ assertThat(taskViewItemInfo.user).isEqualTo(Process.myUserHandle())
+ assertThat(taskViewItemInfo.intent).isNotNull()
+ }
+
private fun createTask(id: Int) =
Task(TaskKey(id, 0, Intent(), ComponentName(PACKAGE, CLASS), 0, 2000))
@@ -176,6 +203,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/TaskbarRecentAppsControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
index 8d8e62e..c792783 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
@@ -21,6 +21,7 @@
import android.content.Context
import android.content.Intent
import android.content.res.Resources
+import android.graphics.Rect
import android.os.Process
import android.os.UserHandle
import android.platform.test.annotations.EnableFlags
@@ -34,13 +35,18 @@
import com.android.launcher3.model.data.ItemInfo
import com.android.launcher3.model.data.TaskItemInfo
import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.taskbar.TaskbarRecentAppsController.TaskState
import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.android.launcher3.util.SplitConfigurationOptions
import com.android.quickstep.RecentsModel
import com.android.quickstep.RecentsModel.RecentTasksChangedListener
import com.android.quickstep.TaskIconCache
import com.android.quickstep.util.DesktopTask
import com.android.quickstep.util.GroupTask
+import com.android.quickstep.util.SingleTask
+import com.android.quickstep.util.SplitTask
import com.android.systemui.shared.recents.model.Task
+import com.android.wm.shell.shared.split.SplitScreenConstants
import com.google.common.truth.Truth.assertThat
import java.util.function.Consumer
import org.junit.Before
@@ -147,7 +153,7 @@
@Test
fun canShowRunningAndRecentAppsIsFalseAfterInit_getTasksOnlyCalledInInit() {
// getTasks() should have been called once from init().
- verify(mockRecentsModel, times(1)).getTasks(any<Consumer<List<GroupTask>>>())
+ verify(mockRecentsModel, times(1)).getTasks(any<Consumer<List<GroupTask>>>(), any())
recentAppsController.canShowRunningApps = false
recentAppsController.canShowRecentApps = false
prepareHotseatAndRunningAndRecentApps(
@@ -156,29 +162,28 @@
recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2),
)
// Verify that getTasks() was not called again after the init().
- verify(mockRecentsModel, times(1)).getTasks(any<Consumer<List<GroupTask>>>())
+ verify(mockRecentsModel, times(1)).getTasks(any<Consumer<List<GroupTask>>>(), any())
}
@Test
fun getDesktopItemState_nullItemInfo_returnsNotRunning() {
setInDesktopMode(true)
- assertThat(recentAppsController.getDesktopItemState(/* itemInfo= */ null))
- .isEqualTo(RunningAppState.NOT_RUNNING)
+ val taskState = recentAppsController.getDesktopItemState(/* itemInfo= */ null)
+ assertThat(taskState).isEqualTo(TaskState(RunningAppState.NOT_RUNNING))
}
@Test
fun getDesktopItemState_noItemPackage_returnsNotRunning() {
setInDesktopMode(true)
- assertThat(recentAppsController.getDesktopItemState(ItemInfo()))
- .isEqualTo(RunningAppState.NOT_RUNNING)
+ val taskState = recentAppsController.getDesktopItemState(ItemInfo())
+ assertThat(taskState).isEqualTo(TaskState(RunningAppState.NOT_RUNNING))
}
@Test
fun getDesktopItemState_noMatchingTasks_returnsNotRunning() {
setInDesktopMode(true)
- val itemInfo = createItemInfo("package")
- assertThat(recentAppsController.getDesktopItemState(itemInfo))
- .isEqualTo(RunningAppState.NOT_RUNNING)
+ val taskState = recentAppsController.getDesktopItemState(createItemInfo("package"))
+ assertThat(taskState).isEqualTo(TaskState(RunningAppState.NOT_RUNNING))
}
@Test
@@ -186,10 +191,10 @@
setInDesktopMode(true)
val visibleTask = createTask(id = 1, "visiblePackage", isVisible = true)
updateRecentTasks(runningTasks = listOf(visibleTask), recentTaskPackages = emptyList())
- val itemInfo = createItemInfo("visiblePackage")
- assertThat(recentAppsController.getDesktopItemState(itemInfo))
- .isEqualTo(RunningAppState.RUNNING)
+ val taskState = recentAppsController.getDesktopItemState(createItemInfo("visiblePackage"))
+
+ assertThat(taskState).isEqualTo(TaskState(RunningAppState.RUNNING, taskId = 1))
}
@Test
@@ -197,10 +202,10 @@
setInDesktopMode(true)
val minimizedTask = createTask(id = 1, "minimizedPackage", isVisible = false)
updateRecentTasks(runningTasks = listOf(minimizedTask), recentTaskPackages = emptyList())
- val itemInfo = createItemInfo("minimizedPackage")
- assertThat(recentAppsController.getDesktopItemState(itemInfo))
- .isEqualTo(RunningAppState.MINIMIZED)
+ val taskState = recentAppsController.getDesktopItemState(createItemInfo("minimizedPackage"))
+
+ assertThat(taskState).isEqualTo(TaskState(RunningAppState.MINIMIZED, taskId = 1))
}
@Test
@@ -214,10 +219,10 @@
),
recentTaskPackages = emptyList(),
)
- val itemInfo = createItemInfo("package")
- assertThat(recentAppsController.getDesktopItemState(itemInfo))
- .isEqualTo(RunningAppState.RUNNING)
+ val taskState = recentAppsController.getDesktopItemState(createItemInfo("package"))
+
+ assertThat(taskState).isEqualTo(TaskState(RunningAppState.RUNNING, taskId = 2))
}
@Test
@@ -231,10 +236,11 @@
),
recentTaskPackages = emptyList(),
)
- val itemInfo = createItemInfo("package", USER_HANDLE_2)
- assertThat(recentAppsController.getDesktopItemState(itemInfo))
- .isEqualTo(RunningAppState.NOT_RUNNING)
+ val taskState =
+ recentAppsController.getDesktopItemState(createItemInfo("package", USER_HANDLE_2))
+
+ assertThat(taskState).isEqualTo(TaskState(RunningAppState.NOT_RUNNING))
}
@Test
@@ -881,7 +887,7 @@
taskListChangeId
}
.whenever(mockRecentsModel)
- .getTasks(any<Consumer<List<GroupTask>>>())
+ .getTasks(any<Consumer<List<GroupTask>>>(), any())
recentTasksChangedListener?.onRecentTasksChanged()
}
@@ -912,15 +918,21 @@
return packageNames.map { packageName ->
if (packageName.startsWith("split")) {
val splitPackages = packageName.split("_")
- GroupTask(
+ SplitTask(
createTask(100, splitPackages[0]),
createTask(101, splitPackages[1]),
- /* splitBounds = */ null,
+ SplitConfigurationOptions.SplitBounds(
+ /* leftTopBounds = */ Rect(),
+ /* rightBottomBounds = */ Rect(),
+ /* leftTopTaskId = */ -1,
+ /* rightBottomTaskId = */ -1,
+ /* snapPosition = */ SplitScreenConstants.SNAP_TO_2_50_50,
+ ),
)
} else {
// Use the number at the end of the test packageName as the id.
val id = 1000 + packageName[packageName.length - 1].code
- GroupTask(createTask(id, packageName))
+ SingleTask(createTask(id, packageName))
}
}
}
@@ -945,7 +957,7 @@
}
private fun setInDesktopMode(inDesktopMode: Boolean) {
- whenever(taskbarControllers.taskbarDesktopModeController.areDesktopTasksVisible)
+ whenever(taskbarControllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar())
.thenReturn(inDesktopMode)
}
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/launcher3/taskbar/TaskbarViewTestUtil.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt
index e7f3523..df70b10 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt
@@ -28,6 +28,7 @@
import com.android.launcher3.taskbar.TaskbarIconType.OVERFLOW
import com.android.launcher3.taskbar.TaskbarIconType.RECENT
import com.android.quickstep.util.GroupTask
+import com.android.quickstep.util.SingleTask
import com.android.systemui.shared.recents.model.Task
import com.android.systemui.shared.recents.model.Task.TaskKey
import com.google.common.truth.FailureMetadata
@@ -56,7 +57,7 @@
/** Creates a list of fake recent tasks. */
fun createRecents(size: Int): List<GroupTask> {
return List(size) {
- GroupTask(
+ SingleTask(
Task().apply {
key =
TaskKey(
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt
index e150568..90c9553 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt
@@ -125,7 +125,8 @@
// Needs to be set on window context instead of sandbox context, because it does
// does not propagate between them. However, this change will impact created
// TaskbarActivityContext instances, since they wrap the window context.
- taskbarManager.windowContext.resources.configuration.setLayoutDirection(
+ // TODO: iterate through all window contexts and do this.
+ taskbarManager.primaryWindowContext.resources.configuration.setLayoutDirection(
RTL_LOCALE
)
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetCategoryFilterTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetCategoryFilterTest.kt
new file mode 100644
index 0000000..9b0a95a
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetCategoryFilterTest.kt
@@ -0,0 +1,133 @@
+/*
+ * 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.widget.picker
+
+import android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN
+import android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD
+import android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_NOT_KEYGUARD
+import android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class WidgetCategoryFilterTest {
+
+ @Test
+ fun filterValueZero_everythingMatches() {
+ val noFilter = WidgetCategoryFilter(categoryMask = 0)
+
+ noFilter.assertMatches(WIDGET_CATEGORY_HOME_SCREEN)
+ noFilter.assertMatches(WIDGET_CATEGORY_KEYGUARD)
+ noFilter.assertMatches(WIDGET_CATEGORY_NOT_KEYGUARD)
+ noFilter.assertMatches(WIDGET_CATEGORY_SEARCHBOX)
+ noFilter.assertMatches(WIDGET_CATEGORY_HOME_SCREEN or WIDGET_CATEGORY_KEYGUARD)
+ noFilter.assertMatches(WIDGET_CATEGORY_HOME_SCREEN or WIDGET_CATEGORY_NOT_KEYGUARD)
+ noFilter.assertMatches(
+ WIDGET_CATEGORY_HOME_SCREEN or WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_NOT_KEYGUARD
+ )
+ noFilter.assertMatches(
+ WIDGET_CATEGORY_HOME_SCREEN or WIDGET_CATEGORY_KEYGUARD or WIDGET_CATEGORY_NOT_KEYGUARD
+ )
+ noFilter.assertMatches(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_KEYGUARD)
+ }
+
+ @Test
+ fun includeHomeScreen_matchesOnlyIfHomeScreenExists() {
+ val filter = WidgetCategoryFilter(WIDGET_CATEGORY_HOME_SCREEN)
+
+ filter.assertMatches(WIDGET_CATEGORY_HOME_SCREEN)
+ filter.assertMatches(WIDGET_CATEGORY_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN)
+ filter.assertMatches(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_HOME_SCREEN)
+ filter.assertMatches(WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN)
+
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_KEYGUARD)
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_NOT_KEYGUARD)
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_SEARCHBOX)
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_KEYGUARD or WIDGET_CATEGORY_SEARCHBOX)
+ }
+
+ @Test
+ fun includeHomeScreenOrKeyguard_matchesIfEitherHomeScreenOrKeyguardExists() {
+ val filter = WidgetCategoryFilter(WIDGET_CATEGORY_HOME_SCREEN or WIDGET_CATEGORY_KEYGUARD)
+
+ filter.assertMatches(WIDGET_CATEGORY_HOME_SCREEN)
+ filter.assertMatches(WIDGET_CATEGORY_KEYGUARD)
+ filter.assertMatches(WIDGET_CATEGORY_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN)
+ filter.assertMatches(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_HOME_SCREEN)
+ filter.assertMatches(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_KEYGUARD)
+ filter.assertMatches(WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN)
+ filter.assertMatches(WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_KEYGUARD)
+
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_NOT_KEYGUARD)
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_SEARCHBOX)
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_NOT_KEYGUARD)
+ }
+
+ @Test
+ fun excludeNotKeyguard_doesNotMatchIfNotKeyguardExists() {
+ val filter = WidgetCategoryFilter(WIDGET_CATEGORY_NOT_KEYGUARD.inv())
+
+ filter.assertMatches(WIDGET_CATEGORY_HOME_SCREEN)
+ filter.assertMatches(WIDGET_CATEGORY_KEYGUARD)
+ filter.assertMatches(WIDGET_CATEGORY_SEARCHBOX)
+ filter.assertMatches(WIDGET_CATEGORY_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN)
+ filter.assertMatches(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_HOME_SCREEN)
+ filter.assertMatches(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_KEYGUARD)
+
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_NOT_KEYGUARD)
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN)
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_KEYGUARD)
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_NOT_KEYGUARD)
+ filter.assertDoesNotMatch(
+ WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN
+ )
+ }
+
+ @Test
+ fun multipleExclusions_doesNotMatchIfExcludedCategoriesExist() {
+ val filter =
+ WidgetCategoryFilter(
+ WIDGET_CATEGORY_HOME_SCREEN.inv() and WIDGET_CATEGORY_NOT_KEYGUARD.inv()
+ )
+
+ filter.assertMatches(WIDGET_CATEGORY_SEARCHBOX)
+ filter.assertMatches(WIDGET_CATEGORY_KEYGUARD)
+ filter.assertMatches(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_KEYGUARD)
+
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_HOME_SCREEN)
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN)
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_HOME_SCREEN)
+
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_NOT_KEYGUARD)
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN)
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_KEYGUARD)
+ filter.assertDoesNotMatch(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_NOT_KEYGUARD)
+ filter.assertDoesNotMatch(
+ WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN
+ )
+ }
+
+ private fun WidgetCategoryFilter.assertMatches(category: Int) {
+ assertThat(matches(category)).isTrue()
+ }
+
+ private fun WidgetCategoryFilter.assertDoesNotMatch(category: Int) {
+ assertThat(matches(category)).isFalse()
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
index 542eb64..0c74610 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
@@ -18,6 +18,8 @@
import static com.android.quickstep.AbsSwipeUpHandler.STATE_HANDLER_INVALIDATED;
import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION;
+import static com.android.wm.shell.shared.split.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertTrue;
@@ -68,6 +70,7 @@
import com.android.quickstep.views.RecentsViewContainer;
import com.android.systemui.shared.Flags;
import com.android.systemui.shared.system.InputConsumerController;
+import com.android.wm.shell.shared.split.SplitBounds;
import com.google.android.msdl.data.model.MSDLToken;
@@ -140,6 +143,12 @@
public void setUpAnimationTargets() {
Bundle extras = new Bundle();
extras.putBoolean(KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION, true);
+ extras.putParcelable(KEY_EXTRA_SPLIT_BOUNDS, new SplitBounds(
+ /* leftTopBounds = */ new Rect(),
+ /* rightBottomBounds = */ new Rect(),
+ /* leftTopTaskId = */ -1,
+ /* rightBottomTaskId = */ -1,
+ /* snapPosition = */ SNAP_TO_2_50_50));
mRecentsAnimationTargets = new RecentsAnimationTargets(
new RemoteAnimationTarget[] {mRemoteAnimationTarget},
new RemoteAnimationTarget[] {mRemoteAnimationTarget},
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
index f05b422..af741f6 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
@@ -86,17 +86,11 @@
whenever(displayManager.displays).thenReturn(arrayOf(display))
sandboxContext.initDaggerComponent(
- DaggerTestComponent.builder().bindSystemUiProxy(systemUiProxy)
+ DaggerTestComponent.builder()
+ .bindSystemUiProxy(systemUiProxy)
+ .bindRotationHelper(mock(RotationTouchHelper::class.java))
+ .bindRecentsState(mock(RecentsAnimationDeviceState::class.java))
)
- sandboxContext.putObject(
- RotationTouchHelper.INSTANCE,
- mock(RotationTouchHelper::class.java),
- )
- sandboxContext.putObject(
- RecentsAnimationDeviceState.INSTANCE,
- mock(RecentsAnimationDeviceState::class.java),
- )
-
gestureState = spy(GestureState(OverviewComponentObserver.INSTANCE.get(sandboxContext), 0))
underTest =
@@ -117,20 +111,14 @@
gestureState.setTrackpadGestureType(GestureState.TrackpadGestureType.THREE_FINGER)
underTest.onGestureEnded(flingSpeed, PointF())
verify(systemUiProxy)
- .updateContextualEduStats(
- /* isTrackpadGesture= */ eq(true),
- eq(GestureType.HOME),
- )
+ .updateContextualEduStats(/* isTrackpadGesture= */ eq(true), eq(GestureType.HOME))
}
@Test
fun goHomeFromAppByTouch_updateEduStats() {
underTest.onGestureEnded(flingSpeed, PointF())
verify(systemUiProxy)
- .updateContextualEduStats(
- /* isTrackpadGesture= */ eq(false),
- eq(GestureType.HOME),
- )
+ .updateContextualEduStats(/* isTrackpadGesture= */ eq(false), eq(GestureType.HOME))
}
}
@@ -141,6 +129,10 @@
interface Builder : LauncherAppComponent.Builder {
@BindsInstance fun bindSystemUiProxy(proxy: SystemUiProxy): Builder
+ @BindsInstance fun bindRotationHelper(helper: RotationTouchHelper): Builder
+
+ @BindsInstance fun bindRecentsState(state: RecentsAnimationDeviceState): Builder
+
override fun build(): TestComponent
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java
index ccfe36d..c399bdb 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java
@@ -16,11 +16,17 @@
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;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@@ -33,6 +39,10 @@
import android.app.TaskInfo;
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;
@@ -43,8 +53,11 @@
import com.android.quickstep.views.TaskViewType;
import com.android.systemui.shared.recents.model.Task;
import com.android.wm.shell.shared.GroupedTaskInfo;
+import com.android.wm.shell.shared.split.SplitBounds;
+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;
@@ -60,6 +73,8 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
public class RecentTasksListTest {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock
private Context mContext;
@@ -97,7 +112,12 @@
@Test
public void loadTasksInBackground_onlyKeys_noValidTaskDescription() throws Exception {
GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forSplitTasks(
- new RecentTaskInfo(), new RecentTaskInfo(), null);
+ new RecentTaskInfo(), new RecentTaskInfo(), new SplitBounds(
+ /* leftTopBounds = */ new Rect(),
+ /* rightBottomBounds = */ new Rect(),
+ /* leftTopTaskId = */ -1,
+ /* rightBottomTaskId = */ -1,
+ /* snapPosition = */ SplitScreenConstants.SNAP_TO_2_50_50));
when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
.thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
@@ -127,7 +147,13 @@
task1.taskDescription = new ActivityManager.TaskDescription(taskDescription);
RecentTaskInfo task2 = new RecentTaskInfo();
task2.taskDescription = new ActivityManager.TaskDescription();
- GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forSplitTasks(task1, task2, null);
+ GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forSplitTasks(task1, task2,
+ new SplitBounds(
+ /* leftTopBounds = */ new Rect(),
+ /* rightBottomBounds = */ new Rect(),
+ /* leftTopTaskId = */ -1,
+ /* rightBottomTaskId = */ -1,
+ /* snapPosition = */ SplitScreenConstants.SNAP_TO_2_50_50));
when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
.thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
@@ -142,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()))
@@ -158,19 +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
- public void loadTasksInBackground_freeformTask_onlyMinimizedTasks_doesNotCreateDesktopTask()
+ @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);
@@ -180,12 +246,22 @@
List<GroupTask> taskList = mRecentTasksList.loadTasksInBackground(
Integer.MAX_VALUE /* numTasks */, -1 /* requestId */, false /* loadKeysOnly */);
- assertEquals(0, taskList.size());
+ assertEquals(1, taskList.size());
+ assertEquals(TaskViewType.DESKTOP, taskList.get(0).taskViewType);
+ List<Task> actualFreeformTasks = taskList.get(0).getTasks();
+ assertEquals(3, actualFreeformTasks.size());
+ assertEquals(1, actualFreeformTasks.get(0).key.id);
+ assertTrue(actualFreeformTasks.get(0).isMinimized);
+ assertEquals(4, actualFreeformTasks.get(1).key.id);
+ assertTrue(actualFreeformTasks.get(1).isMinimized);
+ assertEquals(5, actualFreeformTasks.get(2).key.id);
+ 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/RecentsAnimationDeviceStateTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
index 0245908..b652ee8 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
@@ -1,9 +1,8 @@
package com.android.quickstep
-import android.content.Context
import androidx.test.annotation.UiThreadTest
-import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.SmallTest
+import com.android.launcher3.dagger.LauncherComponentProvider
import com.android.launcher3.util.DisplayController.CHANGE_DENSITY
import com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE
import com.android.launcher3.util.DisplayController.CHANGE_ROTATION
@@ -12,6 +11,7 @@
import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
import com.android.launcher3.util.LauncherMultivalentJUnit
import com.android.launcher3.util.NavigationMode
+import com.android.launcher3.util.SandboxApplication
import com.android.quickstep.util.GestureExclusionManager
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING
@@ -27,6 +27,7 @@
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
@@ -43,21 +44,32 @@
@RunWith(LauncherMultivalentJUnit::class)
class RecentsAnimationDeviceStateTest {
+ @get:Rule val context = SandboxApplication()
+
@Mock private lateinit var exclusionManager: GestureExclusionManager
@Mock private lateinit var info: Info
- private val context = ApplicationProvider.getApplicationContext() as Context
private lateinit var underTest: RecentsAnimationDeviceState
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- underTest = RecentsAnimationDeviceState(context, exclusionManager)
+
+ val component = LauncherComponentProvider.get(context)
+ underTest =
+ RecentsAnimationDeviceState(
+ context,
+ exclusionManager,
+ component.displayController,
+ component.contextualSearchStateManager,
+ component.rotationTouchHelper,
+ component.settingsCache,
+ component.daggerSingletonTracker,
+ )
}
@After
fun tearDown() {
- underTest.close()
UI_HELPER_EXECUTOR.submit {}.get()
MAIN_EXECUTOR.submit {}.get()
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsModelTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsModelTest.java
index a5c60ce..722e1da 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsModelTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsModelTest.java
@@ -32,6 +32,7 @@
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Rect;
import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.annotation.UiThreadTest;
@@ -42,9 +43,13 @@
import com.android.launcher3.R;
import com.android.launcher3.graphics.ThemeManager;
import com.android.launcher3.icons.IconProvider;
+import com.android.launcher3.util.LockedUserState;
+import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SplitTask;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.TaskStackChangeListeners;
+import com.android.wm.shell.shared.split.SplitScreenConstants;
import org.junit.Before;
import org.junit.Rule;
@@ -72,6 +77,12 @@
@Mock
private HighResLoadingState mHighResLoadingState;
+ @Mock
+ private LockedUserState mLockedUserState;
+
+ @Mock
+ private ThemeManager mThemeManager;
+
private RecentsModel mRecentsModel;
private RecentTasksList.TaskLoadResult mTaskResult;
@@ -98,7 +109,7 @@
mRecentsModel = new RecentsModel(mContext, mTasksList, mock(TaskIconCache.class),
mThumbnailCache, mock(IconProvider.class), mock(TaskStackChangeListeners.class),
- mock(ThemeManager.class));
+ mLockedUserState, () -> mThemeManager);
mResource = mock(Resources.class);
when(mResource.getInteger((R.integer.recentsThumbnailCacheSize))).thenReturn(3);
@@ -163,6 +174,17 @@
.updateThumbnailInCache(any(), anyBoolean());
}
+ @Test
+ public void themeCallbackAttachedOnUnlock() {
+ verify(mThemeManager, never()).addChangeListener(any());
+
+ ArgumentCaptor<Runnable> callbackCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(mLockedUserState).runOnUserUnlocked(callbackCaptor.capture());
+
+ callbackCaptor.getAllValues().forEach(Runnable::run);
+ verify(mThemeManager, times(1)).addChangeListener(any());
+ }
+
private RecentTasksList.TaskLoadResult getTaskResult() {
RecentTasksList.TaskLoadResult allTasks = new RecentTasksList.TaskLoadResult(0, false, 1);
ActivityManager.RecentTaskInfo taskInfo1 = new ActivityManager.RecentTaskInfo();
@@ -173,7 +195,13 @@
Task.TaskKey taskKey2 = new Task.TaskKey(taskInfo2);
Task task2 = Task.from(taskKey2, taskInfo2, false);
- allTasks.add(new GroupTask(task1, task2, null));
+ allTasks.add(
+ new SplitTask(task1, task2, new SplitConfigurationOptions.SplitBounds(
+ /* leftTopBounds = */ new Rect(),
+ /* rightBottomBounds = */ new Rect(),
+ /* leftTopTaskId = */ -1,
+ /* rightBottomTaskId = */ -1,
+ /* snapPosition = */ SplitScreenConstants.SNAP_TO_2_50_50)));
return allTasks;
}
}
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/data/TasksRepositoryTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
index b6cf5bd..823f808 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
@@ -19,13 +19,17 @@
import android.content.ComponentName
import android.content.Intent
import android.graphics.Bitmap
+import android.graphics.Rect
import android.graphics.drawable.Drawable
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.util.SplitConfigurationOptions
import com.android.launcher3.util.TestDispatcherProvider
import com.android.quickstep.util.DesktopTask
-import com.android.quickstep.util.GroupTask
+import com.android.quickstep.util.SingleTask
+import com.android.quickstep.util.SplitTask
import com.android.systemui.shared.recents.model.Task
import com.android.systemui.shared.recents.model.ThumbnailData
+import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
@@ -48,8 +52,18 @@
private val tasks = (0..5).map(::createTaskWithId)
private val defaultTaskList =
listOf(
- GroupTask(tasks[0]),
- GroupTask(tasks[1], tasks[2], null),
+ SingleTask(tasks[0]),
+ SplitTask(
+ tasks[1],
+ tasks[2],
+ SplitConfigurationOptions.SplitBounds(
+ /* leftTopBounds = */ Rect(),
+ /* rightBottomBounds = */ Rect(),
+ /* leftTopTaskId = */ -1,
+ /* rightBottomTaskId = */ -1,
+ /* snapPosition = */ SNAP_TO_2_50_50,
+ ),
+ ),
DesktopTask(tasks.subList(3, 6)),
)
private val recentsModel = FakeRecentTasksDataSource()
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/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt
index 108cfb5..fa043b9 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt
@@ -21,7 +21,6 @@
import android.graphics.Rect
import com.android.launcher3.util.LauncherMultivalentJUnit
import com.android.launcher3.util.SplitConfigurationOptions
-import com.android.quickstep.views.TaskViewType
import com.android.systemui.shared.recents.model.Task
import com.android.wm.shell.shared.split.SplitScreenConstants
import com.google.common.truth.Truth.assertThat
@@ -33,28 +32,28 @@
@Test
fun testGroupTask_sameInstance_isEqual() {
- val task = GroupTask(createTask(1))
+ val task = SingleTask(createTask(1))
assertThat(task).isEqualTo(task)
}
@Test
fun testGroupTask_identicalConstructor_isEqual() {
- val task1 = GroupTask(createTask(1))
- val task2 = GroupTask(createTask(1))
+ val task1 = SingleTask(createTask(1))
+ val task2 = SingleTask(createTask(1))
assertThat(task1).isEqualTo(task2)
}
@Test
fun testGroupTask_copy_isEqual() {
- val task1 = GroupTask(createTask(1))
+ val task1 = SingleTask(createTask(1))
val task2 = task1.copy()
assertThat(task1).isEqualTo(task2)
}
@Test
fun testGroupTask_differentId_isNotEqual() {
- val task1 = GroupTask(createTask(1))
- val task2 = GroupTask(createTask(2))
+ val task1 = SingleTask(createTask(1))
+ val task2 = SingleTask(createTask(2))
assertThat(task1).isNotEqualTo(task2)
}
@@ -66,10 +65,10 @@
Rect(),
1,
2,
- SplitScreenConstants.SNAP_TO_2_50_50
+ SplitScreenConstants.SNAP_TO_2_50_50,
)
- val task1 = GroupTask(createTask(1), createTask(2), splitBounds, TaskViewType.GROUPED)
- val task2 = GroupTask(createTask(1), createTask(2), splitBounds, TaskViewType.GROUPED)
+ val task1 = SplitTask(createTask(1), createTask(2), splitBounds)
+ val task2 = SplitTask(createTask(1), createTask(2), splitBounds)
assertThat(task1).isEqualTo(task2)
}
@@ -81,7 +80,7 @@
Rect(),
1,
2,
- SplitScreenConstants.SNAP_TO_2_50_50
+ SplitScreenConstants.SNAP_TO_2_50_50,
)
val splitBounds2 =
SplitConfigurationOptions.SplitBounds(
@@ -89,17 +88,17 @@
Rect(),
1,
2,
- SplitScreenConstants.SNAP_TO_2_33_66
+ SplitScreenConstants.SNAP_TO_2_33_66,
)
- val task1 = GroupTask(createTask(1), createTask(2), splitBounds1, TaskViewType.GROUPED)
- val task2 = GroupTask(createTask(1), createTask(2), splitBounds2, TaskViewType.GROUPED)
+ val task1 = SplitTask(createTask(1), createTask(2), splitBounds1)
+ val task2 = SplitTask(createTask(1), createTask(2), splitBounds2)
assertThat(task1).isNotEqualTo(task2)
}
@Test
fun testGroupTask_differentType_isNotEqual() {
- val task1 = GroupTask(createTask(1), null, null, TaskViewType.SINGLE)
- val task2 = GroupTask(createTask(1), null, null, TaskViewType.DESKTOP)
+ val task1 = SingleTask(createTask(1))
+ val task2 = DesktopTask(listOf(createTask(1)))
assertThat(task1).isNotEqualTo(task2)
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
index 708273e..e4bdba5 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
@@ -102,12 +102,12 @@
fun activeTasks_noMatchingTasks() {
val nonMatchingComponent = ComponentKey(ComponentName("no", "match"), primaryUserHandle)
val groupTask1 =
- generateGroupTask(
+ generateSplitTask(
ComponentName("pomegranate", "juice"),
ComponentName("pumpkin", "pie"),
)
val groupTask2 =
- generateGroupTask(
+ generateSplitTask(
ComponentName("hotdog", "juice"),
ComponentName("personal", "computer"),
)
@@ -143,12 +143,12 @@
val matchingComponent =
ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
val groupTask1 =
- generateGroupTask(
+ generateSplitTask(
ComponentName(matchingPackage, matchingClass),
ComponentName("pomegranate", "juice"),
)
val groupTask2 =
- generateGroupTask(
+ generateSplitTask(
ComponentName("pumpkin", "pie"),
ComponentName("personal", "computer"),
)
@@ -170,7 +170,7 @@
it[0].key.baseIntent.component?.className,
matchingClass,
)
- assertEquals(it[0], groupTask1.task1)
+ assertEquals(it[0], groupTask1.topLeftTask)
}
// Capture callback from recentsModel#getTasks()
@@ -196,12 +196,12 @@
val nonPrimaryUserComponent =
ComponentKey(ComponentName(matchingPackage, matchingClass), nonPrimaryUserHandle)
val groupTask1 =
- generateGroupTask(
+ generateSplitTask(
ComponentName(matchingPackage, matchingClass),
ComponentName("pomegranate", "juice"),
)
val groupTask2 =
- generateGroupTask(
+ generateSplitTask(
ComponentName("pumpkin", "pie"),
ComponentName("personal", "computer"),
)
@@ -237,14 +237,14 @@
val nonPrimaryUserComponent =
ComponentKey(ComponentName(matchingPackage, matchingClass), nonPrimaryUserHandle)
val groupTask1 =
- generateGroupTask(
+ generateSplitTask(
ComponentName(matchingPackage, matchingClass),
nonPrimaryUserHandle,
ComponentName("pomegranate", "juice"),
nonPrimaryUserHandle,
)
val groupTask2 =
- generateGroupTask(
+ generateSplitTask(
ComponentName("pumpkin", "pie"),
ComponentName("personal", "computer"),
)
@@ -267,7 +267,7 @@
matchingClass,
)
assertEquals("userId mismatched", it[0].key.userId, nonPrimaryUserHandle.identifier)
- assertEquals(it[0], groupTask1.task1)
+ assertEquals(it[0], groupTask1.topLeftTask)
}
// Capture callback from recentsModel#getTasks()
@@ -293,12 +293,12 @@
val matchingComponent =
ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
val groupTask1 =
- generateGroupTask(
+ generateSplitTask(
ComponentName(matchingPackage, matchingClass),
ComponentName("pumpkin", "pie"),
)
val groupTask2 =
- generateGroupTask(
+ generateSplitTask(
ComponentName("pomegranate", "juice"),
ComponentName(matchingPackage, matchingClass),
)
@@ -320,7 +320,7 @@
it[0].key.baseIntent.component?.className,
matchingClass,
)
- assertEquals(it[0], groupTask1.task1)
+ assertEquals(it[0], groupTask1.topLeftTask)
}
// Capture callback from recentsModel#getTasks()
@@ -348,9 +348,9 @@
ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
val groupTask1 =
- generateGroupTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
+ generateSplitTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
val groupTask2 =
- generateGroupTask(
+ generateSplitTask(
ComponentName("pomegranate", "juice"),
ComponentName(matchingPackage, matchingClass),
)
@@ -374,7 +374,7 @@
it[1].key.baseIntent.component?.className,
matchingClass,
)
- assertEquals(it[1], groupTask2.task2)
+ assertEquals(it[1], groupTask2.bottomRightTask)
}
// Capture callback from recentsModel#getTasks()
@@ -401,9 +401,9 @@
ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
val groupTask1 =
- generateGroupTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
+ generateSplitTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
val groupTask2 =
- generateGroupTask(
+ generateSplitTask(
ComponentName("pomegranate", "juice"),
ComponentName(matchingPackage, matchingClass),
)
@@ -426,7 +426,7 @@
it[0].key.baseIntent.component?.className,
matchingClass,
)
- assertEquals(it[0], groupTask2.task2)
+ assertEquals(it[0], groupTask2.bottomRightTask)
assertNull("No tasks should have matched", it[1] /*task*/)
}
@@ -454,12 +454,12 @@
ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
val groupTask1 =
- generateGroupTask(
+ generateSplitTask(
ComponentName(matchingPackage, matchingClass),
ComponentName("pumpkin", "pie"),
)
val groupTask2 =
- generateGroupTask(
+ generateSplitTask(
ComponentName("pomegranate", "juice"),
ComponentName(matchingPackage, matchingClass),
)
@@ -482,7 +482,7 @@
it[0].key.baseIntent.component?.className,
matchingClass,
)
- assertEquals(it[0], groupTask1.task1)
+ assertEquals(it[0], groupTask1.topLeftTask)
assertEquals(
"ComponentName package mismatched",
it[1].key.baseIntent.component?.packageName,
@@ -493,7 +493,7 @@
it[1].key.baseIntent.component?.className,
matchingClass,
)
- assertEquals(it[1], groupTask2.task2)
+ assertEquals(it[1], groupTask2.bottomRightTask)
}
// Capture callback from recentsModel#getTasks()
@@ -524,14 +524,14 @@
ComponentKey(ComponentName(matchingPackage2, matchingClass2), primaryUserHandle)
val groupTask1 =
- generateGroupTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
+ generateSplitTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
val groupTask2 =
- generateGroupTask(
+ generateSplitTask(
ComponentName(matchingPackage2, matchingClass2),
ComponentName(matchingPackage, matchingClass),
)
val groupTask3 =
- generateGroupTask(
+ generateSplitTask(
ComponentName("hotdog", "pie"),
ComponentName(matchingPackage, matchingClass),
)
@@ -545,7 +545,7 @@
val taskConsumer =
Consumer<Array<Task>> {
assertEquals("Expected array length 2", 2, it.size)
- assertEquals("Found wrong task", it[0], groupTask2.task1)
+ assertEquals("Found wrong task", it[0], groupTask2.topLeftTask)
}
// Capture callback from recentsModel#getTasks()
@@ -640,11 +640,11 @@
verify(recentsView, times(0)).resetDesktopTaskFromSplitSelectState()
}
- // Generate GroupTask with default userId.
- private fun generateGroupTask(
+ /** Generates a [SplitTask] with default userId. */
+ private fun generateSplitTask(
task1ComponentName: ComponentName,
task2ComponentName: ComponentName,
- ): GroupTask {
+ ): SplitTask {
val task1 = Task()
var taskInfo = ActivityManager.RunningTaskInfo()
taskInfo.taskId = getUniqueId()
@@ -660,20 +660,26 @@
intent.component = task2ComponentName
taskInfo.baseIntent = intent
task2.key = Task.TaskKey(taskInfo)
- return GroupTask(
+ return SplitTask(
task1,
task2,
- SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1, SNAP_TO_2_50_50),
+ SplitConfigurationOptions.SplitBounds(
+ /* leftTopBounds = */ Rect(),
+ /* rightBottomBounds = */ Rect(),
+ /* leftTopTaskId = */ task1.key.id,
+ /* rightBottomTaskId = */ task2.key.id,
+ /* snapPosition = */ SNAP_TO_2_50_50,
+ ),
)
}
- // Generate GroupTask with custom user handles.
- private fun generateGroupTask(
+ /** Generates a [SplitTask] with custom user handles. */
+ private fun generateSplitTask(
task1ComponentName: ComponentName,
userHandle1: UserHandle,
task2ComponentName: ComponentName,
userHandle2: UserHandle,
- ): GroupTask {
+ ): SplitTask {
val task1 = Task()
var taskInfo = ActivityManager.RunningTaskInfo()
taskInfo.taskId = getUniqueId()
@@ -692,10 +698,16 @@
intent.component = task2ComponentName
taskInfo.baseIntent = intent
task2.key = Task.TaskKey(taskInfo)
- return GroupTask(
+ return SplitTask(
task1,
task2,
- SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1, SNAP_TO_2_50_50),
+ SplitConfigurationOptions.SplitBounds(
+ /* leftTopBounds = */ Rect(),
+ /* rightBottomBounds = */ Rect(),
+ /* leftTopTaskId = */ task1.key.id,
+ /* rightBottomTaskId = */ task2.key.id,
+ /* snapPosition = */ SNAP_TO_2_50_50,
+ ),
)
}
}
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/InputConsumerUtilsTest.java b/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java
index 3c5e1e8..e2ca91a 100644
--- a/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java
@@ -16,8 +16,6 @@
package com.android.quickstep;
-import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
-
import static com.android.quickstep.InputConsumerUtils.newBaseConsumer;
import static com.android.quickstep.InputConsumerUtils.newConsumer;
@@ -40,6 +38,9 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.anim.AnimatedFloat;
+import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherAppModule;
+import com.android.launcher3.dagger.LauncherAppSingleton;
import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.taskbar.TaskbarManager;
import com.android.launcher3.taskbar.bubbles.BubbleBarController;
@@ -54,7 +55,7 @@
import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController;
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController;
import com.android.launcher3.util.LockedUserState;
-import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.SandboxApplication;
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
import com.android.quickstep.inputconsumers.BubbleBarInputConsumer;
@@ -75,6 +76,9 @@
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.shared.system.InputMonitorCompat;
+import dagger.BindsInstance;
+import dagger.Component;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -93,8 +97,8 @@
@RunWith(AndroidJUnit4.class)
public class InputConsumerUtilsTest {
- @NonNull private final MainThreadInitializedObject.SandboxContext mContext =
- new MainThreadInitializedObject.SandboxContext(getApplicationContext());
+ @Rule public final SandboxApplication mContext = new SandboxApplication();
+
@NonNull private final InputMonitorCompat mInputMonitorCompat = new InputMonitorCompat("", 0);
private TaskAnimationManager mTaskAnimationManager;
@@ -125,10 +129,12 @@
}
@Before
- public void setupMainThreadInitializedObjects() {
- mContext.putObject(LockedUserState.INSTANCE, mLockedUserState);
- mContext.putObject(RotationTouchHelper.INSTANCE, mock(RotationTouchHelper.class));
- mContext.putObject(RecentsAnimationDeviceState.INSTANCE, mDeviceState);
+ public void setupDaggerGraphOverrides() {
+ mContext.initDaggerComponent(DaggerInputConsumerUtilsTest_TestComponent
+ .builder()
+ .bindLockedState(mLockedUserState)
+ .bindRotationHelper(mock(RotationTouchHelper.class))
+ .bindRecentsState(mDeviceState));
}
@Before
@@ -595,4 +601,18 @@
return bubbleControllers;
}
+
+ @LauncherAppSingleton
+ @Component(modules = {LauncherAppModule.class})
+ interface TestComponent extends LauncherAppComponent {
+ @Component.Builder
+ interface Builder extends LauncherAppComponent.Builder {
+ @BindsInstance Builder bindLockedState(LockedUserState state);
+ @BindsInstance Builder bindRotationHelper(RotationTouchHelper helper);
+ @BindsInstance Builder bindRecentsState(RecentsAnimationDeviceState state);
+
+ @Override
+ TestComponent build();
+ }
+ }
}
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/layout/work_mode_utility_view.xml b/res/layout/work_mode_utility_view.xml
index fc112ce..b68ff3e 100644
--- a/res/layout/work_mode_utility_view.xml
+++ b/res/layout/work_mode_utility_view.xml
@@ -14,6 +14,7 @@
~ limitations under the License.
-->
<com.android.launcher3.allapps.WorkUtilityView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/work_utility_view"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:orientation="vertical"
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index a925866..2b7ec21 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -24,7 +24,7 @@
<string name="activity_not_found" msgid="8071924732094499514">"لم يتم تثبيت التطبيق."</string>
<string name="activity_not_available" msgid="7456344436509528827">"التطبيق ليس متاحًا"</string>
<string name="safemode_shortcut_error" msgid="9160126848219158407">"تم إيقاف التطبيق الذي تم تنزيله في الوضع الآمن"</string>
- <string name="safemode_widget_error" msgid="4863470563535682004">"الأدوات غير مفعّلة في الوضع الآمن"</string>
+ <string name="safemode_widget_error" msgid="4863470563535682004">"التطبيقات المصغَّرة غير مفعّلة في الوضع الآمن"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"الاختصار غير متاح"</string>
<string name="home_screen" msgid="5629429142036709174">"الشاشة الرئيسية"</string>
<string name="set_default_home_app" msgid="5808906607627586381">"يمكن ضبط \"<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>\" كتطبيق الشاشة الرئيسية التلقائي من خلال \"الإعدادات\""</string>
@@ -38,13 +38,13 @@
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"لا يمكن استخدام هذين التطبيقَين في الوقت نفسه على هذا الجهاز"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"افتح الجهاز لاستخدام هذين التطبيقَين في الوقت نفسه"</string>
<string name="app_pair_not_available" msgid="3556767440808032031">"ميزة \"استخدام تطبيقين في الوقت نفسه\" غير متوفّرة"</string>
- <string name="long_press_widget_to_add" msgid="3587712543577675817">"انقر مع الاستمرار لنقل أداة."</string>
- <string name="long_accessible_way_to_add" msgid="2733588281439571974">"انقر مرتين مع تثبيت إصبعك لنقل أداة أو استخدام الإجراءات المخصّصة."</string>
+ <string name="long_press_widget_to_add" msgid="3587712543577675817">"انقر مع الاستمرار لنقل تطبيق مصغَّر."</string>
+ <string name="long_accessible_way_to_add" msgid="2733588281439571974">"انقر مرتين مع تثبيت إصبعك لنقل تطبيق مصغَّر أو استخدام الإجراءات المخصّصة."</string>
<string name="widget_picker_widget_options_button_description" msgid="4770099264476852363">"خيارات إضافية"</string>
<string name="widget_picker_show_all_widgets_menu_item_title" msgid="9023638224586908119">"عرض كل التطبيقات المصغّرة"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"العرض %1$d الطول %2$d"</string>
- <string name="widget_preview_context_description" msgid="9045841361655787574">"أداة <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
+ <string name="widget_preview_context_description" msgid="9045841361655787574">"التطبيق المصغَّر <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"التطبيق المصغّرة \"<xliff:g id="WIDGET_NAME">%1$s</xliff:g>\"، بعرض %2$d وارتفاع %3$d"</string>
<string name="add_item_request_drag_hint" msgid="8730547755622776606">"يُرجى النقر مع الاستمرار على التطبيق المصغّر لنقله إلى الشاشة الرئيسية"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"إضافة إلى الشاشة الرئيسية"</string>
@@ -62,7 +62,7 @@
<string name="widget_button_text" msgid="2880537293434387943">"التطبيقات المصغّرة"</string>
<string name="widgets_full_sheet_search_bar_hint" msgid="8484659090860596457">"بحث"</string>
<string name="widgets_full_sheet_cancel_button_description" msgid="5766167035728653605">"محو النص من مربّع البحث"</string>
- <string name="no_widgets_available" msgid="4337693382501046170">"الأدوات والاختصارات غير متاحة."</string>
+ <string name="no_widgets_available" msgid="4337693382501046170">"التطبيقات المصغَّرة والاختصارات غير متاحة."</string>
<string name="no_search_results" msgid="3787956167293097509">"لم يتم العثور على تطبيقات مصغّرة أو اختصارات."</string>
<string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"التطبيقات الشخصية"</string>
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"تطبيقات العمل"</string>
@@ -75,8 +75,8 @@
<string name="widgets_list_expand_button_label" msgid="7912016136574932622">"عرض الكل"</string>
<string name="widgets_list_expand_button_content_description" msgid="4600513860973450888">"عرض كل التطبيقات المصغّرة"</string>
<string name="widgets_list_expanded" msgid="7374857868788557730">"جارٍ عرض كل التطبيقات المصغّرة"</string>
- <string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"انقر لتغيير إعدادات الأداة"</string>
- <string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"تغيير إعدادات الأداة"</string>
+ <string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"انقر لتغيير إعدادات التطبيق المصغَّر"</string>
+ <string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"تغيير إعدادات التطبيق المصغَّر"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"بحث في التطبيقات"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"جارٍ تحميل التطبيقات…"</string>
<string name="all_apps_no_search_results" msgid="3200346862396363786">"لم يتم العثور على أي تطبيقات تتطابق مع \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
@@ -108,7 +108,7 @@
<string name="permdesc_read_settings" msgid="4208061150510996676">"يسمح هذا الإذن للتطبيق بالاطلاع على الإعدادات والاختصارات على الشاشة الرئيسية."</string>
<string name="permlab_write_settings" msgid="4820028712156303762">"تعديل الإعدادات والاختصارات على الشاشة الرئيسية"</string>
<string name="permdesc_write_settings" msgid="726859348127868466">"يسمح هذا الإذن للتطبيق بتغيير الإعدادات والاختصارات على الشاشة الرئيسية."</string>
- <string name="gadget_error_text" msgid="740356548025791839">"يتعذّر تحميل الأداة."</string>
+ <string name="gadget_error_text" msgid="740356548025791839">"يتعذّر تحميل التطبيق المصغَّر."</string>
<string name="gadget_setup_text" msgid="8348374825537681407">"إعدادات التطبيق المصغّر"</string>
<string name="gadget_complete_setup_text" msgid="309040266978007925">"انقر لإكمال الإعداد."</string>
<string name="uninstall_system_app_text" msgid="4172046090762920660">"هذا تطبيق نظام وتتعذر إزالته."</string>
@@ -159,8 +159,8 @@
<string name="dialog_update_message" msgid="4176784553982226114">"لم يتمّ تحديث التطبيق الخاص بهذا الرمز. يمكنك تحديث التطبيق يدويًا لإعادة تفعيل هذا الاختصار أو إزالة الرمز."</string>
<string name="dialog_update" msgid="2178028071796141234">"تحديث"</string>
<string name="dialog_remove" msgid="6510806469849709407">"إزالة"</string>
- <string name="widgets_list" msgid="796804551140113767">"قائمة الأدوات"</string>
- <string name="widgets_list_closed" msgid="6141506579418771922">"تم إغلاق قائمة الأدوات."</string>
+ <string name="widgets_list" msgid="796804551140113767">"قائمة التطبيقات المصغَّرة"</string>
+ <string name="widgets_list_closed" msgid="6141506579418771922">"تم إغلاق قائمة التطبيقات المصغَّرة."</string>
<string name="action_add_to_workspace" msgid="215894119683164916">"إضافة تطبيق للشاشة الرئيسية"</string>
<string name="action_move_here" msgid="2170188780612570250">"نقل العنصر إلى هنا"</string>
<string name="item_removed" msgid="851119963877842327">"تمّت إزالة العنصر."</string>
@@ -181,7 +181,7 @@
<string name="action_increase_height" msgid="459390020612501122">"زيادة الارتفاع"</string>
<string name="action_decrease_width" msgid="1374549771083094654">"تقليل العرض"</string>
<string name="action_decrease_height" msgid="282377193880900022">"تقليل الارتفاع"</string>
- <string name="widget_resized" msgid="9130327887929620">"تم تغيير حجم الأداة إلى العرض <xliff:g id="NUMBER_0">%1$s</xliff:g> والارتفاع <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+ <string name="widget_resized" msgid="9130327887929620">"تم تغيير حجم التطبيق المصغَّر إلى العرض <xliff:g id="NUMBER_0">%1$s</xliff:g> والارتفاع <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
<string name="action_deep_shortcut" msgid="4766835855579976045">"قائمة الاختصارات"</string>
<string name="action_dismiss_notification" msgid="5909461085055959187">"تجاهل"</string>
<string name="accessibility_close" msgid="2277148124685870734">"إغلاق"</string>
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/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 78b566a..e3bde3a 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -83,7 +83,7 @@
<string name="label_application" msgid="8531721983832654978">"एप"</string>
<string name="all_apps_label" msgid="5015784846527570951">"सबै एप"</string>
<string name="all_apps_list_label" msgid="5106226764073070906">"एपहरूको सूची"</string>
- <string name="notifications_header" msgid="1404149926117359025">"सूचनाहरू"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"नोटिफिकेसनहरू"</string>
<string name="long_press_shortcut_to_add" msgid="5405328730817637737">"कुनै सर्टकट सार्न डबल ट्याप गरेर छोइराख्नुहोस्।"</string>
<string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"कुनै सर्टकट सार्न वा आफ्नो रोजाइका कारबाही प्रयोग गर्न डबल ट्याप गरेर छोइराख्नुहोस्।"</string>
<string name="out_of_space" msgid="6455557115204099579">"यो होम स्क्रिनमा ठाउँ छैन"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 754bd76..1dad001 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -46,7 +46,7 @@
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"Szerokość %1$d, wysokość %2$d"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"Widżet <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Widżet <xliff:g id="WIDGET_NAME">%1$s</xliff:g>, %2$d (szerokość), %3$d (wysokość)"</string>
- <string name="add_item_request_drag_hint" msgid="8730547755622776606">"Aby poruszać widżetem po ekranie głównym, kliknij go i przytrzymaj"</string>
+ <string name="add_item_request_drag_hint" msgid="8730547755622776606">"Aby przesunąć widżet na ekranie głównym, kliknij go i przytrzymaj"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Dodaj do ekranu głównego"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Widżet <xliff:g id="WIDGET_NAME">%1$s</xliff:g> został dodany do ekranu głównego"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Sugestie"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 7016df5..9e2307a 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -129,7 +129,7 @@
<string name="app_pair_name_format" msgid="8134106404716224054">"Par aplikacij: <xliff:g id="APP1">%1$s</xliff:g> in <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Zaslonsko ozadje in slog"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Urejanje začetnega zaslona"</string>
- <string name="settings_button_text" msgid="8873672322605444408">"Domače nastavitve"</string>
+ <string name="settings_button_text" msgid="8873672322605444408">"Začetni zaslon"</string>
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"Onemogočil skrbnik."</string>
<string name="allow_rotation_title" msgid="7222049633713050106">"Dovoli sukanje začetnega zaslona"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Ko se telefon zasuka"</string>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index e06895c..f740489 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -174,7 +174,8 @@
<declare-styleable name="GridDisplayOption">
<attr name="name" format="string" />
- <attr name="title" />
+ <attr name="gridTitle" format="string" />
+ <attr name="gridIconId" format="reference"/>
<attr name="numRows" format="integer" />
<attr name="numColumns" format="integer" />
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 212534b..c48f140 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -191,7 +191,7 @@
<!-- Widget tray -->
<dimen name="widget_cell_vertical_padding">8dp</dimen>
- <dimen name="widget_cell_horizontal_padding">8dp</dimen>
+ <dimen name="widget_cell_horizontal_padding">4dp</dimen>
<dimen name="widget_cell_title_font_size">14sp</dimen>
<integer name="widget_cell_title_font_weight">500</integer>
<dimen name="widget_cell_title_line_height">20sp</dimen>
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 2e75261..6277e41 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -16,6 +16,7 @@
package com.android.launcher3;
+import static com.android.launcher3.util.DisplayController.CHANGE_ROTATION;
import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
@@ -29,17 +30,27 @@
import android.content.res.Configuration;
import android.os.Bundle;
import android.util.Log;
+import android.view.ActionMode;
+import android.view.View;
import android.window.OnBackInvokedDispatcher;
import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.util.ActivityOptionsWrapper;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
+import com.android.launcher3.util.DisplayController.Info;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.ViewCache;
+import com.android.launcher3.util.WindowBounds;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.ScrimView;
@@ -52,7 +63,8 @@
/**
* Launcher BaseActivity
*/
-public abstract class BaseActivity extends Activity implements ActivityContext {
+public abstract class BaseActivity extends Activity implements ActivityContext,
+ DisplayInfoChangeListener {
private static final String TAG = "BaseActivity";
static final boolean DEBUG = false;
@@ -126,6 +138,10 @@
public @interface ActivityFlags {
}
+ // When starting an action mode, setting this tag will cause the action mode to be cancelled
+ // automatically when user interacts with the launcher.
+ public static final Object AUTO_CANCEL_ACTION_MODE = new Object();
+
/** Returns a human-readable string for the specified {@link ActivityFlags}. */
public static String getActivityStateString(@ActivityFlags int flags) {
StringJoiner result = new StringJoiner("|");
@@ -160,6 +176,8 @@
private final RunnableList[] mEventCallbacks =
{new RunnableList(), new RunnableList(), new RunnableList(), new RunnableList()};
+ private ActionMode mCurrentActionMode;
+
@Override
public ViewCache getViewCache() {
return mViewCache;
@@ -206,6 +224,7 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
registerBackDispatcher();
+ DisplayController.INSTANCE.get(this).addChangeListener(this);
}
@Override
@@ -253,6 +272,7 @@
protected void onDestroy() {
super.onDestroy();
mEventCallbacks[EVENT_DESTROYED].executeAllAndClear();
+ DisplayController.INSTANCE.get(this).removeChangeListener(this);
}
@Override
@@ -403,6 +423,61 @@
writer.println(prefix + "mForceInvisible: " + mForceInvisible);
}
+
+ @Override
+ public void onActionModeStarted(ActionMode mode) {
+ super.onActionModeStarted(mode);
+ mCurrentActionMode = mode;
+ }
+
+ @Override
+ public void onActionModeFinished(ActionMode mode) {
+ super.onActionModeFinished(mode);
+ mCurrentActionMode = null;
+ }
+
+ protected boolean isInAutoCancelActionMode() {
+ return mCurrentActionMode != null && AUTO_CANCEL_ACTION_MODE == mCurrentActionMode.getTag();
+ }
+
+ @Override
+ public boolean finishAutoCancelActionMode() {
+ if (isInAutoCancelActionMode()) {
+ mCurrentActionMode.finish();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ @NonNull
+ public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) {
+ ActivityOptionsWrapper wrapper = ActivityContext.super.getActivityLaunchOptions(v, item);
+ addEventCallback(EVENT_RESUMED, wrapper.onEndCallback::executeAllAndDestroy);
+ return wrapper;
+ }
+
+ @Override
+ public ActivityOptionsWrapper makeDefaultActivityOptions(int splashScreenStyle) {
+ ActivityOptionsWrapper wrapper =
+ ActivityContext.super.makeDefaultActivityOptions(splashScreenStyle);
+ addEventCallback(EVENT_RESUMED, wrapper.onEndCallback::executeAllAndDestroy);
+ return wrapper;
+ }
+
+ protected WindowBounds getMultiWindowDisplaySize() {
+ return WindowBounds.fromWindowMetrics(getWindowManager().getCurrentWindowMetrics());
+ }
+
+ @Override
+ public void onDisplayInfoChanged(Context context, Info info, int flags) {
+ if ((flags & CHANGE_ROTATION) != 0 && mDeviceProfile.isVerticalBarLayout()) {
+ reapplyUi();
+ }
+ }
+
+ protected void reapplyUi() {}
+
public static <T extends BaseActivity> T fromContext(Context context) {
if (context instanceof BaseActivity) {
return (T) context;
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
deleted file mode 100644
index 3b93cf4..0000000
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import static com.android.launcher3.util.DisplayController.CHANGE_ROTATION;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.view.ActionMode;
-import android.view.View;
-
-import androidx.annotation.MainThread;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.touch.ItemClickHandler;
-import com.android.launcher3.util.ActivityOptionsWrapper;
-import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
-import com.android.launcher3.util.DisplayController.Info;
-import com.android.launcher3.util.OnColorHintListener;
-import com.android.launcher3.util.Themes;
-import com.android.launcher3.util.WallpaperColorHints;
-import com.android.launcher3.util.WindowBounds;
-
-/**
- * Extension of BaseActivity allowing support for drag-n-drop
- */
-@SuppressWarnings("NewApi")
-public abstract class BaseDraggingActivity extends BaseActivity
- implements OnColorHintListener, DisplayInfoChangeListener {
-
- // When starting an action mode, setting this tag will cause the action mode to be cancelled
- // automatically when user interacts with the launcher.
- public static final Object AUTO_CANCEL_ACTION_MODE = new Object();
-
- private ActionMode mCurrentActionMode;
-
- private int mThemeRes = R.style.AppTheme;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- DisplayController.INSTANCE.get(this).addChangeListener(this);
-
- // Update theme
- WallpaperColorHints.get(this).registerOnColorHintsChangedListener(this);
- int themeRes = Themes.getActivityThemeRes(this);
- if (themeRes != mThemeRes) {
- mThemeRes = themeRes;
- setTheme(themeRes);
- }
- }
-
- @MainThread
- @Override
- public void onColorHintsChanged(int colorHints) {
- updateTheme();
- }
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- updateTheme();
- }
-
- private void updateTheme() {
- if (mThemeRes != Themes.getActivityThemeRes(this)) {
- recreateToUpdateTheme();
- }
- }
-
- protected void recreateToUpdateTheme() {
- recreate();
- }
-
- @Override
- public void onActionModeStarted(ActionMode mode) {
- super.onActionModeStarted(mode);
- mCurrentActionMode = mode;
- }
-
- @Override
- public void onActionModeFinished(ActionMode mode) {
- super.onActionModeFinished(mode);
- mCurrentActionMode = null;
- }
-
- protected boolean isInAutoCancelActionMode() {
- return mCurrentActionMode != null && AUTO_CANCEL_ACTION_MODE == mCurrentActionMode.getTag();
- }
-
- @Override
- public boolean finishAutoCancelActionMode() {
- if (isInAutoCancelActionMode()) {
- mCurrentActionMode.finish();
- return true;
- }
- return false;
- }
-
- public abstract View getRootView();
-
- public void returnToHomescreen() {
- // no-op
- }
-
- @Override
- @NonNull
- public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) {
- ActivityOptionsWrapper wrapper = super.getActivityLaunchOptions(v, item);
- addEventCallback(EVENT_RESUMED, wrapper.onEndCallback::executeAllAndDestroy);
- return wrapper;
- }
-
- @Override
- public ActivityOptionsWrapper makeDefaultActivityOptions(int splashScreenStyle) {
- ActivityOptionsWrapper wrapper = super.makeDefaultActivityOptions(splashScreenStyle);
- addEventCallback(EVENT_RESUMED, wrapper.onEndCallback::executeAllAndDestroy);
- return wrapper;
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- DisplayController.INSTANCE.get(this).removeChangeListener(this);
- WallpaperColorHints.get(this).unregisterOnColorsChangedListener(this);
- }
-
- protected void onDeviceProfileInitiated() {
- }
-
- @Override
- public void onDisplayInfoChanged(Context context, Info info, int flags) {
- if ((flags & CHANGE_ROTATION) != 0 && mDeviceProfile.isVerticalBarLayout()) {
- reapplyUi();
- }
- }
-
- @Override
- public View.OnClickListener getItemOnClickListener() {
- return ItemClickHandler.INSTANCE;
- }
-
- protected abstract void reapplyUi();
-
- protected WindowBounds getMultiWindowDisplaySize() {
- return WindowBounds.fromWindowMetrics(getWindowManager().getCurrentWindowMetrics());
- }
-
- @Override
- public boolean isAppBlockedForSafeMode() {
- return LauncherAppState.getInstance(this).isSafeModeEnabled();
- }
-}
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 3d71ff1..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;
@@ -529,9 +533,11 @@
for (int i = 0; i < mDelegatedCellDrawings.size(); i++) {
DelegatedCellDrawing cellDrawing = mDelegatedCellDrawings.get(i);
- cellToPoint(cellDrawing.mDelegateCellX, cellDrawing.mDelegateCellY, mTempLocation);
canvas.save();
- canvas.translate(mTempLocation[0], mTempLocation[1]);
+ if (cellDrawing.mDelegateCellX >= 0 && cellDrawing.mDelegateCellY >= 0) {
+ cellToPoint(cellDrawing.mDelegateCellX, cellDrawing.mDelegateCellY, mTempLocation);
+ canvas.translate(mTempLocation[0], mTempLocation[1]);
+ }
cellDrawing.drawUnderItem(canvas);
canvas.restore();
}
@@ -660,9 +666,11 @@
for (int i = 0; i < mDelegatedCellDrawings.size(); i++) {
DelegatedCellDrawing bg = mDelegatedCellDrawings.get(i);
- cellToPoint(bg.mDelegateCellX, bg.mDelegateCellY, mTempLocation);
canvas.save();
- canvas.translate(mTempLocation[0], mTempLocation[1]);
+ if (bg.mDelegateCellX >= 0 && bg.mDelegateCellY >= 0) {
+ cellToPoint(bg.mDelegateCellX, bg.mDelegateCellY, mTempLocation);
+ canvas.translate(mTempLocation[0], mTempLocation[1]);
+ }
bg.drawOverItem(canvas);
canvas.restore();
}
@@ -781,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;
@@ -1160,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;
@@ -1188,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) {
@@ -1789,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/DropTargetHandler.kt b/src/com/android/launcher3/DropTargetHandler.kt
index 66c948a..0cc7fc7 100644
--- a/src/com/android/launcher3/DropTargetHandler.kt
+++ b/src/com/android/launcher3/DropTargetHandler.kt
@@ -2,7 +2,7 @@
import android.content.ComponentName
import android.view.View
-import com.android.launcher3.BaseDraggingActivity.EVENT_RESUMED
+import com.android.launcher3.BaseActivity.EVENT_RESUMED
import com.android.launcher3.DropTarget.DragObject
import com.android.launcher3.LauncherConstants.ActivityCodes
import com.android.launcher3.SecondaryDropTarget.DeferredOnComplete
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 753e017..e47a44a 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -993,7 +993,8 @@
private static final int DONT_INLINE_QSB = 0;
public final String name;
- public final String title;
+ public final String gridTitle;
+ public final int gridIconId;
public final int numRows;
public final int numColumns;
public final int numSearchContainerColumns;
@@ -1042,7 +1043,9 @@
TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.GridDisplayOption);
name = a.getString(R.styleable.GridDisplayOption_name);
- title = a.getString(R.styleable.GridDisplayOption_title);
+ gridTitle = a.getString(R.styleable.GridDisplayOption_gridTitle);
+ gridIconId = a.getResourceId(
+ R.styleable.GridDisplayOption_gridIconId, INVALID_RESOURCE_HANDLE);
deviceCategory = a.getInt(R.styleable.GridDisplayOption_deviceCategory,
DEVICE_CATEGORY_ALL);
mGridSizeSpecsId = a.getResourceId(
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 7df4014..30ef24b 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -102,6 +102,7 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.ItemInfoMatcher.forFolderMatch;
import static com.android.launcher3.util.SettingsCache.TOUCHPAD_NATURAL_SCROLLING;
+import static com.android.launcher3.util.WallpaperThemeManager.setWallpaperDependentTheme;
import android.animation.Animator;
import android.animation.AnimatorSet;
@@ -144,6 +145,7 @@
import android.view.Menu;
import android.view.MotionEvent;
import android.view.View;
+import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewTreeObserver.OnPreDrawListener;
import android.view.WindowInsets;
@@ -222,6 +224,7 @@
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.touch.AllAppsSwipeController;
+import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.util.ActivityResultInfo;
import com.android.launcher3.util.BackPressHandler;
@@ -277,11 +280,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;
@@ -466,6 +469,7 @@
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
+ .detectActivityLeaks()
.penaltyLog()
.penaltyDeath()
.build());
@@ -509,6 +513,7 @@
}
super.onCreate(savedInstanceState);
+ setWallpaperDependentTheme(this);
LauncherAppState app = LauncherAppState.getInstance(this);
mModel = app.getModel();
@@ -819,7 +824,6 @@
this, getMultiWindowDisplaySize());
}
- onDeviceProfileInitiated();
if (FOLDABLE_SINGLE_PAGE.get() && mDeviceProfile.isTwoPanels) {
mCellPosMapper = new TwoPanelCellPosMapper(mDeviceProfile.inv.numColumns);
} else {
@@ -2598,25 +2602,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);
}
/**
@@ -2853,12 +2844,6 @@
// Overridden
}
- @Override
- public void returnToHomescreen() {
- super.returnToHomescreen();
- getStateManager().goToState(LauncherState.NORMAL);
- }
-
public void closeOpenViews() {
closeOpenViews(true);
}
@@ -3183,5 +3168,10 @@
return findViewById(R.id.popup_container);
}
+ @Override
+ public OnClickListener getItemOnClickListener() {
+ return ItemClickHandler.INSTANCE;
+ }
+
// End of Getters and Setters
}
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index d8bb84e..1120ec8 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -34,6 +34,7 @@
import com.android.launcher3.states.RotationHelper
import com.android.launcher3.util.DaggerSingletonObject
import com.android.launcher3.util.DisplayController
+import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
/**
@@ -52,10 +53,11 @@
.getSharedPreferences(BOOT_AWARE_PREFS_KEY, MODE_PRIVATE)
}
- open val Item.sharedPrefs: SharedPreferences
- get() =
+ open protected fun getSharedPrefs(item: Item): SharedPreferences =
+ item.run {
if (encryptionType == EncryptionType.DEVICE_PROTECTED) deviceProtectedSharedPrefs
else encryptedContext.getSharedPreferences(sharedPrefFile, MODE_PRIVATE)
+ }
/** Returns the value with type [T] for [item]. */
fun <T> get(item: ContextualItem<T>): T =
@@ -71,7 +73,7 @@
*/
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
private fun <T> getInner(item: Item, default: T): T {
- val sp = item.sharedPrefs
+ val sp = getSharedPrefs(item)
return when (item.type) {
String::class.java -> sp.getString(item.sharedPrefKey, default as? String)
@@ -127,7 +129,7 @@
private fun prepareToPutValues(
updates: Array<out Pair<Item, Any>>
): List<SharedPreferences.Editor> {
- val updatesPerPrefFile = updates.groupBy { it.first.sharedPrefs }.toMap()
+ val updatesPerPrefFile = updates.groupBy { getSharedPrefs(it.first) }.toMap()
return updatesPerPrefFile.map { (sharedPref, itemList) ->
sharedPref.edit().apply { itemList.forEach { (item, value) -> putValue(item, value) } }
@@ -140,7 +142,7 @@
* types of Item values.
*/
@Suppress("UNCHECKED_CAST")
- private fun SharedPreferences.Editor.putValue(
+ internal fun SharedPreferences.Editor.putValue(
item: Item,
value: Any?,
): SharedPreferences.Editor =
@@ -168,7 +170,7 @@
*/
fun addListener(listener: LauncherPrefChangeListener, vararg items: Item) {
items
- .map { it.sharedPrefs }
+ .map { getSharedPrefs(it) }
.distinct()
.forEach { it.registerOnSharedPreferenceChangeListener(listener) }
}
@@ -180,7 +182,7 @@
fun removeListener(listener: LauncherPrefChangeListener, vararg items: Item) {
// If a listener is not registered to a SharedPreference, unregistering it does nothing
items
- .map { it.sharedPrefs }
+ .map { getSharedPrefs(it) }
.distinct()
.forEach { it.unregisterOnSharedPreferenceChangeListener(listener) }
}
@@ -191,7 +193,7 @@
*/
fun has(vararg items: Item): Boolean {
items
- .groupBy { it.sharedPrefs }
+ .groupBy { getSharedPrefs(it) }
.forEach { (prefs, itemsSublist) ->
if (!itemsSublist.none { !prefs.contains(it.sharedPrefKey) }) return false
}
@@ -215,7 +217,7 @@
* .apply() or .commit()
*/
private fun prepareToRemove(items: Array<out Item>): List<SharedPreferences.Editor> {
- val itemsPerFile = items.groupBy { it.sharedPrefs }.toMap()
+ val itemsPerFile = items.groupBy { getSharedPrefs(it) }.toMap()
return itemsPerFile.map { (prefs, items) ->
prefs.edit().also { editor ->
@@ -406,3 +408,26 @@
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 copiedPrefs = ConcurrentHashMap<SharedPreferences, Boolean>()
+
+ override fun getSharedPrefs(item: Item): SharedPreferences {
+ val originalPrefs = super.getSharedPrefs(item)
+ // Copy all existing values, when the pref is accessed for the first time
+ copiedPrefs.computeIfAbsent(originalPrefs) { op ->
+ val editor = prefs.edit()
+ op.all.forEach { (key, value) ->
+ if (value != null) {
+ editor.putValue(backedUpItem(key, value), value)
+ }
+ }
+ editor.commit()
+ }
+ return prefs
+ }
+}
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index a5b95c7..b8a0abd 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -12,6 +12,7 @@
import com.android.launcher3.graphics.SysUiScrim;
import com.android.launcher3.statemanager.StatefulContainer;
import com.android.launcher3.util.window.WindowManagerProxy;
+import com.android.launcher3.views.ActivityContext;
import java.util.Collections;
import java.util.List;
@@ -36,7 +37,7 @@
public LauncherRootView(Context context, AttributeSet attrs) {
super(context, attrs);
- mStatefulContainer = StatefulContainer.fromContext(context);
+ mStatefulContainer = ActivityContext.lookupContext(context);
mSysUiScrim = new SysUiScrim(this);
}
@@ -54,7 +55,7 @@
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
mStatefulContainer.handleConfigurationChanged(
- mStatefulContainer.getContext().getResources().getConfiguration());
+ mStatefulContainer.asContext().getResources().getConfiguration());
return updateInsets(insets);
}
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 7d5e481..8c6555e 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -118,7 +118,7 @@
LAUNCHER_STATE_HOME,
FLAG_DISABLE_RESTORE | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED | FLAG_HAS_SYS_UI_SCRIM) {
@Override
- public int getTransitionDuration(Context context, boolean isToState) {
+ public int getTransitionDuration(ActivityContext context, boolean isToState) {
// Arbitrary duration, when going to NORMAL we use the state we're coming from instead.
return 0;
}
@@ -345,7 +345,7 @@
public final <DEVICE_PROFILE_CONTEXT extends Context & ActivityContext>
float getDepth(DEVICE_PROFILE_CONTEXT context) {
return getDepth(context,
- BaseDraggingActivity.fromContext(context).getDeviceProfile().isMultiWindowMode);
+ ActivityContext.lookupContext(context).getDeviceProfile().isMultiWindowMode);
}
/**
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/allapps/WorkUtilityView.java b/src/com/android/launcher3/allapps/WorkUtilityView.java
index bccc279..e42a6b9 100644
--- a/src/com/android/launcher3/allapps/WorkUtilityView.java
+++ b/src/com/android/launcher3/allapps/WorkUtilityView.java
@@ -15,6 +15,11 @@
*/
package com.android.launcher3.allapps;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORK_UTILITY_VIEW_EXPAND_ANIMATION_BEGIN;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORK_UTILITY_VIEW_EXPAND_ANIMATION_END;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORK_UTILITY_VIEW_SHRINK_ANIMATION_BEGIN;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORK_UTILITY_VIEW_SHRINK_ANIMATION_END;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
@@ -46,6 +51,7 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatedPropertySetter;
import com.android.launcher3.anim.KeyboardInsetAnimationCallback;
+import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.StringCache;
import com.android.launcher3.views.ActivityContext;
@@ -88,6 +94,8 @@
private TextView mPauseText;
private ImageView mWorkIcon;
private ImageButton mSchedulerButton;
+ private final StatsLogManager mStatsLogManager;
+ private LinearLayout mWorkUtilityView;
public WorkUtilityView(@NonNull Context context) {
this(context, null, 0);
@@ -111,6 +119,7 @@
R.dimen.work_fab_icon_start_margin_expanded);
mWorkSchedulerIntentAction = mContext.getResources().getString(
R.string.work_profile_scheduler_intent);
+ mStatsLogManager = mActivityContext.getStatsLogManager();
}
@Override
@@ -121,6 +130,7 @@
mWorkIcon = findViewById(R.id.work_icon);
mWorkFAB = findViewById(R.id.work_mode_toggle);
mSchedulerButton = findViewById(R.id.work_scheduler);
+ mWorkUtilityView = findViewById(R.id.work_utility_view);
setSelected(true);
KeyboardInsetAnimationCallback keyboardInsetAnimationCallback =
new KeyboardInsetAnimationCallback(this);
@@ -319,6 +329,23 @@
animatorList.add(animateSchedulerScale(isExpanding));
animatorList.add(animateSchedulerAlpha(isExpanding));
}
+ animatorSet.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mStatsLogManager.logger().sendToInteractionJankMonitor(
+ isExpanding ? LAUNCHER_WORK_UTILITY_VIEW_EXPAND_ANIMATION_BEGIN
+ : LAUNCHER_WORK_UTILITY_VIEW_SHRINK_ANIMATION_BEGIN,
+ mWorkUtilityView);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mStatsLogManager.logger().sendToInteractionJankMonitor(
+ isExpanding ? LAUNCHER_WORK_UTILITY_VIEW_EXPAND_ANIMATION_END
+ : LAUNCHER_WORK_UTILITY_VIEW_SHRINK_ANIMATION_END,
+ mWorkUtilityView);
+ }
+ });
animatorSet.playTogether(animatorList);
animatorSet.start();
}
diff --git a/src/com/android/launcher3/dagger/LauncherAppModule.java b/src/com/android/launcher3/dagger/LauncherAppModule.java
index ef136d0..c58a414 100644
--- a/src/com/android/launcher3/dagger/LauncherAppModule.java
+++ b/src/com/android/launcher3/dagger/LauncherAppModule.java
@@ -21,7 +21,9 @@
@Module(includes = {
WindowManagerProxyModule.class,
ApiWrapperModule.class,
- PluginManagerWrapperModule.class
+ PluginManagerWrapperModule.class,
+ StaticObjectModule.class,
+ AppModule.class
})
public class LauncherAppModule {
}
diff --git a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
index c3e3992..7bd7c3e 100644
--- a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
+++ b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
@@ -23,10 +23,12 @@
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;
import com.android.launcher3.util.DynamicResource;
+import com.android.launcher3.util.LockedUserState;
import com.android.launcher3.util.MSDLPlayerWrapper;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.PluginManagerWrapper;
@@ -66,8 +68,10 @@
WindowManagerProxy getWmProxy();
LauncherPrefs getLauncherPrefs();
ThemeManager getThemeManager();
+ UserCache getUserCache();
DisplayController getDisplayController();
WallpaperColorHints getWallpaperColorHints();
+ LockedUserState getLockedUserState();
/** Builder for LauncherBaseAppComponent. */
interface Builder {
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/Folder.java b/src/com/android/launcher3/folder/Folder.java
index b76e098..fb48a4d 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -1708,7 +1708,7 @@
@Override
public boolean canInterceptEventsInSystemGestureRegion() {
- return true;
+ return !mIsEditingName;
}
/**
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index 588a6db..2157610 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -35,6 +35,7 @@
import android.util.Property;
import android.view.View;
import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.CellLayout;
@@ -79,9 +80,10 @@
private final int mDuration;
private final int mDelay;
- private final TimeInterpolator mFolderInterpolator;
- private final TimeInterpolator mLargeFolderPreviewItemOpenInterpolator;
- private final TimeInterpolator mLargeFolderPreviewItemCloseInterpolator;
+ private final Interpolator mFolderOpenInterpolator;
+ private final Interpolator mFolderCloseInterpolator;
+ private final Interpolator mLargeFolderPreviewItemOpenInterpolator;
+ private final Interpolator mLargeFolderPreviewItemCloseInterpolator;
private final PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0);
private final FolderGridOrganizer mPreviewVerifier;
@@ -108,7 +110,9 @@
mDuration = res.getInteger(R.integer.config_materialFolderExpandDuration);
mDelay = res.getInteger(R.integer.config_folderDelay);
- mFolderInterpolator = AnimationUtils.loadInterpolator(mContext,
+ mFolderOpenInterpolator = AnimationUtils.loadInterpolator(mContext,
+ R.interpolator.standard_interpolator);
+ mFolderCloseInterpolator = AnimationUtils.loadInterpolator(mContext,
R.interpolator.standard_interpolator);
mLargeFolderPreviewItemOpenInterpolator = AnimationUtils.loadInterpolator(mContext,
R.interpolator.large_folder_preview_item_open_interpolator);
@@ -233,7 +237,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));
@@ -252,6 +256,7 @@
(int) (left + (startRect.right / initialScale)) + extraRadius,
(int) (startRect.bottom / initialScale) + extraRadius);
Rect contentEnd = new Rect(left, 0, left + lp.width, lp.height);
+ // animated contents of folder with the folder background
play(a, shapeDelegate.createRevealAnimator(
mFolder.getContent(), contentStart, contentEnd, finalRadius, !mIsOpening));
@@ -332,7 +337,11 @@
// We set the interpolator on all current child animators here, because the preview item
// animators may use a different interpolator.
for (Animator animator : a.getChildAnimations()) {
- animator.setInterpolator(mFolderInterpolator);
+ animator.setInterpolator(
+ mIsOpening
+ ? mFolderOpenInterpolator
+ : mFolderCloseInterpolator
+ );
}
int radiusDiff = scaledRadius - mPreviewBackground.getRadius();
@@ -467,7 +476,7 @@
return mFolder.getItemCount() > MAX_NUM_ITEMS_IN_PREVIEW;
}
- private TimeInterpolator getPreviewItemInterpolator() {
+ private Interpolator getPreviewItemInterpolator() {
if (isLargeFolder()) {
// With larger folders, we want the preview items to reach their final positions faster
// (when opening) and later (when closing) so that they appear aligned with the rest of
@@ -476,7 +485,7 @@
? mLargeFolderPreviewItemOpenInterpolator
: mLargeFolderPreviewItemCloseInterpolator;
}
- return mFolderInterpolator;
+ return mIsOpening ? mFolderOpenInterpolator : mFolderCloseInterpolator;
}
private Animator getAnimator(View view, Property property, float v1, float v2) {
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..80743af 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,17 @@
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.Optional;
import java.util.Set;
-import java.util.WeakHashMap;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
/**
@@ -84,10 +87,13 @@
private static final String TAG = "GridCustomizationsProvider";
+ // KEY_NAME is the name of the grid used internally while the KEY_GRID_TITLE is the translated
+ // string title of the grid.
private static final String KEY_NAME = "name";
private static final String KEY_GRID_TITLE = "grid_title";
private static final String KEY_ROWS = "rows";
private static final String KEY_COLS = "cols";
+ private static final String KEY_GRID_ICON_ID = "grid_icon_id";
private static final String KEY_PREVIEW_COUNT = "preview_count";
// is_default means if a certain option is currently set to the system
private static final String KEY_IS_DEFAULT = "is_default";
@@ -120,7 +126,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 +147,25 @@
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()
+ String currentShapePath =
+ ThemeManager.INSTANCE.get(context).getIconState().getIconMask();
+ Optional<IconShapeModel> selectedShape = ShapesProvider.INSTANCE.getIconShapes()
.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"));
+ .filter(shape -> shape.getPathString().equals(currentShapePath))
+ .findFirst();
+ // Handle default for when current shape doesn't match new shapes.
+ if (selectedShape.isEmpty()) {
+ selectedShape = Optional.ofNullable(ShapesProvider.INSTANCE.getIconShapes()
+ .get("circle"));
+ }
- for (int i = 0; i < shapes.size(); i++) {
- IconShapeModel shape = shapes.get(i);
+ 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.equals(selectedShape.get()));
}
return cursor;
} else {
@@ -167,17 +175,18 @@
case KEY_LIST_OPTIONS: {
MatrixCursor cursor = new MatrixCursor(new String[]{
KEY_NAME, KEY_GRID_TITLE, KEY_ROWS, KEY_COLS, KEY_PREVIEW_COUNT,
- KEY_IS_DEFAULT});
+ KEY_IS_DEFAULT, KEY_GRID_ICON_ID});
InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(getContext());
for (GridOption gridOption : idp.parseAllGridOptions(getContext())) {
cursor.newRow()
.add(KEY_NAME, gridOption.name)
- .add(KEY_GRID_TITLE, gridOption.title)
+ .add(KEY_GRID_TITLE, gridOption.gridTitle)
.add(KEY_ROWS, gridOption.numRows)
.add(KEY_COLS, gridOption.numColumns)
.add(KEY_PREVIEW_COUNT, 1)
.add(KEY_IS_DEFAULT, idp.numColumns == gridOption.numColumns
- && idp.numRows == gridOption.numRows);
+ && idp.numRows == gridOption.numRows)
+ .add(KEY_GRID_ICON_ID, gridOption.gridIconId);
}
return cursor;
}
@@ -218,9 +227,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 +329,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 +377,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 +415,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..eac3440 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
@@ -39,21 +38,15 @@
import androidx.graphics.shapes.Morph
import androidx.graphics.shapes.RoundedPolygon
import androidx.graphics.shapes.SvgPathParser
+import androidx.graphics.shapes.rectangle
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 +56,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 +115,6 @@
): ValueAnimator where T : View, T : ClipPathView
}
- @VisibleForTesting
class Circle : RoundedSquare(1f) {
override fun drawShape(
@@ -217,14 +205,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 +290,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 +313,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)
@@ -354,8 +363,8 @@
}
/**
- * Creates a rounded rect with the start point at the center of the top edge. This ensures a
- * better animation since our shape paths also start at top-center of the bounding box.
+ * Create RoundedRect using RoundedPolygon API. Ensures smoother animation morphing between
+ * generic polygon by using [RoundedPolygon.Companion.rectangle] directly.
*/
fun createRoundedRect(
left: Float,
@@ -364,27 +373,11 @@
bottom: Float,
cornerR: Float,
) =
- RoundedPolygon(
- vertices =
- floatArrayOf(
- // x1, y1
- (left + right) / 2,
- top,
- // x2, y2
- right,
- top,
- // x3, y3
- right,
- bottom,
- // x4, y4
- left,
- bottom,
- // x5, y5
- left,
- top,
- ),
- centerX = (left + right) / 2,
- centerY = (top + bottom) / 2,
+ RoundedPolygon.rectangle(
+ width = right - left,
+ height = bottom - top,
+ centerX = (right - left) / 2,
+ centerY = (bottom - top) / 2,
rounding = CornerRounding(cornerR),
)
}
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/SysUiScrim.java b/src/com/android/launcher3/graphics/SysUiScrim.java
index d59fc19..6f1d98f 100644
--- a/src/com/android/launcher3/graphics/SysUiScrim.java
+++ b/src/com/android/launcher3/graphics/SysUiScrim.java
@@ -40,6 +40,7 @@
import com.android.launcher3.util.ScreenOnTracker;
import com.android.launcher3.util.ScreenOnTracker.ScreenOnListener;
import com.android.launcher3.util.Themes;
+import com.android.launcher3.views.ActivityContext;
/**
* View scrim which draws behind hotseat and workspace
@@ -94,8 +95,8 @@
public SysUiScrim(View view) {
mRoot = view;
- mContainer = StatefulContainer.fromContext(view.getContext());
- DisplayMetrics dm = mContainer.getContext().getResources().getDisplayMetrics();
+ mContainer = ActivityContext.lookupContext(view.getContext());
+ DisplayMetrics dm = mContainer.asContext().getResources().getDisplayMetrics();
mTopMaskHeight = ResourceUtils.pxFromDp(TOP_MASK_HEIGHT_DP, dm);
mBottomMaskHeight = ResourceUtils.pxFromDp(BOTTOM_MASK_HEIGHT_DP, dm);
@@ -173,12 +174,12 @@
@Override
public void onViewAttachedToWindow(View view) {
- ScreenOnTracker.INSTANCE.get(mContainer.getContext()).addListener(mScreenOnListener);
+ ScreenOnTracker.INSTANCE.get(mContainer.asContext()).addListener(mScreenOnListener);
}
@Override
public void onViewDetachedFromWindow(View view) {
- ScreenOnTracker.INSTANCE.get(mContainer.getContext()).removeListener(mScreenOnListener);
+ ScreenOnTracker.INSTANCE.get(mContainer.asContext()).removeListener(mScreenOnListener);
}
/**
@@ -213,7 +214,7 @@
}
private Bitmap createDitheredAlphaMask(int height, @ColorInt int[] colors, float[] positions) {
- DisplayMetrics dm = mContainer.getContext().getResources().getDisplayMetrics();
+ DisplayMetrics dm = mContainer.asContext().getResources().getDisplayMetrics();
int width = ResourceUtils.pxFromDp(ALPHA_MASK_BITMAP_WIDTH_DP, dm);
Bitmap dst = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8);
Canvas c = new Canvas(dst);
diff --git a/src/com/android/launcher3/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..5c6debe 100644
--- a/src/com/android/launcher3/icons/LauncherIcons.java
+++ b/src/com/android/launcher3/icons/LauncherIcons.java
@@ -16,23 +16,25 @@
package com.android.launcher3.icons;
+import static android.graphics.Color.BLACK;
+
+import static com.android.launcher3.graphics.ThemeManager.PREF_ICON_SHAPE;
+
import android.content.Context;
-import android.graphics.Matrix;
+import android.graphics.Canvas;
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.LauncherPrefs;
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;
@@ -45,6 +47,12 @@
*/
public class LauncherIcons extends BaseIconFactory implements AutoCloseable {
+ private static final float SEVEN_SIDED_COOKIE_SCALE = 72f / 80f;
+ private static final float FOUR_SIDED_COOKIE_SCALE = 72f / 83.4f;
+ private static final float VERY_SUNNY_SCALE = 72f / 92f;
+ private static final float DEFAULT_ICON_SCALE = 1f;
+
+
private static final MainThreadInitializedObject<Pool> POOL =
new MainThreadInitializedObject<>(Pool::new);
@@ -87,22 +95,37 @@
@Override
public Path getShapePath(AdaptiveIconDrawable drawable, Rect iconBounds) {
if (!Flags.enableLauncherIconShapes()) return drawable.getIconMask();
+ return IconShape.INSTANCE.get(mContext).getShape().getPath(iconBounds);
+ }
- 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();
+ @Override
+ protected void drawAdaptiveIcon(
+ @NonNull Canvas canvas,
+ @NonNull AdaptiveIconDrawable drawable,
+ @NonNull Path overridePath
+ ) {
+ if (!Flags.enableLauncherIconShapes()) {
+ super.drawAdaptiveIcon(canvas, drawable, overridePath);
+ return;
}
+ String shapeKey = LauncherPrefs.get(mContext).get(PREF_ICON_SHAPE);
+ float iconScale = switch (shapeKey) {
+ case "seven_sided_cookie" -> SEVEN_SIDED_COOKIE_SCALE;
+ case "four_sided_cookie" -> FOUR_SIDED_COOKIE_SCALE;
+ case "sunny" -> VERY_SUNNY_SCALE;
+ default -> DEFAULT_ICON_SCALE;
+ };
+ canvas.clipPath(overridePath);
+ canvas.drawColor(BLACK);
+ canvas.save();
+ canvas.scale(iconScale, iconScale, canvas.getWidth() / 2f, canvas.getHeight() / 2f);
+ if (drawable.getBackground() != null) {
+ drawable.getBackground().draw(canvas);
+ }
+ if (drawable.getForeground() != null) {
+ drawable.getForeground().draw(canvas);
+ }
+ canvas.restore();
}
@Override
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 6eb02ab..9a1c874 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),
@@ -856,6 +859,18 @@
@UiEvent(doc = "User sets the device in Fixed Landscape")
FIXED_LANDSCAPE_TOGGLE_DISABLED(2020),
+
+ @UiEvent(doc = "Work utility view expand animation started")
+ LAUNCHER_WORK_UTILITY_VIEW_EXPAND_ANIMATION_BEGIN(2075),
+
+ @UiEvent(doc = "Work utility view expand animation ended")
+ LAUNCHER_WORK_UTILITY_VIEW_EXPAND_ANIMATION_END(2076),
+
+ @UiEvent(doc = "Work utility view shrink animation started")
+ LAUNCHER_WORK_UTILITY_VIEW_SHRINK_ANIMATION_BEGIN(2077),
+
+ @UiEvent(doc = "Work utility view shrink animation ended")
+ LAUNCHER_WORK_UTILITY_VIEW_SHRINK_ANIMATION_END(2078),
// ADD MORE
;
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 0732379..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) {
@@ -140,22 +140,13 @@
try (SQLiteTransaction t = new SQLiteTransaction(target.getWritableDatabase())) {
if (shouldMigrateToStrictlyTallerGrid) {
- // This is a special case where if the grid is the same amount of columns but a
- // larger amount of rows we simply copy over the source grid to the destination
- // grid, rather than undergoing the general grid migration. If there are more icons
- // on the bottom of the first page then we shift the icons down to the bottom of the
- // grid so that the icons remain bottom-anchored.
- if (oneGridSpecs()) {
- DbReader destReader = new DbReader(
- target.getWritableDatabase(), TABLE_NAME, context);
- boolean shouldShiftCells =
- shouldShiftCells(destReader, srcDeviceState.getRows());
- if (shouldShiftCells) {
- shiftTableByXCells(
- target.getWritableDatabase(),
- (destDeviceState.getRows() - srcDeviceState.getRows()),
- TABLE_NAME);
- }
+ // We want to add the extra row(s) to the top of the screen, so we shift the grid
+ // down.
+ if (Flags.oneGridSpecs()) {
+ shiftWorkspaceByXCells(
+ target.getWritableDatabase(),
+ (destDeviceState.getRows() - srcDeviceState.getRows()),
+ TABLE_NAME);
}
// Save current configuration, so that the migration does not run again.
@@ -168,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;
@@ -190,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;
@@ -430,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;
}
@@ -455,22 +454,6 @@
}
}
- private static boolean shouldShiftCells(final DbReader destReader, final int srcGridRowCount) {
- List<DbEntry> workspaceItems = destReader.loadAllWorkspaceEntries();
- int firstPageItemsRowPosSum = workspaceItems.stream()
- .filter(entry -> entry.screenId == 0)
- .mapToInt(entry -> entry.cellY).sum();
- int firstPageWorkspaceItemsCount = (int) workspaceItems.stream()
- .filter(entry -> entry.screenId == 0).count();
- if (firstPageWorkspaceItemsCount == 0) {
- return false;
- }
- float srcGridMidPoint = srcGridRowCount / 2f;
- float firstPageItemPosAvg = (float) firstPageItemsRowPosSum / firstPageWorkspaceItemsCount;
- return (firstPageItemPosAvg >= srcGridMidPoint);
- }
-
-
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public static class DbReader {
diff --git a/src/com/android/launcher3/model/GridSizeMigrationLogic.kt b/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
index 1729153..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
@@ -77,24 +76,16 @@
val migrationStartTime = System.currentTimeMillis()
try {
SQLiteTransaction(target.writableDatabase).use { t ->
- // This is a special case where if the grid is the same amount of columns but a
- // larger amount of rows we simply copy over the source grid to the destination
- // grid, rather than undergoing the general grid migration. If there are more icons
- // on the bottom of the first page then we shift the icons down to the bottom of the
- // grid so that the icons remain bottom-anchored.
+ // We want to add the extra row(s) to the top of the screen, so we shift the grid
+ // down.
if (shouldMigrateToStrtictlyTallerGrid) {
Log.d(TAG, "Migrating to strictly taller grid")
- if (oneGridSpecs()) {
- val destReader = DbReader(target.writableDatabase, TABLE_NAME, context)
- val shouldShiftCells = shouldShiftCells(destReader, srcDeviceState.rows)
- if (shouldShiftCells) {
- Log.i("TAGTAG", "should shift cells")
- shiftTableByXCells(
- target.writableDatabase,
- (destDeviceState.rows - srcDeviceState.rows),
- TABLE_NAME,
- )
- }
+ if (Flags.oneGridSpecs()) {
+ shiftWorkspaceByXCells(
+ target.writableDatabase,
+ (destDeviceState.rows - srcDeviceState.rows),
+ TABLE_NAME,
+ )
}
// Save current configuration, so that the migration does not run again.
destDeviceState.writeToPrefs(context)
@@ -115,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)
@@ -139,22 +137,10 @@
}
}
- private fun shouldShiftCells(destReader: DbReader, srcGridRowCount: Int): Boolean {
- val workspaceItems = destReader.loadAllWorkspaceEntries()
- val firstPageItemsRowPosSum =
- workspaceItems.sumOf { entry -> if (entry.screenId == 0) entry.cellY else 0 }
- val firstPageWorkspaceItemsCount = workspaceItems.count { entry -> entry.screenId == 0 }
- if (firstPageWorkspaceItemsCount == 0) {
- return false
- }
- val srcGridMidPoint = srcGridRowCount / 2f
- val firstPageItemPosAvg = firstPageItemsRowPosSum / firstPageWorkspaceItemsCount.toFloat()
- return (firstPageItemPosAvg >= srcGridMidPoint)
- }
-
/** Handles hotseat migration. */
@VisibleForTesting
fun migrateHotseat(
+ srcHotseatSize: Int,
destHotseatSize: Int,
srcReader: DbReader,
destReader: DbReader,
@@ -164,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(),
@@ -182,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,
@@ -380,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..6a8d86b 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;
@@ -37,6 +42,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import com.android.launcher3.Flags;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings.Favorites;
@@ -48,6 +54,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 +92,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;
@@ -189,7 +202,7 @@
info.itemType = itemType;
info.title = getTitle();
// the fallback icon
- if (!loadIcon(info)) {
+ if (!loadIconFromDb(info)) {
info.bitmap = mIconCache.getDefaultIcon(info.user);
}
@@ -201,15 +214,15 @@
/**
* Loads the icon from the cursor and updates the {@param info} if the icon is an app resource.
*/
- protected boolean loadIcon(WorkspaceItemInfo info) {
- return createIconRequestInfo(info, false).loadWorkspaceIcon(mContext);
+ protected boolean loadIconFromDb(WorkspaceItemInfo info) {
+ return createIconRequestInfo(info, false).loadIconFromDbBlob(mContext);
}
public IconRequestInfo<WorkspaceItemInfo> createIconRequestInfo(
WorkspaceItemInfo wai, boolean useLowResIcon) {
byte[] iconBlob = itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT || restoreFlag != 0
+ || (wai.isInactiveArchive() && Flags.restoreArchivedAppIconsFromDb())
? getIconBlob() : null;
-
return new IconRequestInfo<>(wai, mActivityInfo, iconBlob, useLowResIcon);
}
@@ -300,7 +313,7 @@
info.intent = intent;
// the fallback icon
- if (!loadIcon(info)) {
+ if (!loadIconFromDb(info)) {
mIconCache.getTitleAndIcon(info, DEFAULT_LOOKUP_FLAG);
}
@@ -363,20 +376,11 @@
info.intent = newIntent;
UserCache userCache = UserCache.getInstance(mContext);
UserIconInfo userIconInfo = userCache.getUserInfo(user);
-
- if (loadIcon) {
- mIconCache.getTitleAndIcon(info, mActivityInfo,
- DEFAULT_LOOKUP_FLAG.withUseLowRes(useLowResIcon));
- if (mIconCache.isDefaultIcon(info.bitmap, user)) {
- loadIcon(info);
- }
- }
-
if (mActivityInfo != null) {
AppInfo.updateRuntimeFlagsForActivityTarget(info, mActivityInfo, userIconInfo,
ApiWrapper.INSTANCE.get(mContext), mPmHelper);
}
-
+ loadWorkspaceTitleAndIcon(useLowResIcon, loadIcon, info);
// from the db
if (TextUtils.isEmpty(info.title)) {
if (loadIcon) {
@@ -395,6 +399,32 @@
return info;
}
+ @VisibleForTesting
+ void loadWorkspaceTitleAndIcon(
+ boolean useLowResIcon,
+ boolean loadIconFromCache,
+ WorkspaceItemInfo info
+ ) {
+ boolean isPreArchived = Flags.enableSupportForArchiving()
+ && Flags.restoreArchivedAppIconsFromDb()
+ && info.isInactiveArchive();
+ boolean preArchivedIconNotFound = isPreArchived && !loadIconFromDb(info);
+ if (preArchivedIconNotFound) {
+ Log.d(TAG, "loadIconFromDb failed for pre-archived icon, loading from cache."
+ + " Component=" + info.getTargetComponent());
+ mIconCache.getTitleAndIcon(info, mActivityInfo,
+ DEFAULT_LOOKUP_FLAG.withUseLowRes(useLowResIcon));
+ } else if (loadIconFromCache && !info.isInactiveArchive()) {
+ mIconCache.getTitleAndIcon(info, mActivityInfo,
+ DEFAULT_LOOKUP_FLAG.withUseLowRes(useLowResIcon));
+ if (mIconCache.isDefaultIcon(info.bitmap, user)) {
+ Log.d(TAG, "Default Icon found in cache, trying DB instead. "
+ + " Component=" + info.getTargetComponent());
+ loadIconFromDb(info);
+ }
+ }
+ }
+
/**
* Returns a {@link ContentWriter} which can be used to update the current item.
*/
@@ -479,8 +509,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 +543,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..c1ee69b 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -26,12 +26,14 @@
import static com.android.launcher3.LauncherSettings.Favorites.DESKTOP_ICON_FLAG;
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.icons.CacheableShortcutInfo.convertShortcutsToCacheableShortcuts;
+import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
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 +84,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;
@@ -106,11 +107,13 @@
import com.android.launcher3.widget.WidgetInflater;
import java.util.ArrayList;
+import java.util.Arrays;
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.concurrent.CancellationException;
@@ -154,6 +157,8 @@
private Map<ShortcutKey, ShortcutInfo> mShortcutKeyToPinnedShortcuts;
private HashMap<PackageUserKey, SessionInfo> mInstallingPkgsCached;
+ private List<IconRequestInfo<WorkspaceItemInfo>> mWorkspaceIconRequestInfos = new ArrayList<>();
+
private boolean mStopped;
private final Set<PackageUserKey> mPendingPackages = new HashSet<>();
@@ -210,10 +215,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 +232,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);
@@ -410,7 +415,7 @@
protected void loadWorkspace(
List<CacheableShortcutInfo> allDeepShortcuts,
String selection,
- LoaderMemoryLogger memoryLogger,
+ @Nullable LoaderMemoryLogger memoryLogger,
@Nullable LauncherRestoreEventLogger restoreEventLogger
) {
Trace.beginSection("LoadWorkspace");
@@ -474,13 +479,12 @@
final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>();
queryPinnedShortcutsForUnlockedUsers(context, unlockedUsers);
- List<IconRequestInfo<WorkspaceItemInfo>> iconRequestInfos = new ArrayList<>();
-
+ mWorkspaceIconRequestInfos = new ArrayList<>();
WorkspaceItemProcessor itemProcessor = new WorkspaceItemProcessor(c, memoryLogger,
mUserCache, mUserManagerState, mLauncherApps, mPendingPackages,
mShortcutKeyToPinnedShortcuts, mApp, mBgDataModel,
mWidgetProvidersMap, installingPkgs, isSdCardReady,
- widgetInflater, mPmHelper, iconRequestInfos, unlockedUsers,
+ widgetInflater, mPmHelper, mWorkspaceIconRequestInfos, unlockedUsers,
allDeepShortcuts);
if (mStopped) {
@@ -490,7 +494,7 @@
itemProcessor.processItem();
}
}
- tryLoadWorkspaceIconsInBulk(iconRequestInfos);
+ tryLoadWorkspaceIconsInBulk(mWorkspaceIconRequestInfos);
} finally {
IOUtils.closeSilently(c);
}
@@ -523,14 +527,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 +589,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;
}
@@ -622,7 +625,9 @@
for (IconRequestInfo<WorkspaceItemInfo> iconRequestInfo : iconRequestInfos) {
WorkspaceItemInfo wai = iconRequestInfo.itemInfo;
if (mIconCache.isDefaultIcon(wai.bitmap, wai.user)) {
- iconRequestInfo.loadWorkspaceIcon(mApp.getContext());
+ logASplit("tryLoadWorkspaceIconsInBulk: default icon found for "
+ + wai.getTargetComponent() + ", will attempt to load from iconBlob");
+ iconRequestInfo.loadIconFromDbBlob(mApp.getContext());
}
}
} finally {
@@ -657,8 +662,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 +679,6 @@
synchronized (mBgDataModel) {
for (int id : deleted) {
- mBgDataModel.workspaceItems.remove(mBgDataModel.collections.get(id));
- mBgDataModel.collections.remove(id);
mBgDataModel.itemsIdMap.remove(id);
}
}
@@ -707,7 +708,7 @@
// Clear the list of apps
mBgAllAppsList.clear();
- List<IconRequestInfo<AppInfo>> iconRequestInfos = new ArrayList<>();
+ List<IconRequestInfo<AppInfo>> allAppsItemRequestInfos = new ArrayList<>();
boolean isWorkProfileQuiet = false;
boolean isPrivateProfileQuiet = false;
for (UserHandle user : profiles) {
@@ -747,15 +748,14 @@
}
}
- iconRequestInfos.add(new IconRequestInfo<>(
- appInfo, app, /* useLowResIcon= */ false));
- mBgAllAppsList.add(
- appInfo, app, false);
+ IconRequestInfo<AppInfo> iconRequestInfo = getAppInfoIconRequestInfo(
+ appInfo, app, mWorkspaceIconRequestInfos);
+ allAppsItemRequestInfos.add(iconRequestInfo);
+ mBgAllAppsList.add(appInfo, app, false);
}
allActivityList.addAll(apps);
}
-
if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
// get all active sessions and add them to the all apps list
for (PackageInstaller.SessionInfo info :
@@ -766,7 +766,7 @@
false);
if (promiseAppInfo != null) {
- iconRequestInfos.add(new IconRequestInfo<>(
+ allAppsItemRequestInfos.add(new IconRequestInfo<>(
promiseAppInfo,
/* launcherActivityInfo= */ null,
promiseAppInfo.getMatchingLookupFlag().useLowRes()));
@@ -775,9 +775,22 @@
}
Trace.beginSection("LoadAllAppsIconsInBulk");
+
try {
- mIconCache.getTitlesAndIconsInBulk(iconRequestInfos);
- iconRequestInfos.forEach(iconRequestInfo ->
+ mIconCache.getTitlesAndIconsInBulk(allAppsItemRequestInfos);
+ if (Flags.restoreArchivedAppIconsFromDb()) {
+ for (IconRequestInfo<AppInfo> iconRequestInfo : allAppsItemRequestInfos) {
+ AppInfo appInfo = iconRequestInfo.itemInfo;
+ if (mIconCache.isDefaultIcon(appInfo.bitmap, appInfo.user)) {
+ logASplit("LoadAllAppsIconsInBulk: default icon found for "
+ + appInfo.getTargetComponent()
+ + ", will attempt to load from iconBlob: "
+ + Arrays.toString(iconRequestInfo.iconBlob));
+ iconRequestInfo.loadIconFromDbBlob(mApp.getContext());
+ }
+ }
+ }
+ allAppsItemRequestInfos.forEach(iconRequestInfo ->
mBgAllAppsList.updateSectionName(iconRequestInfo.itemInfo));
} finally {
Trace.endSection();
@@ -800,6 +813,49 @@
return allActivityList;
}
+ @NonNull
+ @VisibleForTesting
+ IconRequestInfo<AppInfo> getAppInfoIconRequestInfo(
+ AppInfo appInfo,
+ LauncherActivityInfo activityInfo,
+ List<IconRequestInfo<WorkspaceItemInfo>> workspaceRequestInfos
+ ) {
+ if (Flags.restoreArchivedAppIconsFromDb()) {
+ Optional<IconRequestInfo<WorkspaceItemInfo>> workspaceIconRequest =
+ workspaceRequestInfos.stream()
+ .filter(request -> appInfo.getTargetComponent().equals(
+ request.itemInfo.getTargetComponent()))
+ .findFirst();
+
+ if (workspaceIconRequest.isPresent() && activityInfo.getApplicationInfo().isArchived) {
+ logASplit("getAppInfoIconRequestInfo:"
+ + " matching archived info found, loading icon blob into icon request."
+ + " Component=" + appInfo.getTargetComponent());
+ IconRequestInfo<AppInfo> iconRequestInfo = new IconRequestInfo<>(
+ appInfo,
+ activityInfo,
+ workspaceIconRequest.get().iconBlob,
+ false /* useLowResIcon= */
+ );
+ if (!iconRequestInfo.loadIconFromDbBlob(mApp.getContext())) {
+ Log.d(TAG, "AppInfo Icon failed to load from blob, using cache.");
+ mIconCache.getTitleAndIcon(
+ appInfo,
+ iconRequestInfo.launcherActivityInfo,
+ DEFAULT_LOOKUP_FLAG
+ );
+ }
+ return iconRequestInfo;
+ } else {
+ Log.d(TAG, "App not archived or workspace info not found"
+ + ", creating IconRequestInfo without icon blob."
+ + " Component:" + appInfo.getTargetComponent()
+ + ", isArchived: " + activityInfo.getApplicationInfo().isArchived);
+ }
+ }
+ return new IconRequestInfo<>(appInfo, activityInfo, false /* useLowResIcon= */);
+ }
+
private List<ShortcutInfo> loadDeepShortcuts() {
List<ShortcutInfo> allShortcuts = new ArrayList<>();
mBgDataModel.deepShortcutMap.clear();
@@ -819,18 +875,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..90f11a3 100644
--- a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
+++ b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
@@ -286,7 +286,7 @@
// If the pinned deep shortcut is no longer published,
// use the last saved icon instead of the default.
val csi = CacheableShortcutInfo(pinnedShortcut, appInfoWrapper)
- iconCache.getShortcutIcon(info, csi, c::loadIcon)
+ iconCache.getShortcutIcon(info, csi, c::loadIconFromDb)
if (appInfoWrapper.isSuspended()) {
info.runtimeStatusFlags =
info.runtimeStatusFlags or ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED
@@ -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/AppInfo.java b/src/com/android/launcher3/model/data/AppInfo.java
index 97b62b4..fe8fb5f 100644
--- a/src/com/android/launcher3/model/data/AppInfo.java
+++ b/src/com/android/launcher3/model/data/AppInfo.java
@@ -215,8 +215,7 @@
PackageManagerHelper.getLoadingProgress(lai),
PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING);
info.setNonResizeable(apiWrapper.isNonResizeableActivity(lai));
- info.setSupportsMultiInstance(
- pmHelper.supportsMultiInstance(lai.getComponentName()));
+ info.setSupportsMultiInstance(apiWrapper.supportsMultiInstance(lai));
return (oldProgressLevel != info.getProgressLevel())
|| (oldRuntimeStatusFlags != info.runtimeStatusFlags);
}
diff --git a/src/com/android/launcher3/model/data/IconRequestInfo.java b/src/com/android/launcher3/model/data/IconRequestInfo.java
index e77e527..42af018 100644
--- a/src/com/android/launcher3/model/data/IconRequestInfo.java
+++ b/src/com/android/launcher3/model/data/IconRequestInfo.java
@@ -64,23 +64,25 @@
}
/**
- * Loads this request's item info's title. This method should only be used on IconRequestInfos
- * for WorkspaceItemInfos.
+ * Loads this request's item info's title and icon from given iconBlob from Launcher.db.
+ * This method should only be used on {@link IconRequestInfo} for {@link WorkspaceItemInfo}
+ * or {@link AppInfo}.
*/
- public boolean loadWorkspaceIcon(Context context) {
- if (!(itemInfo instanceof WorkspaceItemInfo)) {
+ public boolean loadIconFromDbBlob(Context context) {
+ if (!(itemInfo instanceof WorkspaceItemInfo) && !(itemInfo instanceof AppInfo)) {
throw new IllegalStateException(
- "loadWorkspaceIcon should only be use for a WorkspaceItemInfos: " + itemInfo);
+ "loadIconFromDb should only be used for either WorkspaceItemInfo or AppInfo: "
+ + itemInfo);
}
try (LauncherIcons li = LauncherIcons.obtain(context)) {
- WorkspaceItemInfo info = (WorkspaceItemInfo) itemInfo;
- // Failed to load from resource, try loading from DB.
+ ItemInfoWithIcon info = itemInfo;
if (iconBlob == null) {
+ Log.d(TAG, "loadIconFromDb: icon blob null, returning. Component="
+ + info.getTargetComponent());
return false;
}
- info.bitmap = li.createIconBitmap(decodeByteArray(
- iconBlob, 0, iconBlob.length));
+ info.bitmap = li.createIconBitmap(decodeByteArray(iconBlob, 0, iconBlob.length));
return true;
} catch (Exception e) {
Log.e(TAG, "Failed to decode byte array for info " + itemInfo, e);
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/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
index 836ea4a..864fed0 100644
--- a/src/com/android/launcher3/notification/NotificationListener.java
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -170,6 +170,9 @@
for (NotificationsChangedListener listener : sNotificationsChangedListeners) {
listener.onNotificationPosted(msg.first, msg.second);
}
+ Log.i(TAG, "received notification posted event - " + msg.first);
+ } else {
+ Log.i(TAG, "received notification posted event, but there are no listeners");
}
break;
case MSG_NOTIFICATION_REMOVED:
@@ -178,6 +181,9 @@
for (NotificationsChangedListener listener : sNotificationsChangedListeners) {
listener.onNotificationRemoved(msg.first, msg.second);
}
+ Log.i(TAG, "received notification removed event - " + msg.first);
+ } else {
+ Log.i(TAG, "received notification removed event, but there are no listeners");
}
break;
case MSG_NOTIFICATION_FULL_REFRESH:
@@ -186,6 +192,11 @@
listener.onNotificationFullRefresh(
(List<StatusBarNotification>) message.obj);
}
+ ((List<StatusBarNotification>) message.obj).forEach(sbn -> Log.i(TAG,
+ "Handling notification state refresh for " + sbn.getPackageName() + "#"
+ + sbn.getUserId()));
+ } else {
+ Log.i(TAG, "received notification refresh event, but there are no listeners");
}
break;
}
@@ -205,6 +216,7 @@
@Override
public void onListenerConnected() {
super.onListenerConnected();
+ Log.i(TAG, "onListenerConnected");
sIsConnected = true;
// Register an observer to rebind the notification listener when dots are re-enabled.
@@ -230,6 +242,7 @@
@Override
public void onListenerDisconnected() {
super.onListenerDisconnected();
+ Log.i(TAG, "onListenerDisconnected");
sIsConnected = false;
mSettingsCache.unregister(NOTIFICATION_BADGING_URI, mNotificationSettingsChangedListener);
onNotificationFullRefresh();
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/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 1c9db17..e52ca6d 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -40,7 +40,6 @@
import androidx.annotation.LayoutRes;
import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
@@ -579,7 +578,7 @@
/**
* Dismisses the popup if it is no longer valid
*/
- public static void dismissInvalidPopup(BaseDraggingActivity activity) {
+ public static <T extends Context & ActivityContext> void dismissInvalidPopup(T activity) {
PopupContainerWithArrow popup = getOpen(activity);
if (popup != null && (!popup.mOriginalIcon.isAttachedToWindow()
|| !ShortcutUtil.supportsShortcuts((ItemInfo) popup.mOriginalIcon.getTag()))) {
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/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index 9b3292d..df27b54 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.secondarydisplay;
+import static com.android.launcher3.util.WallpaperThemeManager.setWallpaperDependentTheme;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Intent;
@@ -29,7 +31,7 @@
import androidx.annotation.UiThread;
import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.BaseActivity;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
@@ -59,7 +61,6 @@
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.BaseDragLayer;
-import com.android.launcher3.widget.picker.model.WidgetPickerDataProvider;
import java.util.HashMap;
import java.util.Map;
@@ -67,7 +68,7 @@
/**
* Launcher activity for secondary displays
*/
-public class SecondaryDisplayLauncher extends BaseDraggingActivity
+public class SecondaryDisplayLauncher extends BaseActivity
implements BgDataModel.Callbacks, DragController.DragListener {
private LauncherModel mModel;
@@ -77,7 +78,6 @@
private View mAppsButton;
private PopupDataProvider mPopupDataProvider;
- private WidgetPickerDataProvider mWidgetPickerDataProvider;
private boolean mAppDrawerShown = false;
@@ -90,6 +90,7 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ setWallpaperDependentTheme(this);
mModel = LauncherAppState.getInstance(this).getModel();
mDragController = new SecondaryDragController(this);
mSecondaryDisplayPredictions = SecondaryDisplayPredictions.newInstance(this);
@@ -202,14 +203,6 @@
}
@Override
- public View getRootView() {
- return mDragLayer;
- }
-
- @Override
- protected void reapplyUi() { }
-
- @Override
public BaseDragLayer getDragLayer() {
return mDragLayer;
}
@@ -317,11 +310,6 @@
}
@Override
- public WidgetPickerDataProvider getWidgetPickerDataProvider() {
- return mWidgetPickerDataProvider;
- }
-
- @Override
public OnClickListener getItemOnClickListener() {
return this::onIconClicked;
}
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..f1ea3a0
--- /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(
+ "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/statemanager/BaseState.java b/src/com/android/launcher3/statemanager/BaseState.java
index f6b610c..b7dd2bf 100644
--- a/src/com/android/launcher3/statemanager/BaseState.java
+++ b/src/com/android/launcher3/statemanager/BaseState.java
@@ -15,15 +15,13 @@
*/
package com.android.launcher3.statemanager;
-import android.content.Context;
-
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.views.ActivityContext;
/**
* Interface representing a state of a StatefulContainer
*/
-public interface BaseState<T extends BaseState> {
+public interface BaseState<T> {
// Flag to indicate that Launcher is non-interactive in this state
int FLAG_NON_INTERACTIVE = 1 << 0;
@@ -37,8 +35,7 @@
/**
* @return How long the animation to this state should take (or from this state to NORMAL).
*/
- <DEVICE_PROFILE_CONTEXT extends Context & ActivityContext>
- int getTransitionDuration(DEVICE_PROFILE_CONTEXT context, boolean isToState);
+ int getTransitionDuration(ActivityContext context, boolean isToState);
/**
* Returns the state to go back to from this state
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index 763f3ba..a125331 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -27,7 +27,6 @@
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
-import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
@@ -50,50 +49,49 @@
/**
* Class to manage transitions between different states for a StatefulActivity based on different
* states
- * @param STATE_TYPE Basestate used by the state manager
- * @param STATEFUL_CONTAINER container object used to manage state
+ * @param <S> Basestate used by the state manager
+ * @param <T> container object used to manage state
*/
-public class StateManager<STATE_TYPE extends BaseState<STATE_TYPE>,
- STATEFUL_CONTAINER extends Context & StatefulContainer<STATE_TYPE>> {
+public class StateManager<S extends BaseState<S>, T extends StatefulContainer<S>> {
public static final String TAG = "StateManager";
// b/279059025, b/325463989
private static final boolean DEBUG = true;
- private final AnimationState mConfig = new AnimationState();
+ private final AnimationState<S> mConfig = new AnimationState<>();
private final Handler mUiHandler;
- private final STATEFUL_CONTAINER mStatefulContainer;
- private final ArrayList<StateListener<STATE_TYPE>> mListeners = new ArrayList<>();
- private final STATE_TYPE mBaseState;
+ private final T mContainer;
+ private final ArrayList<StateListener<S>> mListeners = new ArrayList<>();
+ private final S mBaseState;
// Animators which are run on properties also controlled by state animations.
- private final AtomicAnimationFactory mAtomicAnimationFactory;
+ private final AtomicAnimationFactory<S> mAtomicAnimationFactory;
- private StateHandler<STATE_TYPE>[] mStateHandlers;
- private STATE_TYPE mState;
+ private StateHandler<S>[] mStateHandlers;
+ private S mState;
- private STATE_TYPE mLastStableState;
- private STATE_TYPE mCurrentStableState;
+ private S mLastStableState;
+ private S mCurrentStableState;
- private STATE_TYPE mRestState;
+ private S mRestState;
- public StateManager(STATEFUL_CONTAINER container, STATE_TYPE baseState) {
+ public StateManager(T container, S baseState) {
mUiHandler = new Handler(Looper.getMainLooper());
- mStatefulContainer = container;
+ mContainer = container;
mBaseState = baseState;
mState = mLastStableState = mCurrentStableState = baseState;
mAtomicAnimationFactory = container.createAtomicAnimationFactory();
}
- public STATE_TYPE getState() {
+ public S getState() {
return mState;
}
- public STATE_TYPE getTargetState() {
- return (STATE_TYPE) mConfig.targetState;
+ public S getTargetState() {
+ return mConfig.targetState;
}
- public STATE_TYPE getCurrentStableState() {
+ public S getCurrentStableState() {
return mCurrentStableState;
}
@@ -115,20 +113,20 @@
writer.println(prefix + "\tisInTransition:" + isInTransition());
}
- public StateHandler<STATE_TYPE>[] getStateHandlers() {
+ public StateHandler<S>[] getStateHandlers() {
if (mStateHandlers == null) {
- ArrayList<StateHandler<STATE_TYPE>> handlers = new ArrayList<>();
- mStatefulContainer.collectStateHandlers(handlers);
+ ArrayList<StateHandler<S>> handlers = new ArrayList<>();
+ mContainer.collectStateHandlers(handlers);
mStateHandlers = handlers.toArray(new StateHandler[handlers.size()]);
}
return mStateHandlers;
}
- public void addStateListener(StateListener listener) {
+ public void addStateListener(StateListener<S> listener) {
mListeners.add(listener);
}
- public void removeStateListener(StateListener listener) {
+ public void removeStateListener(StateListener<S> listener) {
mListeners.remove(listener);
}
@@ -136,14 +134,14 @@
* Returns true if the state changes should be animated.
*/
public boolean shouldAnimateStateChange() {
- return mStatefulContainer.shouldAnimateStateChange();
+ return mContainer.shouldAnimateStateChange();
}
/**
* @return {@code true} if the state matches the current state and there is no active
* transition to different state.
*/
- public boolean isInStableState(STATE_TYPE state) {
+ public boolean isInStableState(S state) {
return mState == state && mCurrentStableState == state
&& (mConfig.targetState == null || mConfig.targetState == state);
}
@@ -156,23 +154,23 @@
}
/**
- * @see #goToState(STATE_TYPE, boolean, AnimatorListener)
+ * @see #goToState(S, boolean, AnimatorListener)
*/
- public void goToState(STATE_TYPE state) {
+ public void goToState(S state) {
goToState(state, shouldAnimateStateChange());
}
/**
- * @see #goToState(STATE_TYPE, boolean, AnimatorListener)
+ * @see #goToState(S, boolean, AnimatorListener)
*/
- public void goToState(STATE_TYPE state, AnimatorListener listener) {
+ public void goToState(S state, AnimatorListener listener) {
goToState(state, shouldAnimateStateChange(), listener);
}
/**
- * @see #goToState(STATE_TYPE, boolean, AnimatorListener)
+ * @see #goToState(S, boolean, AnimatorListener)
*/
- public void goToState(STATE_TYPE state, boolean animated) {
+ public void goToState(S state, boolean animated) {
goToState(state, animated, 0, null);
}
@@ -183,21 +181,21 @@
* true otherwise
* @param listener any action to perform at the end of the transition, or null.
*/
- public void goToState(STATE_TYPE state, boolean animated, AnimatorListener listener) {
+ public void goToState(S state, boolean animated, AnimatorListener listener) {
goToState(state, animated, 0, listener);
}
/**
* Changes the Launcher state to the provided state after the given delay.
*/
- public void goToState(STATE_TYPE state, long delay, AnimatorListener listener) {
+ public void goToState(S state, long delay, AnimatorListener listener) {
goToState(state, true, delay, listener);
}
/**
* Changes the Launcher state to the provided state after the given delay.
*/
- public void goToState(STATE_TYPE state, long delay) {
+ public void goToState(S state, long delay) {
goToState(state, true, delay, null);
}
@@ -219,7 +217,7 @@
cancelAnimation();
}
if (mConfig.currentAnimation == null) {
- for (StateHandler handler : getStateHandlers()) {
+ for (StateHandler<S> handler : getStateHandlers()) {
handler.setState(mState);
}
if (wasInAnimation) {
@@ -230,21 +228,21 @@
/** Handles backProgress in predictive back gesture by passing it to state handlers. */
public void onBackProgressed(
- STATE_TYPE toState, @FloatRange(from = 0.0, to = 1.0) float backProgress) {
- for (StateHandler handler : getStateHandlers()) {
+ S toState, @FloatRange(from = 0.0, to = 1.0) float backProgress) {
+ for (StateHandler<S> handler : getStateHandlers()) {
handler.onBackProgressed(toState, backProgress);
}
}
/** Handles back cancelled event in predictive back gesture by passing it to state handlers. */
- public void onBackCancelled(STATE_TYPE toState) {
- for (StateHandler handler : getStateHandlers()) {
+ public void onBackCancelled(S toState) {
+ for (StateHandler<S> handler : getStateHandlers()) {
handler.onBackCancelled(toState);
}
}
private void goToState(
- STATE_TYPE state, boolean animated, long delay, AnimatorListener listener) {
+ S state, boolean animated, long delay, AnimatorListener listener) {
if (enableStateManagerProtoLog()) {
StateManagerProtoLogProxy.logGoToState(
mState, state, getTrimmedStackTrace("StateManager.goToState"));
@@ -254,7 +252,7 @@
}
animated &= areAnimatorsEnabled();
- if (mStatefulContainer.isInState(state)) {
+ if (getState() == state) {
if (mConfig.currentAnimation == null) {
// Run any queued runnable
if (listener != null) {
@@ -273,13 +271,13 @@
}
// Cancel the current animation. This will reset mState to mCurrentStableState, so store it.
- STATE_TYPE fromState = mState;
+ S fromState = mState;
cancelAnimation();
if (!animated) {
mAtomicAnimationFactory.cancelAllStateElementAnimation();
onStateTransitionStart(state);
- for (StateHandler handler : getStateHandlers()) {
+ for (StateHandler<S> handler : getStateHandlers()) {
handler.setState(state);
}
@@ -306,13 +304,13 @@
}
}
- private void goToStateAnimated(STATE_TYPE state, STATE_TYPE fromState,
+ private void goToStateAnimated(S state, S fromState,
AnimatorListener listener) {
// Since state mBaseState can be reached from multiple states, just assume that the
// transition plays in reverse and use the same duration as previous state.
mConfig.duration = state == mBaseState
- ? fromState.getTransitionDuration(mStatefulContainer, false /* isToState */)
- : state.getTransitionDuration(mStatefulContainer, true /* isToState */);
+ ? fromState.getTransitionDuration(mContainer, false /* isToState */)
+ : state.getTransitionDuration(mContainer, true /* isToState */);
prepareForAtomicAnimation(fromState, state, mConfig);
AnimatorSet animation = createAnimationToNewWorkspaceInternal(state).buildAnim();
if (listener != null) {
@@ -326,7 +324,7 @@
* - Setting interpolators for various animations included in the state transition.
* - Setting some start values (e.g. scale) for views that are hidden but about to be shown.
*/
- public void prepareForAtomicAnimation(STATE_TYPE fromState, STATE_TYPE toState,
+ public void prepareForAtomicAnimation(S fromState, S toState,
StateAnimationConfig config) {
mAtomicAnimationFactory.prepareForAtomicAnimation(fromState, toState, config);
}
@@ -335,7 +333,7 @@
* Creates an animation representing atomic transitions between the provided states
*/
public AnimatorSet createAtomicAnimation(
- STATE_TYPE fromState, STATE_TYPE toState, StateAnimationConfig config) {
+ S fromState, S toState, StateAnimationConfig config) {
if (enableStateManagerProtoLog()) {
StateManagerProtoLogProxy.logCreateAtomicAnimation(
mState, toState, getTrimmedStackTrace("StateManager.createAtomicAnimation"));
@@ -348,7 +346,7 @@
PendingAnimation builder = new PendingAnimation(config.duration);
prepareForAtomicAnimation(fromState, toState, config);
- for (StateHandler handler : mStatefulContainer.getStateManager().getStateHandlers()) {
+ for (StateHandler<S> handler : getStateHandlers()) {
handler.setStateWithAnimation(toState, config, builder);
}
return builder.buildAnim();
@@ -362,19 +360,19 @@
* accuracy.
*/
public AnimatorPlaybackController createAnimationToNewWorkspace(
- STATE_TYPE state, long duration) {
+ S state, long duration) {
return createAnimationToNewWorkspace(state, duration, 0 /* animFlags */);
}
public AnimatorPlaybackController createAnimationToNewWorkspace(
- STATE_TYPE state, long duration, @AnimationFlags int animFlags) {
+ S state, long duration, @AnimationFlags int animFlags) {
StateAnimationConfig config = new StateAnimationConfig();
config.duration = duration;
config.animFlags = animFlags;
return createAnimationToNewWorkspace(state, config);
}
- public AnimatorPlaybackController createAnimationToNewWorkspace(STATE_TYPE state,
+ public AnimatorPlaybackController createAnimationToNewWorkspace(S state,
StateAnimationConfig config) {
config.animProps |= StateAnimationConfig.USER_CONTROLLED;
cancelAnimation();
@@ -384,10 +382,10 @@
return mConfig.playbackController;
}
- private PendingAnimation createAnimationToNewWorkspaceInternal(final STATE_TYPE state) {
+ private PendingAnimation createAnimationToNewWorkspaceInternal(final S state) {
PendingAnimation builder = new PendingAnimation(mConfig.duration);
if (!mConfig.hasAnimationFlag(SKIP_ALL_ANIMATIONS)) {
- for (StateHandler handler : getStateHandlers()) {
+ for (StateHandler<S> handler : getStateHandlers()) {
handler.setStateWithAnimation(state, mConfig, builder);
}
}
@@ -396,7 +394,7 @@
return builder;
}
- private AnimatorListener createStateAnimationListener(STATE_TYPE state) {
+ private AnimatorListener createStateAnimationListener(S state) {
return new AnimationSuccessListener() {
@Override
@@ -412,9 +410,9 @@
};
}
- private void onStateTransitionStart(STATE_TYPE state) {
+ private void onStateTransitionStart(S state) {
mState = state;
- mStatefulContainer.onStateSetStart(mState);
+ mContainer.onStateSetStart(mState);
if (enableStateManagerProtoLog()) {
StateManagerProtoLogProxy.logOnStateTransitionStart(state);
@@ -426,14 +424,14 @@
}
}
- private void onStateTransitionEnd(STATE_TYPE state) {
+ private void onStateTransitionEnd(S state) {
// Only change the stable states after the transitions have finished
if (state != mCurrentStableState) {
mLastStableState = state.getHistoryForState(mCurrentStableState);
mCurrentStableState = state;
}
- mStatefulContainer.onStateSetEnd(state);
+ mContainer.onStateSetEnd(state);
if (state == mBaseState) {
setRestState(null);
}
@@ -448,7 +446,7 @@
}
}
- public STATE_TYPE getLastState() {
+ public S getLastState() {
return mLastStableState;
}
@@ -468,11 +466,11 @@
}
}
- public STATE_TYPE getRestState() {
+ public S getRestState() {
return mRestState == null ? mBaseState : mRestState;
}
- public void setRestState(STATE_TYPE restState) {
+ public void setRestState(S restState) {
mRestState = restState;
}
@@ -518,7 +516,7 @@
* @param anim The custom animation to the given state.
* @param toState The state we are animating towards.
*/
- public void setCurrentAnimation(AnimatorSet anim, STATE_TYPE toState) {
+ public void setCurrentAnimation(AnimatorSet anim, S toState) {
cancelAnimation();
setCurrentAnimation(anim);
anim.addListener(createStateAnimationListener(toState));
@@ -691,10 +689,10 @@
/** Handles backProgress in predictive back gesture for target state. */
default void onBackProgressed(
- STATE_TYPE toState, @FloatRange(from = 0.0, to = 1.0) float backProgress) {};
+ STATE_TYPE toState, @FloatRange(from = 0.0, to = 1.0) float backProgress) {}
/** Handles back cancelled event in predictive back gesture for target state. */
- default void onBackCancelled(STATE_TYPE toState) {};
+ default void onBackCancelled(STATE_TYPE toState) {}
}
public interface StateListener<STATE_TYPE> {
diff --git a/src/com/android/launcher3/statemanager/StatefulActivity.java b/src/com/android/launcher3/statemanager/StatefulActivity.java
index f21e5da..445701d 100644
--- a/src/com/android/launcher3/statemanager/StatefulActivity.java
+++ b/src/com/android/launcher3/statemanager/StatefulActivity.java
@@ -18,10 +18,8 @@
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
-import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_RECREATE_TO_UPDATE_THEME;
import static com.android.launcher3.LauncherState.FLAG_NON_INTERACTIVE;
-import android.content.Context;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Handler;
@@ -30,9 +28,8 @@
import android.view.View;
import androidx.annotation.CallSuper;
-import androidx.annotation.NonNull;
-import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.BaseActivity;
import com.android.launcher3.LauncherRootView;
import com.android.launcher3.Utilities;
import com.android.launcher3.statemanager.StateManager.StateHandler;
@@ -46,7 +43,7 @@
* @param <STATE_TYPE> Type of state object
*/
public abstract class StatefulActivity<STATE_TYPE extends BaseState<STATE_TYPE>>
- extends BaseDraggingActivity implements StatefulContainer<STATE_TYPE> {
+ extends BaseActivity implements StatefulContainer<STATE_TYPE> {
public final Handler mHandler = new Handler();
private final Runnable mHandleDeferredResume = this::handleDeferredResume;
@@ -56,7 +53,6 @@
protected Configuration mOldConfig;
private int mOldRotation;
- private boolean mRecreateToUpdateTheme = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -66,18 +62,6 @@
mOldRotation = WindowManagerProxy.INSTANCE.get(this).getRotation(this);
}
- @Override
- protected void onSaveInstanceState(@NonNull Bundle outState) {
- outState.putBoolean(RUNTIME_STATE_RECREATE_TO_UPDATE_THEME, mRecreateToUpdateTheme);
- super.onSaveInstanceState(outState);
- }
-
- @Override
- protected void recreateToUpdateTheme() {
- mRecreateToUpdateTheme = true;
- super.recreateToUpdateTheme();
- }
-
/**
* Create handlers to control the property changes for this activity
*/
@@ -214,11 +198,6 @@
mOldRotation = rotation;
}
- @Override
- public Context getContext() {
- return this;
- }
-
/**
* Logic for when device configuration changes (rotation, screen size change, multi-window,
* etc.)
diff --git a/src/com/android/launcher3/statemanager/StatefulContainer.java b/src/com/android/launcher3/statemanager/StatefulContainer.java
index b10af0a..83a2fdc 100644
--- a/src/com/android/launcher3/statemanager/StatefulContainer.java
+++ b/src/com/android/launcher3/statemanager/StatefulContainer.java
@@ -20,8 +20,6 @@
import static com.android.launcher3.LauncherState.FLAG_CLOSE_POPUPS;
import static com.android.launcher3.statemanager.BaseState.FLAG_NON_INTERACTIVE;
-import android.content.Context;
-import android.content.ContextWrapper;
import android.content.res.Configuration;
import androidx.annotation.CallSuper;
@@ -40,23 +38,6 @@
ActivityContext {
/**
- * Returns an instance of an implementation of StatefulContainer
- *
- * @param context will find instance of StatefulContainer from given context.
- */
- static <T extends StatefulContainer> T fromContext(Context context) {
- if (context instanceof StatefulContainer) {
- return (T) context;
- } else if (context instanceof ContextWrapper) {
- return fromContext(((ContextWrapper) context).getBaseContext());
- } else {
- throw new IllegalArgumentException("Cannot find StatefulContainer in parent tree");
- }
- }
-
- Context getContext();
-
- /**
* Creates a factory for atomic state animations
*/
default StateManager.AtomicAnimationFactory<STATE_TYPE> createAtomicAnimationFactory() {
diff --git a/src/com/android/launcher3/states/EditModeState.kt b/src/com/android/launcher3/states/EditModeState.kt
index 6ff47ae..268a373 100644
--- a/src/com/android/launcher3/states/EditModeState.kt
+++ b/src/com/android/launcher3/states/EditModeState.kt
@@ -36,11 +36,7 @@
FLAG_WORKSPACE_HAS_BACKGROUNDS)
}
- override fun <T> getTransitionDuration(context: T, isToState: Boolean): Int where
- T : Context?,
- T : ActivityContext? {
- return 150
- }
+ override fun getTransitionDuration(context: ActivityContext, isToState: Boolean) = 150
override fun <T> getDepthUnchecked(context: T): Float where T : Context?, T : ActivityContext? {
if (enableScalingRevealHomeAnimation()) {
diff --git a/src/com/android/launcher3/states/HintState.java b/src/com/android/launcher3/states/HintState.java
index bf2fb30..ed22d39 100644
--- a/src/com/android/launcher3/states/HintState.java
+++ b/src/com/android/launcher3/states/HintState.java
@@ -26,6 +26,7 @@
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.util.Themes;
+import com.android.launcher3.views.ActivityContext;
/**
* Scale down workspace/hotseat to hint at going to either overview (on pause) or first home screen.
@@ -46,7 +47,7 @@
}
@Override
- public int getTransitionDuration(Context context, boolean isToState) {
+ public int getTransitionDuration(ActivityContext context, boolean isToState) {
return 80;
}
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index 2e57ed8..15e6c61 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -24,6 +24,7 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Workspace;
+import com.android.launcher3.views.ActivityContext;
/**
* Definition for spring loaded state used during drag and drop.
@@ -41,7 +42,7 @@
}
@Override
- public int getTransitionDuration(Context context, boolean isToState) {
+ public int getTransitionDuration(ActivityContext context, boolean isToState) {
return 150;
}
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 3817563..4509bae 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -16,6 +16,7 @@
package com.android.launcher3.touch;
import static com.android.app.animation.Interpolators.scrollInterpolatorForVelocity;
+import static com.android.launcher3.Flags.enableMouseInteractionChanges;
import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
import static com.android.launcher3.LauncherAnimUtils.TABLET_BOTTOM_SHEET_SUCCESS_TRANSITION_PROGRESS;
import static com.android.launcher3.LauncherAnimUtils.newCancelListener;
@@ -33,6 +34,7 @@
import android.animation.Animator.AnimatorListener;
import android.animation.ValueAnimator;
+import android.view.InputDevice;
import android.view.MotionEvent;
import com.android.launcher3.Launcher;
@@ -107,7 +109,9 @@
ignoreSlopWhenSettling = true;
} else {
directionsToDetectScroll = getSwipeDirection();
- if (directionsToDetectScroll == 0) {
+ boolean ignoreMouseScroll = ev.getSource() == InputDevice.SOURCE_MOUSE
+ && enableMouseInteractionChanges();
+ if (directionsToDetectScroll == 0 || ignoreMouseScroll) {
mNoIntercept = true;
return false;
}
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/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
index b69bc17..d72e6f9 100644
--- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java
+++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
@@ -21,6 +21,7 @@
import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
+import static com.android.launcher3.Flags.enableMouseInteractionChanges;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_CLOSE_TAP_OUTSIDE;
@@ -31,6 +32,7 @@
import android.graphics.Rect;
import android.view.GestureDetector;
import android.view.HapticFeedbackConstants;
+import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
@@ -41,7 +43,6 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.Workspace;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.testing.TestLogging;
@@ -193,6 +194,10 @@
@Override
public void onLongPress(MotionEvent event) {
+ if (event.getSource() == InputDevice.SOURCE_MOUSE && enableMouseInteractionChanges()) {
+ // Stop mouse long press events from showing the menu.
+ return;
+ }
maybeShowMenu();
}
diff --git a/src/com/android/launcher3/util/ApiWrapper.java b/src/com/android/launcher3/util/ApiWrapper.java
index 467a7ec..56337b0 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;
@@ -58,10 +59,13 @@
LauncherAppComponent::getApiWrapper);
protected final Context mContext;
+ private final String[] mLegacyMultiInstanceSupportedApps;
@Inject
public ApiWrapper(@ApplicationContext Context context) {
mContext = context;
+ mLegacyMultiInstanceSupportedApps = context.getResources().getStringArray(
+ com.android.launcher3.R.array.config_appsSupportMultiInstancesSplit);
}
/**
@@ -120,6 +124,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")
@@ -141,12 +160,31 @@
/**
* Checks if an activity is flagged as non-resizeable.
*/
- public boolean isNonResizeableActivity(LauncherActivityInfo lai) {
- // Overridden in quickstep
+ public boolean isNonResizeableActivity(@NonNull LauncherActivityInfo lai) {
+ // Overridden in Quickstep
return false;
}
/**
+ * Checks if an activity supports multi-instance.
+ */
+ public boolean supportsMultiInstance(@NonNull LauncherActivityInfo lai) {
+ // Check app multi-instance properties after V
+ if (!Utilities.ATLEAST_V) {
+ return false;
+ }
+
+ // Check the legacy hardcoded allowlist first
+ for (String pkg : mLegacyMultiInstanceSupportedApps) {
+ if (pkg.equals(lai.getComponentName().getPackageName())) {
+ return true;
+ }
+ }
+
+ // Overridden in Quickstep
+ return false;
+ }
+ /**
* Starts an Activity which can be used to set this Launcher as the HOME app, via a consent
* screen. In case the consent screen cannot be shown, or the user does not set current Launcher
* as HOME app, a toast asking the user to do the latter is shown.
@@ -171,6 +209,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/LockedUserState.kt b/src/com/android/launcher3/util/LockedUserState.kt
index c8d86d4..a6a6ceb 100644
--- a/src/com/android/launcher3/util/LockedUserState.kt
+++ b/src/com/android/launcher3/util/LockedUserState.kt
@@ -20,10 +20,17 @@
import android.os.Process
import android.os.UserManager
import androidx.annotation.VisibleForTesting
+import com.android.launcher3.dagger.ApplicationContext
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
import com.android.launcher3.util.Executors.MAIN_EXECUTOR
import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
+import javax.inject.Inject
-class LockedUserState(private val mContext: Context) : SafeCloseable {
+@LauncherAppSingleton
+class LockedUserState
+@Inject
+constructor(@ApplicationContext private val context: Context, lifeCycle: DaggerSingletonTracker) {
val isUserUnlockedAtLauncherStartup: Boolean
var isUserUnlocked = false
private set(value) {
@@ -36,7 +43,7 @@
private val mUserUnlockedActions: RunnableList = RunnableList()
@VisibleForTesting
- val mUserUnlockedReceiver =
+ val userUnlockedReceiver =
SimpleBroadcastReceiver(UI_HELPER_EXECUTOR) {
if (Intent.ACTION_USER_UNLOCKED == it.action) {
isUserUnlocked = true
@@ -53,8 +60,8 @@
isUserUnlocked = checkIsUserUnlocked()
isUserUnlockedAtLauncherStartup = isUserUnlocked
if (!isUserUnlocked) {
- mUserUnlockedReceiver.register(
- mContext,
+ userUnlockedReceiver.register(
+ context,
{
// If user is unlocked while registering broadcast receiver, we should update
// [isUserUnlocked], which will call [notifyUserUnlocked] in setter
@@ -62,22 +69,18 @@
MAIN_EXECUTOR.execute { isUserUnlocked = true }
}
},
- Intent.ACTION_USER_UNLOCKED
+ Intent.ACTION_USER_UNLOCKED,
)
}
+ lifeCycle.addCloseable { userUnlockedReceiver.unregisterReceiverSafely(context) }
}
private fun checkIsUserUnlocked() =
- mContext.getSystemService(UserManager::class.java)!!.isUserUnlocked(Process.myUserHandle())
+ context.getSystemService(UserManager::class.java)!!.isUserUnlocked(Process.myUserHandle())
private fun notifyUserUnlocked() {
mUserUnlockedActions.executeAllAndDestroy()
- mUserUnlockedReceiver.unregisterReceiverSafely(mContext)
- }
-
- /** Stops the receiver from listening for ACTION_USER_UNLOCK broadcasts. */
- override fun close() {
- mUserUnlockedReceiver.unregisterReceiverSafely(mContext)
+ userUnlockedReceiver.unregisterReceiverSafely(context)
}
/**
@@ -88,9 +91,7 @@
mUserUnlockedActions.add(action)
}
- /**
- * Removes a previously queued `Runnable` to be run when the user is unlocked.
- */
+ /** Removes a previously queued `Runnable` to be run when the user is unlocked. */
fun removeOnUserUnlockedRunnable(action: Runnable) {
mUserUnlockedActions.remove(action)
}
@@ -98,7 +99,7 @@
companion object {
@VisibleForTesting
@JvmField
- val INSTANCE = MainThreadInitializedObject { LockedUserState(it) }
+ val INSTANCE = DaggerSingletonObject(LauncherAppComponent::getLockedUserState)
@JvmStatic fun get(context: Context): LockedUserState = INSTANCE.get(context)
}
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/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 4b60d98..3d01f24 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -16,8 +16,6 @@
package com.android.launcher3.util;
-import static android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI;
-
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
import android.content.ActivityNotFoundException;
@@ -41,7 +39,6 @@
import com.android.launcher3.PendingAddItemInfo;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
import com.android.launcher3.dagger.ApplicationContext;
import com.android.launcher3.dagger.LauncherAppSingleton;
import com.android.launcher3.dagger.LauncherBaseAppComponent;
@@ -77,15 +74,11 @@
@NonNull
private final LauncherApps mLauncherApps;
- private final String[] mLegacyMultiInstanceSupportedApps;
-
@Inject
public PackageManagerHelper(@ApplicationContext final Context context) {
mContext = context;
mPm = context.getPackageManager();
mLauncherApps = Objects.requireNonNull(context.getSystemService(LauncherApps.class));
- mLegacyMultiInstanceSupportedApps = mContext.getResources().getStringArray(
- R.array.config_appsSupportMultiInstancesSplit);
}
/**
@@ -193,39 +186,6 @@
}
/**
- * Returns whether the given component or its application has the multi-instance property set.
- */
- public boolean supportsMultiInstance(@NonNull ComponentName component) {
- // Check the legacy hardcoded allowlist first
- for (String pkg : mLegacyMultiInstanceSupportedApps) {
- if (pkg.equals(component.getPackageName())) {
- return true;
- }
- }
-
- // Check app multi-instance properties after V
- if (!Utilities.ATLEAST_V) {
- return false;
- }
-
- try {
- // Check if the component has the multi-instance property
- return mPm.getProperty(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, component)
- .getBoolean();
- } catch (PackageManager.NameNotFoundException e1) {
- try {
- // Check if the application has the multi-instance property
- return mPm.getProperty(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI,
- component.getPackageName())
- .getBoolean();
- } catch (PackageManager.NameNotFoundException e2) {
- // Fall through
- }
- }
- return false;
- }
-
- /**
* Returns whether two apps should be considered the same for multi-instance purposes, which
* requires additional checks to ensure they can be started as multiple instances.
*/
diff --git a/src/com/android/launcher3/util/SettingsCache.java b/src/com/android/launcher3/util/SettingsCache.java
index 8fe6e93..fa183c8 100644
--- a/src/com/android/launcher3/util/SettingsCache.java
+++ b/src/com/android/launcher3/util/SettingsCache.java
@@ -34,11 +34,11 @@
import com.android.launcher3.dagger.LauncherAppSingleton;
import com.android.launcher3.dagger.LauncherBaseAppComponent;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Function;
import javax.inject.Inject;
@@ -57,7 +57,7 @@
* Cache will also be updated if a key queried is missing (even if it has no listeners registered).
*/
@LauncherAppSingleton
-public class SettingsCache extends ContentObserver implements SafeCloseable {
+public class SettingsCache extends ContentObserver {
/** Hidden field Settings.Secure.NOTIFICATION_BADGING */
public static final Uri NOTIFICATION_BADGING_URI =
@@ -79,11 +79,17 @@
private static final String SYSTEM_URI_PREFIX = Settings.System.CONTENT_URI.toString();
private static final String GLOBAL_URI_PREFIX = Settings.Global.CONTENT_URI.toString();
+ private final Function<Uri, CopyOnWriteArrayList<OnChangeListener>> mListenerMapper = uri -> {
+ registerUriAsync(uri);
+ return new CopyOnWriteArrayList<>();
+ };
+
/**
* Caches the last seen value for registered keys.
*/
- private Map<Uri, Boolean> mKeyCache = new ConcurrentHashMap<>();
- private final Map<Uri, CopyOnWriteArrayList<OnChangeListener>> mListenerMap = new HashMap<>();
+ private final Map<Uri, Boolean> mKeyCache = new ConcurrentHashMap<>();
+ private final Map<Uri, CopyOnWriteArrayList<OnChangeListener>> mListenerMap =
+ new ConcurrentHashMap<>();
protected final ContentResolver mResolver;
/**
@@ -96,12 +102,8 @@
SettingsCache(@ApplicationContext Context context, DaggerSingletonTracker tracker) {
super(new Handler(Looper.getMainLooper()));
mResolver = context.getContentResolver();
- tracker.addCloseable(this);
- }
-
- @Override
- public void close() {
- UI_HELPER_EXECUTOR.execute(() -> mResolver.unregisterContentObserver(this));
+ tracker.addCloseable(() ->
+ UI_HELPER_EXECUTOR.execute(() -> mResolver.unregisterContentObserver(this)));
}
@Override
@@ -109,11 +111,12 @@
// We use default of 1, but if we're getting an onChange call, can assume a non-default
// value will exist
boolean newVal = updateValue(uri, 1 /* Effectively Unused */);
- if (!mListenerMap.containsKey(uri)) {
+ List<OnChangeListener> listeners = mListenerMap.get(uri);
+ if (listeners == null) {
return;
}
- for (OnChangeListener listener : mListenerMap.get(uri)) {
+ for (OnChangeListener listener : listeners) {
listener.onSettingsChanged(newVal);
}
}
@@ -138,22 +141,17 @@
}
}
+ private void registerUriAsync(Uri uri) {
+ UI_HELPER_EXECUTOR.execute(() -> mResolver.registerContentObserver(uri, false, this));
+ }
+
/**
* Does not de-dupe if you add same listeners for the same key multiple times.
* Unregister once complete using {@link #unregister(Uri, OnChangeListener)}
*/
@UiThread
public void register(Uri uri, OnChangeListener changeListener) {
- Preconditions.assertUIThread();
- if (mListenerMap.containsKey(uri)) {
- mListenerMap.get(uri).add(changeListener);
- } else {
- CopyOnWriteArrayList<OnChangeListener> l = new CopyOnWriteArrayList<>();
- l.add(changeListener);
- mListenerMap.put(uri, l);
- UI_HELPER_EXECUTOR.execute(
- () -> mResolver.registerContentObserver(uri, false, this));
- }
+ mListenerMap.computeIfAbsent(uri, mListenerMapper).add(changeListener);
}
private boolean updateValue(Uri keyUri, int defaultValue) {
diff --git a/src/com/android/launcher3/util/WallpaperThemeManager.kt b/src/com/android/launcher3/util/WallpaperThemeManager.kt
new file mode 100644
index 0000000..c48ef07
--- /dev/null
+++ b/src/com/android/launcher3/util/WallpaperThemeManager.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.app.Activity
+import android.content.ComponentCallbacks
+import android.content.res.Configuration
+import android.os.Bundle
+import com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_RECREATE_TO_UPDATE_THEME
+import com.android.launcher3.R
+
+/** Utility class to manage activity's theme in case it is wallpaper dependent */
+class WallpaperThemeManager private constructor(private val activity: Activity) :
+ OnColorHintListener, ActivityLifecycleCallbacksAdapter, ComponentCallbacks {
+
+ private var themeRes: Int = R.style.AppTheme
+
+ private var recreateToUpdateTheme = false
+
+ init {
+ // Update theme
+ WallpaperColorHints.get(activity).registerOnColorHintsChangedListener(this)
+ val expectedTheme = Themes.getActivityThemeRes(activity)
+ if (expectedTheme != themeRes) {
+ themeRes = expectedTheme
+ activity.setTheme(expectedTheme)
+ }
+
+ activity.registerActivityLifecycleCallbacks(this)
+ activity.registerComponentCallbacks(this)
+ }
+
+ override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) =
+ bundle.putBoolean(RUNTIME_STATE_RECREATE_TO_UPDATE_THEME, recreateToUpdateTheme)
+
+ override fun onActivityDestroyed(unused: Activity) =
+ WallpaperColorHints.get(activity).unregisterOnColorsChangedListener(this)
+
+ override fun onConfigurationChanged(config: Configuration) = updateTheme()
+
+ override fun onLowMemory() {}
+
+ override fun onColorHintsChanged(colorHints: Int) = updateTheme()
+
+ private fun updateTheme() {
+ if (themeRes != Themes.getActivityThemeRes(activity)) {
+ recreateToUpdateTheme = true
+ activity.recreate()
+ }
+ }
+
+ companion object {
+
+ /**
+ * Sets a wallpaper dependent theme on this activity. The activity is automatically
+ * recreated when a wallpaper change can potentially change the theme.
+ */
+ @JvmStatic
+ fun Activity.setWallpaperDependentTheme() {
+ if (!isDestroyed) {
+ WallpaperThemeManager(this)
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/util/rects/Rects.kt b/src/com/android/launcher3/util/rects/Rects.kt
index 1e6d717..2f1942a 100644
--- a/src/com/android/launcher3/util/rects/Rects.kt
+++ b/src/com/android/launcher3/util/rects/Rects.kt
@@ -18,6 +18,24 @@
import android.graphics.Rect
import android.view.View
+import com.android.launcher3.Utilities
+
+/**
+ * Linearly interpolate between two rectangles. The result is stored in the rect the function is
+ * called on.
+ *
+ * @param start the starting rectangle
+ * @param end the ending rectangle
+ * @param t the interpolation factor, where 0 is the start and 1 is the end
+ */
+fun Rect.lerpRect(start: Rect, end: Rect, t: Float) {
+ set(
+ Utilities.mapRange(t, start.left.toFloat(), end.left.toFloat()).toInt(),
+ Utilities.mapRange(t, start.top.toFloat(), end.top.toFloat()).toInt(),
+ Utilities.mapRange(t, start.right.toFloat(), end.right.toFloat()).toInt(),
+ Utilities.mapRange(t, start.bottom.toFloat(), end.bottom.toFloat()).toInt(),
+ )
+}
/** Copy the coordinates of the [view] relative to its parent into this rectangle. */
fun Rect.set(view: View) {
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index b164b7f..81968fc 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -62,6 +62,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.DropTargetHandler;
+import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -166,6 +167,11 @@
return false;
}
+ /** Returns the RootView */
+ default View getRootView() {
+ return getDragLayer();
+ }
+
/**
* The root view to support drag-and-drop and popup support.
*/
@@ -410,7 +416,8 @@
View v, Intent intent, @Nullable ItemInfo item) {
Preconditions.assertUIThread();
Context context = (Context) this;
- if (isAppBlockedForSafeMode() && !new ApplicationInfoWrapper(context, intent).isSystem()) {
+ if (LauncherAppState.getInstance(context).isSafeModeEnabled()
+ && !new ApplicationInfoWrapper(context, intent).isSystem()) {
Toast.makeText(context, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
return null;
}
@@ -456,11 +463,6 @@
return null;
}
- /** Returns {@code true} if an app launch is blocked due to safe mode. */
- default boolean isAppBlockedForSafeMode() {
- return false;
- }
-
/**
* Creates and logs a new app launch event.
*/
@@ -476,6 +478,7 @@
* @param v View initiating a launch.
* @param item Item associated with the view.
*/
+ @NonNull
default ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) {
int left = 0, top = 0;
int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
@@ -525,6 +528,11 @@
return false;
}
+ /** Returns the current ActivityContext as context */
+ default Context asContext() {
+ return (Context) this;
+ }
+
/**
* Returns the ActivityContext associated with the given Context, or throws an exception if
* the Context is not associated with any ActivityContext.
diff --git a/src/com/android/launcher3/views/ArrowTipView.java b/src/com/android/launcher3/views/ArrowTipView.java
index bb4f040..abb0081 100644
--- a/src/com/android/launcher3/views/ArrowTipView.java
+++ b/src/com/android/launcher3/views/ArrowTipView.java
@@ -73,7 +73,7 @@
}
};
- private final ActivityContext mActivityContext;
+ protected final ActivityContext mActivityContext;
private final Handler mHandler = new Handler();
private boolean mIsPointingUp;
private Runnable mOnClosed;
@@ -103,16 +103,26 @@
R.dimen.arrow_toast_arrow_width);
mArrowMinOffset = context.getResources().getDimensionPixelSize(
R.dimen.dynamic_grid_cell_border_spacing);
- TypedArray ta = context.obtainStyledAttributes(R.styleable.ArrowTipView);
+ Context localContext = context;
+ TypedArray ta = localContext.obtainStyledAttributes(R.styleable.ArrowTipView);
// Set style to default to avoid inflation issues with missing attributes.
if (!ta.hasValue(R.styleable.ArrowTipView_arrowTipBackground)
|| !ta.hasValue(R.styleable.ArrowTipView_arrowTipTextColor)) {
- context = new ContextThemeWrapper(context, R.style.ArrowTipStyle);
+ localContext = new ContextThemeWrapper(localContext, R.style.ArrowTipStyle);
}
- mArrowViewPaintColor = ta.getColor(R.styleable.ArrowTipView_arrowTipBackground,
+ mArrowViewPaintColor = applyArrowPaintColor(ta, localContext);
+ init(localContext, layoutId);
+ }
+
+ protected int applyArrowPaintColor(TypedArray typedArray, Context context) {
+ int arrowPaintColor = typedArray.getColor(R.styleable.ArrowTipView_arrowTipBackground,
context.getColor(R.color.arrow_tip_view_bg));
- ta.recycle();
- init(context, layoutId);
+ typedArray.recycle();
+ return arrowPaintColor;
+ }
+
+ protected int getArrowId() {
+ return R.id.arrow;
}
@Override
@@ -154,7 +164,7 @@
inflate(context, layoutId, this);
setOrientation(LinearLayout.VERTICAL);
- mArrowView = findViewById(R.id.arrow);
+ mArrowView = findViewById(getArrowId());
updateArrowTipInView(mIsPointingUp);
setAlpha(0);
@@ -343,6 +353,34 @@
parent.addView(this);
requestLayout();
}
+ return showAtLocation(arrowXCoord, yCoordDownPointingTip, yCoordUpPointingTip,
+ minViewMargin, parentViewWidth, parentViewHeight, shouldAutoClose);
+ }
+
+ /**
+ * Show the ArrowTipView (tooltip) custom aligned. The tooltip is vertically flipped if it
+ * cannot fit on screen in the requested orientation.
+ *
+ * @param arrowXCoord The X coordinate for the arrow on the tooltip. The arrow is usually in the
+ * center of tooltip unless the tooltip goes beyond screen margin.
+ * @param yCoordDownPointingTip The Y coordinate of the pointed tip end of the tooltip when the
+ * tooltip is placed pointing downwards.
+ * @param yCoordUpPointingTip The Y coordinate of the pointed tip end of the tooltip when the
+ * tooltip is placed pointing upwards.
+ * @param minViewMargin The view margin in pixels from the tip end to the y coordinate.
+ * @param parentViewWidth The width in pixels of the parent view.
+ * @param parentViewHeight The height in pixels of the parent view.
+ * @param shouldAutoClose If Tooltip should be auto close.
+ * @return The tool tip view. {@code null} if the tip can not be shown.
+ */
+ protected ArrowTipView showAtLocation(
+ @Px int arrowXCoord,
+ @Px int yCoordDownPointingTip,
+ @Px int yCoordUpPointingTip,
+ @Px int minViewMargin,
+ @Px int parentViewWidth,
+ @Px int parentViewHeight,
+ boolean shouldAutoClose) {
post(() -> {
// Adjust the tooltip horizontally.
diff --git a/src/com/android/launcher3/widget/LauncherWidgetHolder.java b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
index f499fca..78197e2 100644
--- a/src/com/android/launcher3/widget/LauncherWidgetHolder.java
+++ b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
@@ -41,7 +41,6 @@
import androidx.annotation.WorkerThread;
import com.android.launcher3.BaseActivity;
-import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.model.data.ItemInfo;
@@ -50,6 +49,7 @@
import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.util.ResourceBasedOverride;
import com.android.launcher3.util.SafeCloseable;
+import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.widget.custom.CustomWidgetManager;
import java.util.ArrayList;
@@ -218,8 +218,7 @@
* @param widgetId The ID of the widget
* @param requestCode The request code
*/
- public void startConfigActivity(@NonNull BaseDraggingActivity activity, int widgetId,
- int requestCode) {
+ public void startConfigActivity(@NonNull BaseActivity activity, int widgetId, int requestCode) {
if (!WIDGETS_ENABLED) {
sendActionCancelled(activity, requestCode);
return;
@@ -245,7 +244,7 @@
* the configuration of the {@code widgetId} app widget, or null of options cannot be produced.
*/
@Nullable
- protected Bundle getConfigurationActivityOptions(@NonNull BaseDraggingActivity activity,
+ protected Bundle getConfigurationActivityOptions(@NonNull ActivityContext activity,
int widgetId) {
LauncherAppWidgetHostView view = mViews.get(widgetId);
if (view == null) {
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/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 4811a17..7a27bf4 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -153,17 +153,19 @@
mWidgetAddButton = findViewById(R.id.widget_add_button);
if (enableWidgetTapToAdd()) {
-
setAccessibilityDelegate(new AccessibilityDelegate() {
@Override
public void onInitializeAccessibilityNodeInfo(View host,
AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
- String accessibilityLabel = getResources().getString(mWidgetAddButton.isShown()
- ? R.string.widget_cell_tap_to_hide_add_button_label
- : R.string.widget_cell_tap_to_show_add_button_label);
- info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK,
- accessibilityLabel));
+ if (hasOnClickListeners()) {
+ String accessibilityLabel = getResources().getString(
+ mWidgetAddButton.isShown()
+ ? R.string.widget_cell_tap_to_hide_add_button_label
+ : R.string.widget_cell_tap_to_show_add_button_label);
+ info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK,
+ accessibilityLabel));
+ }
}
});
mWidgetAddButton.setVisibility(INVISIBLE);
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index d850fc6..ab0f9a7 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -577,14 +577,11 @@
public void exitSearchMode() {
if (!mIsInSearchMode) return;
onSearchResults(new ArrayList<>());
- WidgetsRecyclerView searchRecyclerView = mAdapters.get(
- AdapterHolder.SEARCH).mWidgetsRecyclerView;
// Remove all views when exiting the search mode; this prevents animating from stale results
// to new ones the next time we enter search mode. By the time recycler view is hidden,
// layout may not have happened to clear up existing results. So, instead of waiting for it
// to happen, we clear the views here.
- searchRecyclerView.swapAdapter(
- searchRecyclerView.getAdapter(), /*removeAndRecycleExistingViews=*/ true);
+ mAdapters.get(AdapterHolder.SEARCH).reset();
setViewVisibilityBasedOnSearch(/*isInSearchMode=*/ false);
if (mHasWorkProfile) {
mViewPager.snapToPage(AdapterHolder.PRIMARY);
@@ -613,13 +610,12 @@
mNoWidgetsView.setVisibility(GONE);
} else {
mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.setVisibility(GONE);
- mAdapters.get(getCurrentAdapterHolderType()).mWidgetsRecyclerView.setVisibility(
- VISIBLE);
- if (mRecommendedWidgetsCount > 0) {
- // Display recommendations immediately, if present, so that other parts of sticky
- // header (e.g. personal / work tabs) don't flash in interim.
- mWidgetRecommendationsContainer.setVisibility(VISIBLE);
- }
+ AdapterHolder currentAdapterHolder = mAdapters.get(getCurrentAdapterHolderType());
+ // Remove all views when exiting the search mode; this prevents animating / flashing old
+ // list position / state.
+ currentAdapterHolder.reset();
+ currentAdapterHolder.mWidgetsRecyclerView.setVisibility(VISIBLE);
+ post(this::onRecommendedWidgetsBound);
// Visibility of recycler views and headers are handled in methods below.
onWidgetsBound();
}
@@ -1126,6 +1122,21 @@
mWidgetsListItemAnimator = new WidgetsListItemAnimator();
}
+ /**
+ * Swaps the adapter to existing adapter to prevent the recycler view from using stale view
+ * to animate in the new visibility update.
+ *
+ * <p>For instance, when clearing search text and re-entering search with new list shouldn't
+ * use stale results to animate in new results. Alternative is setting list animators to
+ * null, but, we need animations with the default item animator.
+ */
+ private void reset() {
+ mWidgetsRecyclerView.swapAdapter(
+ mWidgetsListAdapter,
+ /*removeAndRecycleExistingViews=*/ true
+ );
+ }
+
private int getEmptySpaceHeight() {
return mStickyHeaderLayout != null ? mStickyHeaderLayout.getHeaderHeight() : 0;
}
diff --git a/src_no_quickstep/com/android/launcher3/dagger/Modules.kt b/src_no_quickstep/com/android/launcher3/dagger/Modules.kt
index dab33a0..c3bf7c5 100644
--- a/src_no_quickstep/com/android/launcher3/dagger/Modules.kt
+++ b/src_no_quickstep/com/android/launcher3/dagger/Modules.kt
@@ -25,3 +25,8 @@
@Module abstract class ApiWrapperModule {}
@Module abstract class PluginManagerWrapperModule {}
+
+@Module object StaticObjectModule {}
+
+// Module containing bindings for the final derivative app
+@Module abstract class AppModule {}
diff --git a/src_no_quickstep/com/android/launcher3/uioverrides/states/AllAppsState.java b/src_no_quickstep/com/android/launcher3/uioverrides/states/AllAppsState.java
index 9865516..03a5535 100644
--- a/src_no_quickstep/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/src_no_quickstep/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -18,8 +18,6 @@
import static com.android.app.animation.Interpolators.DECELERATE;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_ALLAPPS;
-import android.content.Context;
-
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
@@ -31,8 +29,6 @@
*/
public class AllAppsState extends LauncherState {
- private static final float PARALLAX_COEFFICIENT = .125f;
-
private static final int STATE_FLAGS = FLAG_WORKSPACE_INACCESSIBLE;
public AllAppsState(int id) {
@@ -40,8 +36,7 @@
}
@Override
- public <DEVICE_PROFILE_CONTEXT extends Context & ActivityContext>
- int getTransitionDuration(DEVICE_PROFILE_CONTEXT context, boolean isToState) {
+ public int getTransitionDuration(ActivityContext context, boolean isToState) {
return isToState
? context.getDeviceProfile().allAppsOpenDuration
: context.getDeviceProfile().allAppsCloseDuration;
diff --git a/src_no_quickstep/com/android/launcher3/uioverrides/states/OverviewState.java b/src_no_quickstep/com/android/launcher3/uioverrides/states/OverviewState.java
index 7a228c4..532a338 100644
--- a/src_no_quickstep/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/src_no_quickstep/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -17,12 +17,11 @@
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
-import android.content.Context;
-
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.util.Themes;
+import com.android.launcher3.views.ActivityContext;
/**
* Definition for overview state
@@ -34,7 +33,7 @@
}
@Override
- public int getTransitionDuration(Context context, boolean isToState) {
+ public int getTransitionDuration(ActivityContext context, boolean isToState) {
return 250;
}
diff --git a/tests/assets/databases/GridMigrationTest/result5x5to5x8.db b/tests/assets/databases/GridMigrationTest/result5x5to5x8.db
index 311a112..d750774 100644
--- a/tests/assets/databases/GridMigrationTest/result5x5to5x8.db
+++ b/tests/assets/databases/GridMigrationTest/result5x5to5x8.db
Binary files differ
diff --git a/tests/assets/databases/GridMigrationTest/result5x5to5x8WithShift.db b/tests/assets/databases/GridMigrationTest/result5x5to5x8WithShift.db
deleted file mode 100644
index d750774..0000000
--- a/tests/assets/databases/GridMigrationTest/result5x5to5x8WithShift.db
+++ /dev/null
Binary files differ
diff --git a/tests/assets/databases/GridMigrationTest/test_launcher_2.db b/tests/assets/databases/GridMigrationTest/test_launcher_2.db
deleted file mode 100644
index b538e26..0000000
--- a/tests/assets/databases/GridMigrationTest/test_launcher_2.db
+++ /dev/null
Binary files differ
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/FakeLauncherPrefs.kt b/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefs.kt
index 7573d2f..5e1e548 100644
--- a/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefs.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefs.kt
@@ -35,6 +35,5 @@
MODE_PRIVATE,
)
- override val Item.sharedPrefs: SharedPreferences
- get() = backingPrefs
+ override fun getSharedPrefs(item: Item): SharedPreferences = backingPrefs
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/ProxyPrefsTest.kt b/tests/multivalentTests/src/com/android/launcher3/ProxyPrefsTest.kt
new file mode 100644
index 0000000..54f6f63
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/ProxyPrefsTest.kt
@@ -0,0 +1,99 @@
+/*
+ * 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
+
+import android.content.Context.MODE_PRIVATE
+import android.platform.uiautomatorhelpers.DeviceHelpers.context
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.LauncherPrefs.Companion.backedUpItem
+import com.google.common.truth.Truth.assertThat
+import java.util.UUID
+import org.junit.After
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ProxyPrefsTest {
+
+ private val prefName = "pref-test-" + UUID.randomUUID().toString()
+
+ private val proxyPrefs by lazy {
+ ProxyPrefs(
+ context,
+ context.getSharedPreferences(prefName, MODE_PRIVATE).apply { edit().clear().commit() },
+ )
+ }
+ private val launcherPrefs by lazy { LauncherPrefs(context) }
+
+ @After
+ fun tearDown() {
+ context.deleteSharedPreferences(prefName)
+ }
+
+ @Test
+ fun `returns fallback value if present`() {
+ launcherPrefs.putSync(TEST_ENTRY.to("new_value"))
+ assertThat(proxyPrefs.get(TEST_ENTRY)).isEqualTo("new_value")
+ }
+
+ @Test
+ fun `returns default value if not present`() {
+ launcherPrefs.removeSync(TEST_ENTRY)
+ assertThat(proxyPrefs.get(TEST_ENTRY)).isEqualTo("default_value")
+ }
+
+ @Test
+ fun `returns overridden value if present`() {
+ launcherPrefs.putSync(TEST_ENTRY.to("new_value"))
+ proxyPrefs.putSync(TEST_ENTRY.to("overridden_value"))
+ assertThat(proxyPrefs.get(TEST_ENTRY)).isEqualTo("overridden_value")
+ }
+
+ @Test
+ fun `value not present when removed`() {
+ launcherPrefs.putSync(TEST_ENTRY.to("new_value"))
+ proxyPrefs.removeSync(TEST_ENTRY)
+ assertThat(proxyPrefs.has(TEST_ENTRY)).isFalse()
+ }
+
+ @Test
+ fun `returns default if removed`() {
+ launcherPrefs.putSync(TEST_ENTRY.to("new_value"))
+ proxyPrefs.removeSync(TEST_ENTRY)
+ assertThat(proxyPrefs.get(TEST_ENTRY)).isEqualTo("default_value")
+ }
+
+ @Test
+ fun `value present on init`() {
+ launcherPrefs.putSync(TEST_ENTRY.to("new_value"))
+ assertThat(proxyPrefs.has(TEST_ENTRY)).isTrue()
+ }
+
+ @Test
+ fun `value absent on init`() {
+ launcherPrefs.removeSync(TEST_ENTRY)
+ assertThat(proxyPrefs.has(TEST_ENTRY)).isFalse()
+ }
+
+ companion object {
+
+ val TEST_ENTRY =
+ backedUpItem("test_prefs", "default_value", EncryptionType.DEVICE_PROTECTED)
+ }
+}
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/LoaderCursorTest.java b/tests/multivalentTests/src/com/android/launcher3/model/LoaderCursorTest.java
index 63359ec..11047fb 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/LoaderCursorTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -16,6 +16,9 @@
package com.android.launcher3.model;
+import static android.graphics.BitmapFactory.decodeByteArray;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+
import static androidx.test.InstrumentationRegistry.getContext;
import static com.android.launcher3.LauncherSettings.Favorites.APPWIDGET_ID;
@@ -40,8 +43,11 @@
import static com.android.launcher3.LauncherSettings.Favorites.SPANY;
import static com.android.launcher3.LauncherSettings.Favorites.TITLE;
import static com.android.launcher3.LauncherSettings.Favorites._ID;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED;
import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
@@ -52,13 +58,19 @@
import android.content.Context;
import android.content.Intent;
import android.database.MatrixCursor;
+import android.graphics.Bitmap;
import android.os.Process;
+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;
+import com.android.launcher3.Flags;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.Executors;
@@ -67,6 +79,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -77,6 +90,9 @@
@RunWith(AndroidJUnit4.class)
public class LoaderCursorTest {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
private LauncherModelHelper mModelHelper;
private LauncherAppState mApp;
private PackageManagerHelper mPmHelper;
@@ -87,6 +103,12 @@
private LoaderCursor mLoaderCursor;
+ private static byte[] sTestBlob = new byte[] {
+ -119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 1, 0, 0, 0, 1,
+ 8, 4, 0, 0, 0, -75, 28, 12, 2, 0, 0, 0, 11, 73, 68, 65, 84, 120, -38, 99, 100, 96, 0,
+ 0, 0, 6, 0, 2, 48, -127, -48, 47, 0, 0, 0, 0, 73, 69, 78, 68, -82, 66, 96, -126
+ };
+
@Before
public void setup() {
mModelHelper = new LauncherModelHelper();
@@ -119,7 +141,8 @@
.add(PROFILE_ID, 0)
.add(ITEM_TYPE, itemType)
.add(TITLE, title)
- .add(CONTAINER, CONTAINER_DESKTOP);
+ .add(CONTAINER, CONTAINER_DESKTOP)
+ .add(ICON, sTestBlob);
}
@Test
@@ -161,7 +184,12 @@
@Test
public void loadSimpleShortcut() {
- initCursor(ITEM_TYPE_DEEP_SHORTCUT, "my-shortcut");
+ mCursor.newRow()
+ .add(_ID, 1)
+ .add(PROFILE_ID, 0)
+ .add(ITEM_TYPE, ITEM_TYPE_DEEP_SHORTCUT)
+ .add(TITLE, "my-shortcut")
+ .add(CONTAINER, CONTAINER_DESKTOP);
assertTrue(mLoaderCursor.moveToNext());
WorkspaceItemInfo info = mLoaderCursor.loadSimpleWorkspaceItem();
@@ -223,6 +251,68 @@
newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 3), true));
}
+ @Test
+ @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB)
+ public void ifArchivedWithFlag_whenloadWorkspaceTitleAndIcon_thenLoadIconFromDb() {
+ // Given
+ initCursor(ITEM_TYPE_APPLICATION, "title");
+ assertTrue(mLoaderCursor.moveToNext());
+ WorkspaceItemInfo itemInfo = new WorkspaceItemInfo();
+ itemInfo.bitmap = null;
+ itemInfo.runtimeStatusFlags |= FLAG_ARCHIVED;
+ Bitmap expectedBitmap = LauncherIcons.obtain(mContext)
+ .createIconBitmap(decodeByteArray(sTestBlob, 0, sTestBlob.length))
+ .icon;
+ // When
+ mLoaderCursor.loadWorkspaceTitleAndIcon(false, true, itemInfo);
+ // Then
+ assertThat(itemInfo.bitmap.icon).isNotNull();
+ assertThat(itemInfo.bitmap.icon.sameAs(expectedBitmap)).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB)
+ public void ifArchivedWithFlag_whenLoadIconFromDb_thenLoadIconFromBlob() {
+ // Given
+ initCursor(ITEM_TYPE_APPLICATION, "title");
+ assertTrue(mLoaderCursor.moveToNext());
+ WorkspaceItemInfo itemInfo = new WorkspaceItemInfo();
+ itemInfo.runtimeStatusFlags |= FLAG_ARCHIVED;
+ // Then
+ assertTrue(mLoaderCursor.loadIconFromDb(itemInfo));
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB)
+ public void ifArchivedWithoutFlag_whenLoadWorkspaceTitleAndIcon_thenDoNotLoadFromDb() {
+ // Given
+ initCursor(ITEM_TYPE_APPLICATION, "title");
+ assertTrue(mLoaderCursor.moveToNext());
+ WorkspaceItemInfo itemInfo = new WorkspaceItemInfo();
+ itemInfo.bitmap = null;
+ itemInfo.runtimeStatusFlags |= FLAG_ARCHIVED;
+ Intent intent = new Intent();
+ intent.setComponent(new ComponentName("package", "class"));
+ itemInfo.intent = intent;
+ // When
+ mLoaderCursor.loadWorkspaceTitleAndIcon(false, false, itemInfo);
+ // Then
+ assertThat(itemInfo.bitmap).isNull();
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB)
+ public void ifArchivedWithoutFlag_whenLoadIconFromDb_thenDoNotLoadFromBlob() {
+ // Given
+ initCursor(ITEM_TYPE_APPLICATION, "title");
+ assertTrue(mLoaderCursor.moveToNext());
+ WorkspaceItemInfo itemInfo = new WorkspaceItemInfo();
+ itemInfo.runtimeStatusFlags |= FLAG_ARCHIVED;
+ // Then
+ assertFalse(mLoaderCursor.loadIconFromDb(itemInfo));
+ }
+
+
private ItemInfo newItemInfo(int cellX, int cellY, int spanX, int spanY,
int container, int screenId) {
ItemInfo info = new ItemInfo();
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/DaggerGraphs.kt b/tests/multivalentTests/src/com/android/launcher3/util/DaggerGraphs.kt
index 68da9ff..b66a9d3 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/DaggerGraphs.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/DaggerGraphs.kt
@@ -19,6 +19,8 @@
import com.android.launcher3.FakeLauncherPrefs
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.dagger.ApiWrapperModule
+import com.android.launcher3.dagger.AppModule
+import com.android.launcher3.dagger.StaticObjectModule
import com.android.launcher3.dagger.WindowManagerProxyModule
import dagger.Binds
import dagger.Module
@@ -31,11 +33,21 @@
}
/** All modules. We also exclude the plugin module from tests */
-@Module(includes = [ApiWrapperModule::class, WindowManagerProxyModule::class])
+@Module(
+ includes =
+ [
+ ApiWrapperModule::class,
+ WindowManagerProxyModule::class,
+ StaticObjectModule::class,
+ AppModule::class,
+ ]
+)
class AllModulesForTest
/** All modules except the WMProxy */
-@Module(includes = [ApiWrapperModule::class]) class AllModulesMinusWMProxy
+@Module(includes = [ApiWrapperModule::class, StaticObjectModule::class, AppModule::class])
+class AllModulesMinusWMProxy
/** All modules except the ApiWrapper */
-@Module(includes = [WindowManagerProxyModule::class]) class AllModulesMinusApiWrapper
+@Module(includes = [WindowManagerProxyModule::class, StaticObjectModule::class, AppModule::class])
+class AllModulesMinusApiWrapper
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/LockedUserStateTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/LockedUserStateTest.kt
index 2711d7a..99f5a5b 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/LockedUserStateTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/LockedUserStateTest.kt
@@ -22,7 +22,10 @@
import android.os.UserManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
import com.google.common.truth.Truth.assertThat
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -38,17 +41,24 @@
private val userManager: UserManager = mock()
private val context: Context = mock()
+ private val lifeCycle: DaggerSingletonTracker = mock()
@Before
fun setup() {
whenever(context.getSystemService(UserManager::class.java)).thenReturn(userManager)
}
+ @After
+ fun tearDown() {
+ UI_HELPER_EXECUTOR.submit {}.get()
+ MAIN_EXECUTOR.submit {}.get()
+ }
+
@Test
fun runOnUserUnlocked_runs_action_immediately_if_already_unlocked() {
whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true)
val action: Runnable = mock()
- LockedUserState(context).runOnUserUnlocked(action)
+ LockedUserState(context, lifeCycle).runOnUserUnlocked(action)
verify(action).run()
}
@@ -56,23 +66,23 @@
fun runOnUserUnlocked_waits_to_run_action_until_user_is_unlocked() {
whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false)
val action: Runnable = mock()
- val state = LockedUserState(context)
+ val state = LockedUserState(context, lifeCycle)
state.runOnUserUnlocked(action)
// b/343530737
verifyNoMoreInteractions(action)
- state.mUserUnlockedReceiver.onReceive(context, Intent(Intent.ACTION_USER_UNLOCKED))
+ state.userUnlockedReceiver.onReceive(context, Intent(Intent.ACTION_USER_UNLOCKED))
verify(action).run()
}
@Test
fun isUserUnlocked_returns_true_when_user_is_unlocked() {
whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true)
- assertThat(LockedUserState(context).isUserUnlocked).isTrue()
+ assertThat(LockedUserState(context, lifeCycle).isUserUnlocked).isTrue()
}
@Test
fun isUserUnlocked_returns_false_when_user_is_locked() {
whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false)
- assertThat(LockedUserState(context).isUserUnlocked).isFalse()
+ assertThat(LockedUserState(context, lifeCycle).isUserUnlocked).isFalse()
}
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt b/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt
index efe7637..0da8891 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt
@@ -48,6 +48,7 @@
class SandboxApplication private constructor(private val base: SandboxApplicationWrapper) :
SandboxModelContext(base), TestRule {
+ @JvmOverloads
constructor(
base: Context = ApplicationProvider.getApplicationContext()
) : this(SandboxApplicationWrapper(base))
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/GridMigrationTest.kt b/tests/src/com/android/launcher3/model/GridMigrationTest.kt
index 8bd0c60..380c208 100644
--- a/tests/src/com/android/launcher3/model/GridMigrationTest.kt
+++ b/tests/src/com/android/launcher3/model/GridMigrationTest.kt
@@ -82,8 +82,6 @@
@RunWith(AndroidJUnit4::class)
class GridMigrationTest {
private val DB_FILE = "test_launcher.db"
- // This DB is used for testing the heuristic where we add an extra row at the bottom.
- private val DB_FILE_NO_SHIFT = "test_launcher_2.db"
@JvmField
@Rule
@@ -230,42 +228,6 @@
@JvmField
@Rule
- val result5x5to5x8WithShift =
- TestToPhoneFileCopier(
- src = "databases/GridMigrationTest/result5x5to5x8WithShift.db",
- dest = "databases/result5x5to5x8WithShift.db",
- removeOnFinish = true,
- )
-
- @Test
- fun `5x5 to 5x8 with cells shifting down`() =
- runTest(
- src = GridMigrationData(DB_FILE, DeviceGridState(5, 5, 5, TYPE_PHONE, DB_FILE)),
- dst =
- GridMigrationData(
- null, // in memory db, to download a new db change null
- // for
- // the filename of the db name to store it. Do not use existing names.
- DeviceGridState(5, 8, 5, TYPE_PHONE, ""),
- ),
- target =
- GridMigrationData(
- "result5x5to5x8WithShift.db",
- DeviceGridState(5, 8, 5, TYPE_PHONE, ""),
- ),
- )
-
- @JvmField
- @Rule
- val fileCopierNoShift =
- TestToPhoneFileCopier(
- src = "databases/GridMigrationTest/$DB_FILE_NO_SHIFT",
- dest = "databases/$DB_FILE_NO_SHIFT",
- removeOnFinish = true,
- )
-
- @JvmField
- @Rule
val result5x5to5x8 =
TestToPhoneFileCopier(
src = "databases/GridMigrationTest/result5x5to5x8.db",
@@ -274,16 +236,13 @@
)
@Test
- fun `5x5 to 5x8 without cell shift`() =
+ fun `5x5 to 5x8`() =
runTest(
- src =
- GridMigrationData(
- DB_FILE_NO_SHIFT,
- DeviceGridState(5, 5, 5, TYPE_PHONE, DB_FILE_NO_SHIFT),
- ),
+ src = GridMigrationData(DB_FILE, DeviceGridState(5, 5, 5, TYPE_PHONE, DB_FILE)),
dst =
GridMigrationData(
- null, // in memory db, to download a new db change null for
+ null, // in memory db, to download a new db change null
+ // for
// the filename of the db name to store it. Do not use existing names.
DeviceGridState(5, 8, 5, TYPE_PHONE, ""),
),
diff --git a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
index d2229c4..cdb45fc 100644
--- a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
+++ b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
@@ -1,7 +1,10 @@
package com.android.launcher3.model
import android.appwidget.AppWidgetManager
+import android.content.ComponentName
import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.LauncherActivityInfo
import android.os.Process
import android.os.UserHandle
import android.platform.test.annotations.DisableFlags
@@ -19,18 +22,31 @@
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.model.data.AppInfo
+import com.android.launcher3.model.data.IconRequestInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
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 com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
import java.util.concurrent.CountDownLatch
import java.util.function.Predicate
import junit.framework.Assert.assertEquals
@@ -51,6 +67,7 @@
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -126,8 +143,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 +173,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 +487,168 @@
// Then
verify(spyContext, times(0)).sendBroadcast(any())
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB)
+ fun `When flag on then archived AllApps icons found on Workspace loaded from db`() {
+ // Given
+ // Given
+ val activityInfo: LauncherActivityInfo = mock()
+ val applicationInfo: ApplicationInfo = mock<ApplicationInfo>().apply { isArchived = true }
+ whenever(activityInfo.applicationInfo).thenReturn(applicationInfo)
+ val expectedIconBlob = byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08)
+ val expectedComponent = ComponentName("package", "class")
+ val workspaceIconRequests =
+ listOf(
+ IconRequestInfo<WorkspaceItemInfo>(
+ WorkspaceItemInfo().apply {
+ intent = Intent().apply { component = expectedComponent }
+ },
+ activityInfo,
+ expectedIconBlob,
+ false, /* useLowResIcon */
+ )
+ )
+ val expectedAppInfo = AppInfo().apply { componentName = expectedComponent }
+ // When
+ val loader =
+ LoaderTask(
+ app,
+ bgAllAppsList,
+ BgDataModel(),
+ modelDelegate,
+ launcherBinder,
+ widgetsFilterDataProvider,
+ )
+ val actualIconRequest =
+ loader.getAppInfoIconRequestInfo(expectedAppInfo, activityInfo, workspaceIconRequests)
+ // Then
+ assertThat(actualIconRequest.iconBlob).isEqualTo(expectedIconBlob)
+ assertThat(actualIconRequest.itemInfo).isEqualTo(expectedAppInfo)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB)
+ fun `When flag on then unarchived AllApps icons not loaded from db`() {
+ // Given
+ val activityInfo: LauncherActivityInfo = mock()
+ val applicationInfo: ApplicationInfo = mock<ApplicationInfo>().apply { isArchived = false }
+ whenever(activityInfo.applicationInfo).thenReturn(applicationInfo)
+ val expectedIconBlob = byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08)
+ val expectedComponent = ComponentName("package", "class")
+ val workspaceIconRequests =
+ listOf(
+ IconRequestInfo<WorkspaceItemInfo>(
+ WorkspaceItemInfo().apply {
+ intent = Intent().apply { component = expectedComponent }
+ },
+ activityInfo,
+ expectedIconBlob,
+ false, /* useLowResIcon */
+ )
+ )
+ val expectedAppInfo = AppInfo().apply { componentName = expectedComponent }
+ // When
+ val loader =
+ LoaderTask(
+ app,
+ bgAllAppsList,
+ BgDataModel(),
+ modelDelegate,
+ launcherBinder,
+ widgetsFilterDataProvider,
+ )
+ val actualIconRequest =
+ loader.getAppInfoIconRequestInfo(expectedAppInfo, activityInfo, workspaceIconRequests)
+ // Then
+ assertThat(actualIconRequest.iconBlob).isNull()
+ assertThat(actualIconRequest.itemInfo).isEqualTo(expectedAppInfo)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB)
+ fun `When flag on then archived AllApps icon not found on Workspace not loaded from db`() {
+ // Given
+ val activityInfo: LauncherActivityInfo = mock()
+ val applicationInfo: ApplicationInfo = mock<ApplicationInfo>().apply { isArchived = true }
+ whenever(activityInfo.applicationInfo).thenReturn(applicationInfo)
+ val expectedIconBlob = byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08)
+ val expectedComponent = ComponentName("package", "class")
+ val workspaceIconRequests =
+ listOf(
+ IconRequestInfo<WorkspaceItemInfo>(
+ WorkspaceItemInfo().apply {
+ intent = Intent().apply { component = expectedComponent }
+ },
+ activityInfo,
+ expectedIconBlob,
+ false, /* useLowResIcon */
+ )
+ )
+ val expectedAppInfo =
+ AppInfo().apply { componentName = ComponentName("differentPkg", "differentClass") }
+ // When
+ val loader =
+ LoaderTask(
+ app,
+ bgAllAppsList,
+ BgDataModel(),
+ modelDelegate,
+ launcherBinder,
+ widgetsFilterDataProvider,
+ )
+ val actualIconRequest =
+ loader.getAppInfoIconRequestInfo(expectedAppInfo, activityInfo, workspaceIconRequests)
+ // Then
+ assertThat(actualIconRequest.iconBlob).isNull()
+ assertThat(actualIconRequest.itemInfo).isEqualTo(expectedAppInfo)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB)
+ fun `When flag off then archived AllApps icons not loaded from db`() {
+ // Given
+ val activityInfo: LauncherActivityInfo = mock()
+ val applicationInfo: ApplicationInfo = mock<ApplicationInfo>().apply { isArchived = true }
+ whenever(activityInfo.applicationInfo).thenReturn(applicationInfo)
+ val expectedIconBlob = byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08)
+ val workspaceIconRequests =
+ listOf(
+ IconRequestInfo<WorkspaceItemInfo>(
+ WorkspaceItemInfo(),
+ activityInfo,
+ expectedIconBlob,
+ false, /* useLowResIcon */
+ )
+ )
+ val expectedAppInfo = AppInfo()
+ // When
+ val loader =
+ LoaderTask(
+ app,
+ bgAllAppsList,
+ BgDataModel(),
+ modelDelegate,
+ launcherBinder,
+ widgetsFilterDataProvider,
+ )
+ val actualIconRequest =
+ loader.getAppInfoIconRequestInfo(expectedAppInfo, activityInfo, workspaceIconRequests)
+ // Then
+ assertThat(actualIconRequest.iconBlob).isNull()
+ assertThat(actualIconRequest.itemInfo).isEqualTo(expectedAppInfo)
+ }
+
+ @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/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java
index cb04e13..cab1ebe 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java
@@ -116,7 +116,13 @@
*/
@ScreenRecord // b/381918059
@Test
- public void testAddAndDeletePageAndFling() {
+ public void testAddAndDeletePageAndFling() throws Exception {
+ // Set workspace that includes the chrome Activity app icon on the hotseat.
+ LauncherLayoutBuilder builder = new LauncherLayoutBuilder()
+ .atHotseat(0).putApp("com.android.chrome", "com.google.android.apps.chrome.Main");
+ mLauncherLayout = TestUtil.setLauncherDefaultLayout(mTargetContext, builder);
+ reinitializeLauncherData();
+
Workspace workspace = mLauncher.getWorkspace();
// Get the first app from the hotseat
HomeAppIcon hotSeatIcon = workspace.getHotseatAppIcon(0);
diff --git a/tests/src/com/android/launcher3/util/WallpaperThemeManagerTest.kt b/tests/src/com/android/launcher3/util/WallpaperThemeManagerTest.kt
new file mode 100644
index 0000000..4c8fd8a
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/WallpaperThemeManagerTest.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.app.Activity
+import android.content.ComponentCallbacks
+import android.content.res.Configuration
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.any
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
+import com.android.dx.mockito.inline.extended.ExtendedMockito.eq
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.ExtendedMockito.times
+import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.launcher3.util.WallpaperThemeManager.Companion.setWallpaperDependentTheme
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.never
+import org.mockito.kotlin.whenever
+
+/** Tests for WallpaperThemeManager */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WallpaperThemeManagerTest {
+
+ @get:Rule val context = SandboxApplication()
+
+ @Mock lateinit var activity: Activity
+ @Captor lateinit var callbacksCaptor: ArgumentCaptor<ComponentCallbacks>
+
+ private lateinit var mockSession: StaticMockitoSession
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ mockSession = mockitoSession().spyStatic(Themes::class.java).startMocking()
+
+ doReturn(1).`when`<Int> { Themes.getActivityThemeRes(any()) }
+ doReturn(context).whenever(activity).applicationContext
+ }
+
+ @After
+ fun tearDown() {
+ mockSession.finishMocking()
+ }
+
+ @Test
+ fun `correct theme set on activity create`() {
+ activity.setWallpaperDependentTheme()
+ verify(activity, times(1)).setTheme(eq(1))
+ }
+
+ @Test
+ fun `ignores update if theme does not change`() {
+ activity.setWallpaperDependentTheme()
+ verify(activity).registerComponentCallbacks(callbacksCaptor.capture())
+ callbacksCaptor.value.onConfigurationChanged(Configuration())
+ verify(activity, never()).recreate()
+ }
+
+ @Test
+ fun `activity recreated if theme changes`() {
+ activity.setWallpaperDependentTheme()
+ verify(activity).registerComponentCallbacks(callbacksCaptor.capture())
+
+ doReturn(3).`when`<Int> { Themes.getActivityThemeRes(any()) }
+ callbacksCaptor.value.onConfigurationChanged(Configuration())
+ verify(activity, times(1)).recreate()
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index edca6dc..afc0dd5 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,
@@ -2077,12 +2131,13 @@
downTime, currentTime, action, point.x, point.y, pointerCount,
mTrackpadGestureType)
: getMotionEvent(downTime, currentTime, action, point.x, point.y, source, toolType);
+ int button = isRightClick ? MotionEvent.BUTTON_SECONDARY : MotionEvent.BUTTON_PRIMARY;
+ if (action == MotionEvent.ACTION_BUTTON_PRESS) {
+ event.setButtonState(event.getButtonState() | button);
+ }
if (action == MotionEvent.ACTION_BUTTON_PRESS
|| action == MotionEvent.ACTION_BUTTON_RELEASE) {
- event.setActionButton(MotionEvent.BUTTON_PRIMARY);
- }
- if (isRightClick) {
- event.setButtonState(event.getButtonState() | MotionEvent.BUTTON_SECONDARY);
+ event.setActionButton(button);
}
injectEvent(event);
}
@@ -2193,11 +2248,17 @@
sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetCenter,
GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_TOUCHSCREEN,
/* isRightClick= */ true, MotionEvent.TOOL_TYPE_STYLUS);
+ sendPointer(downTime, downTime, MotionEvent.ACTION_BUTTON_PRESS, targetCenter,
+ GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_TOUCHSCREEN,
+ /* isRightClick= */ true, MotionEvent.TOOL_TYPE_STYLUS);
try {
expectEvent(TestProtocol.SEQUENCE_MAIN, longClickEvent);
final UiObject2 result = waitForLauncherObject(resName);
return result;
} finally {
+ sendPointer(downTime, downTime, MotionEvent.ACTION_BUTTON_RELEASE, targetCenter,
+ GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_TOUCHSCREEN,
+ /* isRightClick= */ true, MotionEvent.TOOL_TYPE_STYLUS);
sendPointer(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, targetCenter,
GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_TOUCHSCREEN,
/* isRightClick= */ true, MotionEvent.TOOL_TYPE_STYLUS);
@@ -2212,11 +2273,17 @@
sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetCenter,
GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_MOUSE,
/* isRightClick= */ true);
+ sendPointer(downTime, downTime, MotionEvent.ACTION_BUTTON_PRESS, targetCenter,
+ GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_MOUSE,
+ /* isRightClick= */ true);
try {
expectEvent(TestProtocol.SEQUENCE_MAIN, rightClickEvent);
final UiObject2 result = waitForLauncherObject(resName);
return result;
} finally {
+ sendPointer(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_BUTTON_RELEASE,
+ targetCenter, GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_MOUSE,
+ /* isRightClick= */ true);
sendPointer(downTime, SystemClock.uptimeMillis(), ACTION_UP, targetCenter,
GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_MOUSE,
/* isRightClick= */ true);
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 4a7caf8..4230643 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -58,7 +58,7 @@
*/
public final class Workspace extends Home {
private static final int FLING_STEPS = 10;
- private static final int DEFAULT_DRAG_STEPS = 10;
+ private static final int DEFAULT_DRAG_STEPS = 15;
private static final String DROP_BAR_RES_ID = "drop_target_bar";
private static final String DELETE_TARGET_TEXT_ID = "delete_target_text";
private static final String UNINSTALL_TARGET_TEXT_ID = "uninstall_target_text";
@@ -682,7 +682,7 @@
launcher.movePointer(dragStart, targetDest,
DEFAULT_DRAG_STEPS, isDecelerating, downTime, SystemClock.uptimeMillis(),
- false, LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
+ true, LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
dropDraggedIcon(launcher, targetDest, downTime, expectDropEvents, startsActivity);
}